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.annotations.Import; 018import org.apache.tapestry5.annotations.SetupRender; 019import org.apache.tapestry5.func.F; 020import org.apache.tapestry5.func.Mapper; 021import org.apache.tapestry5.func.Worker; 022import org.apache.tapestry5.ioc.services.SymbolSource; 023import org.apache.tapestry5.model.MutableComponentModel; 024import org.apache.tapestry5.plastic.*; 025import org.apache.tapestry5.services.AssetSource; 026import org.apache.tapestry5.services.TransformConstants; 027import org.apache.tapestry5.services.javascript.Initialization; 028import org.apache.tapestry5.services.javascript.JavaScriptSupport; 029import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 030import org.apache.tapestry5.services.transform.TransformationSupport; 031 032import java.util.ArrayList; 033import java.util.List; 034 035/** 036 * Implements the {@link Import} annotation, both at the class and at the method level. 037 * 038 * @since 5.2.0 039 */ 040public class ImportWorker implements ComponentClassTransformWorker2 041{ 042 private final JavaScriptSupport javascriptSupport; 043 044 private final SymbolSource symbolSource; 045 046 private final AssetSource assetSource; 047 048 private final Worker<Asset> importLibrary = new Worker<Asset>() 049 { 050 public void work(Asset asset) 051 { 052 javascriptSupport.importJavaScriptLibrary(asset); 053 } 054 }; 055 056 private final Worker<Asset> importStylesheet = new Worker<Asset>() 057 { 058 public void work(Asset asset) 059 { 060 javascriptSupport.importStylesheet(asset); 061 } 062 }; 063 064 private final Mapper<String, String> expandSymbols = new Mapper<String, String>() 065 { 066 public String map(String element) 067 { 068 return symbolSource.expandSymbols(element); 069 } 070 }; 071 072 public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource) 073 { 074 this.javascriptSupport = javascriptSupport; 075 this.symbolSource = symbolSource; 076 this.assetSource = assetSource; 077 } 078 079 public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model) 080 { 081 processClassAnnotationAtSetupRenderPhase(componentClass, model); 082 083 for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class)) 084 { 085 decorateMethod(componentClass, model, m); 086 } 087 } 088 089 private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model) 090 { 091 Import annotation = componentClass.getAnnotation(Import.class); 092 093 if (annotation != null) 094 { 095 PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION); 096 097 decorateMethod(componentClass, model, setupRender, annotation); 098 099 model.addRenderPhase(SetupRender.class); 100 } 101 } 102 103 private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method) 104 { 105 Import annotation = method.getAnnotation(Import.class); 106 107 decorateMethod(componentClass, model, method, annotation); 108 } 109 110 private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method, 111 Import annotation) 112 { 113 importStacks(method, annotation.stack()); 114 115 String libraryName = model.getLibraryName(); 116 117 importLibraries(componentClass, model, method, annotation.library()); 118 119 importStylesheets(componentClass, model, method, annotation.stylesheet()); 120 121 importModules(method, annotation.module()); 122 } 123 124 private void importStacks(PlasticMethod method, String[] stacks) 125 { 126 if (stacks.length != 0) 127 { 128 method.addAdvice(createImportStackAdvice(stacks)); 129 } 130 } 131 132 private MethodAdvice createImportStackAdvice(final String[] stacks) 133 { 134 return new MethodAdvice() 135 { 136 public void advise(MethodInvocation invocation) 137 { 138 for (String stack : stacks) 139 { 140 javascriptSupport.importStack(stack); 141 } 142 143 invocation.proceed(); 144 } 145 }; 146 } 147 148 private void importModules(PlasticMethod method, String[] moduleNames) 149 { 150 if (moduleNames.length != 0) 151 { 152 method.addAdvice(createImportModulesAdvice(moduleNames)); 153 } 154 } 155 156 class ModuleImport 157 { 158 final String moduleName, functionName; 159 160 ModuleImport(String moduleName, String functionName) 161 { 162 this.moduleName = moduleName; 163 this.functionName = functionName; 164 } 165 166 void apply(JavaScriptSupport javaScriptSupport) 167 { 168 Initialization initialization = javaScriptSupport.require(moduleName); 169 170 if (functionName != null) 171 { 172 initialization.invoke(functionName); 173 } 174 } 175 } 176 177 private MethodAdvice createImportModulesAdvice(final String[] moduleNames) 178 { 179 final List<ModuleImport> moduleImports = new ArrayList<ModuleImport>(moduleNames.length); 180 181 for (String name : moduleNames) 182 { 183 int colonx = name.indexOf(':'); 184 185 String moduleName = colonx < 0 ? name : name.substring(0, colonx); 186 String functionName = colonx < 0 ? null : name.substring(colonx + 1); 187 188 moduleImports.add(new ModuleImport(moduleName, functionName)); 189 } 190 191 return new MethodAdvice() 192 { 193 public void advise(MethodInvocation invocation) 194 { 195 for (ModuleImport moduleImport : moduleImports) 196 { 197 moduleImport.apply(javascriptSupport); 198 } 199 200 invocation.proceed(); 201 } 202 }; 203 } 204 205 private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method, 206 String[] paths) 207 { 208 decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary); 209 } 210 211 private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method, 212 String[] paths) 213 { 214 decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet); 215 } 216 217 private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model, 218 PlasticMethod method, String[] paths, Worker<Asset> operation) 219 { 220 if (paths.length == 0) 221 { 222 return; 223 } 224 225 String[] expandedPaths = expandPaths(paths); 226 227 PlasticField assetListField = componentClass.introduceField(Asset[].class, 228 "importedAssets_" + method.getDescription().methodName); 229 230 initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName()); 231 232 addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation); 233 } 234 235 private String[] expandPaths(String[] paths) 236 { 237 return F.flow(paths).map(expandSymbols).toArray(String.class); 238 } 239 240 private void initializeAssetsFromPaths(final String[] expandedPaths, PlasticField assetsField, final String libraryName) 241 { 242 assetsField.injectComputed(new ComputedValue<Asset[]>() 243 { 244 public Asset[] get(InstanceContext context) 245 { 246 ComponentResources resources = context.get(ComponentResources.class); 247 248 return convertPathsToAssetArray(resources, expandedPaths, libraryName); 249 } 250 }); 251 } 252 253 private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths, final String libraryName) 254 { 255 return F.flow(assetPaths).map(new Mapper<String, Asset>() 256 { 257 public Asset map(String assetPath) 258 { 259 return assetSource.getComponentAsset(resources, assetPath, libraryName); 260 } 261 }).toArray(Asset.class); 262 } 263 264 private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access, 265 final Worker<Asset> operation) 266 { 267 method.addAdvice(new MethodAdvice() 268 { 269 public void advise(MethodInvocation invocation) 270 { 271 invocation.proceed(); 272 273 Asset[] assets = (Asset[]) access.get(invocation.getInstance()); 274 275 F.flow(assets).each(operation); 276 } 277 }); 278 } 279}