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}