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}