001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.internal.transform;
014
015import org.apache.tapestry5.Asset;
016import org.apache.tapestry5.ComponentResources;
017import org.apache.tapestry5.SymbolConstants;
018import org.apache.tapestry5.annotations.Import;
019import org.apache.tapestry5.annotations.SetupRender;
020import org.apache.tapestry5.func.F;
021import org.apache.tapestry5.func.Mapper;
022import org.apache.tapestry5.func.Worker;
023import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
024import org.apache.tapestry5.ioc.services.SymbolSource;
025import org.apache.tapestry5.model.MutableComponentModel;
026import org.apache.tapestry5.plastic.*;
027import org.apache.tapestry5.services.AssetSource;
028import org.apache.tapestry5.services.TransformConstants;
029import org.apache.tapestry5.services.javascript.Initialization;
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.ArrayList;
035import java.util.List;
036
037/**
038 * Implements the {@link Import} annotation, both at the class and at the method level.
039 *
040 * @since 5.2.0
041 */
042public class ImportWorker implements ComponentClassTransformWorker2
043{
044    private final JavaScriptSupport javascriptSupport;
045
046    private final SymbolSource symbolSource;
047
048    private final AssetSource assetSource;
049    
050    private final ResourceChangeTracker resourceChangeTracker;
051    
052    private final boolean multipleClassLoaders;
053
054    private final Worker<Asset> importLibrary = new Worker<Asset>()
055    {
056        public void work(Asset asset)
057        {
058            javascriptSupport.importJavaScriptLibrary(asset);
059        }
060    };
061
062    private final Worker<Asset> importStylesheet = new Worker<Asset>()
063    {
064        public void work(Asset asset)
065        {
066            javascriptSupport.importStylesheet(asset);
067        }
068    };
069
070    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
071    {
072        public String map(String element)
073        {
074            return symbolSource.expandSymbols(element);
075        }
076    };
077
078    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource,
079            ResourceChangeTracker resourceChangeTracker)
080    {
081        this.javascriptSupport = javascriptSupport;
082        this.symbolSource = symbolSource;
083        this.assetSource = assetSource;
084        this.resourceChangeTracker = resourceChangeTracker;
085        this.multipleClassLoaders = 
086                !Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.PRODUCTION_MODE)) &&
087                Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.MULTIPLE_CLASSLOADERS));
088    }
089
090    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
091    {
092        resourceChangeTracker.setCurrentClassName(model.getComponentClassName());
093        processClassAnnotationAtSetupRenderPhase(componentClass, model);
094
095        for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
096        {
097            decorateMethod(componentClass, model, m);
098        }
099        
100        resourceChangeTracker.clearCurrentClassName();
101    }
102
103    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
104    {
105        Import annotation = componentClass.getAnnotation(Import.class);
106
107        if (annotation != null)
108        {
109            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
110
111            decorateMethod(componentClass, model, setupRender, annotation);
112
113            model.addRenderPhase(SetupRender.class);
114        }
115    }
116
117    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
118    {
119        Import annotation = method.getAnnotation(Import.class);
120
121        decorateMethod(componentClass, model, method, annotation);
122    }
123
124    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
125                                Import annotation)
126    {
127        importStacks(method, annotation.stack());
128
129        importLibraries(componentClass, model, method, annotation.library());
130
131        importStylesheets(componentClass, model, method, annotation.stylesheet());
132
133        importModules(method, annotation.module());
134    }
135
136    private void importStacks(PlasticMethod method, String[] stacks)
137    {
138        if (stacks.length != 0)
139        {
140            method.addAdvice(createImportStackAdvice(stacks));
141        }
142    }
143
144    private MethodAdvice createImportStackAdvice(final String[] stacks)
145    {
146        return new MethodAdvice()
147        {
148            public void advise(MethodInvocation invocation)
149            {
150                for (String stack : stacks)
151                {
152                    javascriptSupport.importStack(stack);
153                }
154
155                invocation.proceed();
156            }
157        };
158    }
159
160    private void importModules(PlasticMethod method, String[] moduleNames)
161    {
162        if (moduleNames.length != 0)
163        {
164            method.addAdvice(createImportModulesAdvice(moduleNames));
165        }
166    }
167
168    class ModuleImport
169    {
170        final String moduleName, functionName;
171
172        ModuleImport(String moduleName, String functionName)
173        {
174            this.moduleName = moduleName;
175            this.functionName = functionName;
176        }
177
178        void apply(JavaScriptSupport javaScriptSupport)
179        {
180            Initialization initialization = javaScriptSupport.require(moduleName);
181
182            if (functionName != null)
183            {
184                initialization.invoke(functionName);
185            }
186        }
187    }
188
189    private MethodAdvice createImportModulesAdvice(final String[] moduleNames)
190    {
191        final List<ModuleImport> moduleImports = new ArrayList<ModuleImport>(moduleNames.length);
192
193        for (String name : moduleNames)
194        {
195            int colonx = name.indexOf(':');
196
197            String moduleName = colonx < 0 ? name : name.substring(0, colonx);
198            String functionName = colonx < 0 ? null : name.substring(colonx + 1);
199
200            moduleImports.add(new ModuleImport(moduleName, functionName));
201        }
202
203        return new MethodAdvice()
204        {
205            public void advise(MethodInvocation invocation)
206            {
207                for (ModuleImport moduleImport : moduleImports)
208                {
209                    moduleImport.apply(javascriptSupport);
210                }
211
212                invocation.proceed();
213            }
214        };
215    }
216
217    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
218                                 String[] paths)
219    {
220        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
221    }
222
223    private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
224                                   String[] paths)
225    {
226        decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet);
227    }
228
229    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
230                                             PlasticMethod method, String[] paths, Worker<Asset> operation)
231    {
232        if (paths.length == 0)
233        {
234            return;
235        }
236
237        String[] expandedPaths = expandPaths(paths);
238
239        PlasticField assetListField = componentClass.introduceField(Asset[].class,
240                "importedAssets_" + method.getDescription().methodName);
241
242        initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName());
243
244        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
245    }
246
247    private String[] expandPaths(String[] paths)
248    {
249        return F.flow(paths).map(expandSymbols).toArray(String.class);
250    }
251
252    private void initializeAssetsFromPaths(final String[] expandedPaths, PlasticField assetsField, final String libraryName)
253    {
254        assetsField.injectComputed(new ComputedValue<Asset[]>()
255        {
256            public Asset[] get(InstanceContext context)
257            {
258                ComponentResources resources = context.get(ComponentResources.class);
259
260                return convertPathsToAssetArray(resources, expandedPaths, libraryName);
261            }
262        });
263    }
264
265    private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths, final String libraryName)
266    {
267        return F.flow(assetPaths).map(new Mapper<String, Asset>()
268        {
269            public Asset map(String assetPath)
270            {
271                return assetSource.getComponentAsset(resources, assetPath, libraryName);
272            }
273        }).toArray(Asset.class);
274    }
275
276    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
277                                               final Worker<Asset> operation)
278    {
279        final String className = method.getPlasticClass().getClassName();
280        method.addAdvice(new MethodAdvice()
281        {
282            public void advise(MethodInvocation invocation)
283            {
284                invocation.proceed();
285
286                Asset[] assets = (Asset[]) access.get(invocation.getInstance());
287
288                if (multipleClassLoaders)
289                {
290                    resourceChangeTracker.setCurrentClassName(className);
291                }
292                
293                F.flow(assets).each(operation);
294                
295                if (multipleClassLoaders)
296                {
297                    resourceChangeTracker.clearCurrentClassName();
298                }
299            }
300        });
301    }
302}