001// Copyright 2010, 2011 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.transform;
016
017import org.apache.tapestry5.Asset;
018import org.apache.tapestry5.ComponentResources;
019import org.apache.tapestry5.annotations.Import;
020import org.apache.tapestry5.annotations.SetupRender;
021import org.apache.tapestry5.func.F;
022import org.apache.tapestry5.func.Mapper;
023import org.apache.tapestry5.func.Worker;
024import org.apache.tapestry5.ioc.Resource;
025import org.apache.tapestry5.ioc.services.SymbolSource;
026import org.apache.tapestry5.model.MutableComponentModel;
027import org.apache.tapestry5.plastic.*;
028import org.apache.tapestry5.services.AssetSource;
029import org.apache.tapestry5.services.TransformConstants;
030import org.apache.tapestry5.services.javascript.JavaScriptSupport;
031import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
032import org.apache.tapestry5.services.transform.TransformationSupport;
033
034import java.util.Locale;
035
036/**
037 * Implements the {@link Import} annotation, both at the class and at the method level.
038 *
039 * @since 5.2.0
040 */
041public class ImportWorker implements ComponentClassTransformWorker2
042{
043    private final JavaScriptSupport javascriptSupport;
044
045    private final SymbolSource symbolSource;
046
047    private final AssetSource assetSource;
048
049    private final Worker<Asset> importLibrary = new Worker<Asset>()
050    {
051        public void work(Asset asset)
052        {
053            javascriptSupport.importJavaScriptLibrary(asset);
054        }
055    };
056
057    private final Worker<Asset> importStylesheet = new Worker<Asset>()
058    {
059        public void work(Asset asset)
060        {
061            javascriptSupport.importStylesheet(asset);
062        }
063    };
064
065    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
066    {
067        public String map(String element)
068        {
069            return symbolSource.expandSymbols(element);
070        }
071    };
072
073    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource)
074    {
075        this.javascriptSupport = javascriptSupport;
076        this.symbolSource = symbolSource;
077        this.assetSource = assetSource;
078    }
079
080    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
081    {
082        processClassAnnotationAtSetupRenderPhase(componentClass, model);
083
084        for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
085        {
086            decorateMethod(componentClass, model, m);
087        }
088    }
089
090    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
091    {
092        Import annotation = componentClass.getAnnotation(Import.class);
093
094        if (annotation != null)
095        {
096            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
097
098            decorateMethod(componentClass, model, setupRender, annotation);
099
100            model.addRenderPhase(SetupRender.class);
101        }
102    }
103
104    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
105    {
106        Import annotation = method.getAnnotation(Import.class);
107
108        decorateMethod(componentClass, model, method, annotation);
109    }
110
111    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
112                                Import annotation)
113    {
114        importStacks(method, annotation.stack());
115
116        importLibraries(componentClass, model, method, annotation.library());
117
118        importStylesheets(componentClass, model, method, annotation.stylesheet());
119    }
120
121    private void importStacks(PlasticMethod method, String[] stacks)
122    {
123        if (stacks.length != 0)
124        {
125            method.addAdvice(createImportStackAdvice(stacks));
126        }
127    }
128
129    private MethodAdvice createImportStackAdvice(final String[] stacks)
130    {
131        return new MethodAdvice()
132        {
133            public void advise(MethodInvocation invocation)
134            {
135                for (String stack : stacks)
136                {
137                    javascriptSupport.importStack(stack);
138                }
139
140                invocation.proceed();
141            }
142        };
143    }
144
145    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
146                                 String[] paths)
147    {
148        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
149    }
150
151    private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
152                                   String[] paths)
153    {
154        decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet);
155    }
156
157    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
158                                             PlasticMethod method, String[] paths, Worker<Asset> operation)
159    {
160        if (paths.length == 0)
161            return;
162
163        String[] expandedPaths = expandPaths(paths);
164
165        PlasticField assetListField = componentClass.introduceField(Asset[].class,
166                "importedAssets_" + method.getDescription().methodName);
167
168        initializeAssetsFromPaths(model.getBaseResource(), expandedPaths, assetListField);
169
170        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
171    }
172
173    private String[] expandPaths(String[] paths)
174    {
175        return F.flow(paths).map(expandSymbols).toArray(String.class);
176    }
177
178    private void initializeAssetsFromPaths(final Resource baseResource,
179                                           final String[] expandedPaths, final PlasticField assetsField)
180    {
181        assetsField.injectComputed(new ComputedValue<Asset[]>()
182        {
183            public Asset[] get(InstanceContext context)
184            {
185                ComponentResources resources = context.get(ComponentResources.class);
186
187                return convertPathsToAssetArray(baseResource, resources.getLocale(), expandedPaths);
188            }
189        });
190    }
191
192    private Asset[] convertPathsToAssetArray(final Resource baseResource, final Locale locale, String[] assetPaths)
193    {
194        return F.flow(assetPaths).map(new Mapper<String, Asset>()
195        {
196            public Asset map(String assetPath)
197            {
198                return assetSource.getAsset(baseResource, assetPath, locale);
199            }
200        }).toArray(Asset.class);
201    }
202
203    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
204                                               final Worker<Asset> operation)
205    {
206        method.addAdvice(new MethodAdvice()
207        {
208            public void advise(MethodInvocation invocation)
209            {
210                Asset[] assets = (Asset[]) access.get(invocation.getInstance());
211
212                F.flow(assets).each(operation);
213
214                invocation.proceed();
215            }
216        });
217    }
218}