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.plastic.PlasticUtils.FieldInfo;
028import org.apache.tapestry5.services.AssetSource;
029import org.apache.tapestry5.services.TransformConstants;
030import org.apache.tapestry5.services.javascript.Initialization;
031import org.apache.tapestry5.services.javascript.JavaScriptSupport;
032import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
033import org.apache.tapestry5.services.transform.TransformationSupport;
034
035import java.util.ArrayList;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Set;
039
040/**
041 * Implements the {@link Import} annotation, both at the class and at the method level.
042 *
043 * @since 5.2.0
044 */
045public class ImportWorker implements ComponentClassTransformWorker2
046{
047    
048    private static final String FIELD_PREFIX = "importedAssets_";
049    
050    private final JavaScriptSupport javascriptSupport;
051
052    private final SymbolSource symbolSource;
053
054    private final AssetSource assetSource;
055    
056    private final ResourceChangeTracker resourceChangeTracker;
057    
058    private final boolean multipleClassLoaders;
059    
060    private final PropertyValueProviderWorker propertyValueProviderWorker;
061
062    private final Worker<Asset> importLibrary = new Worker<Asset>()
063    {
064        public void work(Asset asset)
065        {
066            javascriptSupport.importJavaScriptLibrary(asset);
067        }
068    };
069
070    private final Worker<Asset> importStylesheet = new Worker<Asset>()
071    {
072        public void work(Asset asset)
073        {
074            javascriptSupport.importStylesheet(asset);
075        }
076    };
077
078    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
079    {
080        public String map(String element)
081        {
082            return symbolSource.expandSymbols(element);
083        }
084    };
085
086    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource,
087            ResourceChangeTracker resourceChangeTracker, PropertyValueProviderWorker propertyValueProviderWorker)
088    {
089        this.javascriptSupport = javascriptSupport;
090        this.symbolSource = symbolSource;
091        this.assetSource = assetSource;
092        this.resourceChangeTracker = resourceChangeTracker;
093        this.propertyValueProviderWorker = propertyValueProviderWorker;
094        this.multipleClassLoaders = 
095                !Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.PRODUCTION_MODE)) &&
096                Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.MULTIPLE_CLASSLOADERS));
097    }
098
099    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
100    {
101        resourceChangeTracker.setCurrentClassName(model.getComponentClassName());
102        
103        Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? new HashSet<>() : null;
104        processClassAnnotationAtSetupRenderPhase(componentClass, model, fieldInfos);
105
106        final List<PlasticMethod> methods = componentClass.getMethodsWithAnnotation(Import.class);
107        for (PlasticMethod m : methods)
108        {
109            decorateMethod(componentClass, model, m, fieldInfos);
110        }
111        
112        if (multipleClassLoaders && !fieldInfos.isEmpty())
113        {
114            propertyValueProviderWorker.add(componentClass, fieldInfos);
115        }
116        
117        resourceChangeTracker.clearCurrentClassName();
118    }
119
120    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model,
121            Set<FieldInfo> fieldInfos)
122    {
123        Import annotation = componentClass.getAnnotation(Import.class);
124
125        if (annotation != null)
126        {
127            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
128
129            decorateMethod(componentClass, model, setupRender, annotation, fieldInfos);
130
131            model.addRenderPhase(SetupRender.class);
132        }
133    }
134
135    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method, Set<FieldInfo> fieldInfos)
136    {
137        Import annotation = method.getAnnotation(Import.class);
138
139        decorateMethod(componentClass, model, method, annotation, fieldInfos);
140    }
141
142    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
143                                Import annotation, Set<FieldInfo> fieldInfos)
144    {
145        importStacks(method, annotation.stack());
146
147        importLibraries(componentClass, model, method, annotation.library(), fieldInfos);
148
149        importStylesheets(componentClass, model, method, annotation.stylesheet(), fieldInfos);
150
151        importModules(method, annotation.module());
152    }
153
154    private void importStacks(PlasticMethod method, String[] stacks)
155    {
156        if (stacks.length != 0)
157        {
158            method.addAdvice(createImportStackAdvice(stacks));
159        }
160    }
161
162    private MethodAdvice createImportStackAdvice(final String[] stacks)
163    {
164        return new MethodAdvice()
165        {
166            public void advise(MethodInvocation invocation)
167            {
168                for (String stack : stacks)
169                {
170                    javascriptSupport.importStack(stack);
171                }
172
173                invocation.proceed();
174            }
175        };
176    }
177
178    private void importModules(PlasticMethod method, String[] moduleNames)
179    {
180        if (moduleNames.length != 0)
181        {
182            method.addAdvice(createImportModulesAdvice(moduleNames));
183        }
184    }
185
186    class ModuleImport
187    {
188        final String moduleName, functionName;
189
190        ModuleImport(String moduleName, String functionName)
191        {
192            this.moduleName = moduleName;
193            this.functionName = functionName;
194        }
195
196        void apply(JavaScriptSupport javaScriptSupport)
197        {
198            Initialization initialization = javaScriptSupport.require(moduleName);
199
200            if (functionName != null)
201            {
202                initialization.invoke(functionName);
203            }
204        }
205    }
206
207    private MethodAdvice createImportModulesAdvice(final String[] moduleNames)
208    {
209        final List<ModuleImport> moduleImports = new ArrayList<ModuleImport>(moduleNames.length);
210
211        for (String name : moduleNames)
212        {
213            int colonx = name.indexOf(':');
214
215            String moduleName = colonx < 0 ? name : name.substring(0, colonx);
216            String functionName = colonx < 0 ? null : name.substring(colonx + 1);
217
218            moduleImports.add(new ModuleImport(moduleName, functionName));
219        }
220
221        return new MethodAdvice()
222        {
223            public void advise(MethodInvocation invocation)
224            {
225                for (ModuleImport moduleImport : moduleImports)
226                {
227                    moduleImport.apply(javascriptSupport);
228                }
229
230                invocation.proceed();
231            }
232        };
233    }
234
235    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
236                                 String[] paths, Set<FieldInfo> fieldInfos)
237    {
238        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary, fieldInfos);
239    }
240
241    private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
242                                   String[] paths, Set<FieldInfo> fieldInfos)
243    {
244        decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet, fieldInfos);
245    }
246
247    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
248                                             PlasticMethod method, String[] paths, Worker<Asset> operation,
249                                             Set<FieldInfo> fieldInfos)
250    {
251        if (paths.length == 0)
252        {
253            return;
254        }
255
256        String[] expandedPaths = expandPaths(paths);
257
258        final String fieldName = getFieldName(method);
259        PlasticField assetListField = componentClass.introduceField(Asset[].class, fieldName);
260        
261        if (multipleClassLoaders)
262        {
263            fieldInfos.add(PlasticUtils.toFieldInfo(assetListField));
264            assetListField.createAccessors(PropertyAccessType.READ_ONLY);
265        }
266
267        initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName());
268
269        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
270    }
271
272    private String getFieldName(PlasticMethod method) 
273    {
274        final StringBuilder builder = new StringBuilder(FIELD_PREFIX);
275        builder.append(method.getDescription().methodName);
276        if (multipleClassLoaders)
277        {
278            builder.append("_");
279            builder.append(method.getPlasticClass().getClassName().replace('.', '_'));
280        }
281        return builder.toString();
282    }
283
284    private String[] expandPaths(String[] paths)
285    {
286        return F.flow(paths).map(expandSymbols).toArray(String.class);
287    }
288
289    private void initializeAssetsFromPaths(final String[] expandedPaths, PlasticField assetsField, final String libraryName)
290    {
291        assetsField.injectComputed(new ComputedValue<Asset[]>()
292        {
293            public Asset[] get(InstanceContext context)
294            {
295                ComponentResources resources = context.get(ComponentResources.class);
296
297                return convertPathsToAssetArray(resources, expandedPaths, libraryName);
298            }
299        });
300    }
301
302    private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths, final String libraryName)
303    {
304        return F.flow(assetPaths).map(new Mapper<String, Asset>()
305        {
306            public Asset map(String assetPath)
307            {
308                return assetSource.getComponentAsset(resources, assetPath, libraryName);
309            }
310        }).toArray(Asset.class);
311    }
312
313    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
314                                               final Worker<Asset> operation)
315    {
316        final String className = method.getPlasticClass().getClassName();
317        final String fieldName = getFieldName(method);
318        method.addAdvice(new MethodAdvice()
319        {
320            public void advise(MethodInvocation invocation)
321            {
322                invocation.proceed();
323
324                final Object instance = invocation.getInstance();
325                Asset[] assets = (Asset[]) (multipleClassLoaders ?
326                        PropertyValueProvider.get(instance, fieldName) :
327                        access.get(instance));
328
329                if (multipleClassLoaders)
330                {
331                    resourceChangeTracker.setCurrentClassName(className);
332                }
333                
334                F.flow(assets).each(operation);
335                
336                if (multipleClassLoaders)
337                {
338                    resourceChangeTracker.clearCurrentClassName();
339                }
340            }
341        });
342    }
343    
344    public static interface ImportWorkerDataProvider 
345    {
346        Asset[] get(int fieldNameHashcode);
347    }
348    
349}