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