001    // Copyright 2010, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.transform;
016    
017    import org.apache.tapestry5.Asset;
018    import org.apache.tapestry5.ComponentResources;
019    import org.apache.tapestry5.annotations.Import;
020    import org.apache.tapestry5.annotations.SetupRender;
021    import org.apache.tapestry5.func.F;
022    import org.apache.tapestry5.func.Mapper;
023    import org.apache.tapestry5.func.Worker;
024    import org.apache.tapestry5.ioc.Resource;
025    import org.apache.tapestry5.ioc.services.SymbolSource;
026    import org.apache.tapestry5.model.MutableComponentModel;
027    import org.apache.tapestry5.plastic.*;
028    import org.apache.tapestry5.services.AssetSource;
029    import org.apache.tapestry5.services.TransformConstants;
030    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
031    import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
032    import org.apache.tapestry5.services.transform.TransformationSupport;
033    
034    import java.util.Locale;
035    
036    /**
037     * Implements the {@link Import} annotation, both at the class and at the method level.
038     *
039     * @since 5.2.0
040     */
041    public class ImportWorker implements ComponentClassTransformWorker2
042    {
043        private final JavaScriptSupport javascriptSupport;
044    
045        private final SymbolSource symbolSource;
046    
047        private final AssetSource assetSource;
048    
049        private final Worker<Asset> importLibrary = new Worker<Asset>()
050        {
051            public void work(Asset asset)
052            {
053                javascriptSupport.importJavaScriptLibrary(asset);
054            }
055        };
056    
057        private final Worker<Asset> importStylesheet = new Worker<Asset>()
058        {
059            public void work(Asset asset)
060            {
061                javascriptSupport.importStylesheet(asset);
062            }
063        };
064    
065        private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
066        {
067            public String map(String element)
068            {
069                return symbolSource.expandSymbols(element);
070            }
071        };
072    
073        public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource)
074        {
075            this.javascriptSupport = javascriptSupport;
076            this.symbolSource = symbolSource;
077            this.assetSource = assetSource;
078        }
079    
080        public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
081        {
082            processClassAnnotationAtSetupRenderPhase(componentClass, model);
083    
084            for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
085            {
086                decorateMethod(componentClass, model, m);
087            }
088        }
089    
090        private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
091        {
092            Import annotation = componentClass.getAnnotation(Import.class);
093    
094            if (annotation != null)
095            {
096                PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
097    
098                decorateMethod(componentClass, model, setupRender, annotation);
099    
100                model.addRenderPhase(SetupRender.class);
101            }
102        }
103    
104        private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
105        {
106            Import annotation = method.getAnnotation(Import.class);
107    
108            decorateMethod(componentClass, model, method, annotation);
109        }
110    
111        private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
112                                    Import annotation)
113        {
114            importStacks(method, annotation.stack());
115    
116            importLibraries(componentClass, model, method, annotation.library());
117    
118            importStylesheets(componentClass, model, method, annotation.stylesheet());
119        }
120    
121        private void importStacks(PlasticMethod method, String[] stacks)
122        {
123            if (stacks.length != 0)
124            {
125                method.addAdvice(createImportStackAdvice(stacks));
126            }
127        }
128    
129        private MethodAdvice createImportStackAdvice(final String[] stacks)
130        {
131            return new MethodAdvice()
132            {
133                public void advise(MethodInvocation invocation)
134                {
135                    for (String stack : stacks)
136                    {
137                        javascriptSupport.importStack(stack);
138                    }
139    
140                    invocation.proceed();
141                }
142            };
143        }
144    
145        private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
146                                     String[] paths)
147        {
148            decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
149        }
150    
151        private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
152                                       String[] paths)
153        {
154            decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet);
155        }
156    
157        private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
158                                                 PlasticMethod method, String[] paths, Worker<Asset> operation)
159        {
160            if (paths.length == 0)
161                return;
162    
163            String[] expandedPaths = expandPaths(paths);
164    
165            PlasticField assetListField = componentClass.introduceField(Asset[].class,
166                    "importedAssets_" + method.getDescription().methodName);
167    
168            initializeAssetsFromPaths(model.getBaseResource(), expandedPaths, assetListField);
169    
170            addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
171        }
172    
173        private String[] expandPaths(String[] paths)
174        {
175            return F.flow(paths).map(expandSymbols).toArray(String.class);
176        }
177    
178        private void initializeAssetsFromPaths(final Resource baseResource,
179                                               final String[] expandedPaths, final PlasticField assetsField)
180        {
181            assetsField.injectComputed(new ComputedValue<Asset[]>()
182            {
183                public Asset[] get(InstanceContext context)
184                {
185                    ComponentResources resources = context.get(ComponentResources.class);
186    
187                    return convertPathsToAssetArray(baseResource, resources.getLocale(), expandedPaths);
188                }
189            });
190        }
191    
192        private Asset[] convertPathsToAssetArray(final Resource baseResource, final Locale locale, String[] assetPaths)
193        {
194            return F.flow(assetPaths).map(new Mapper<String, Asset>()
195            {
196                public Asset map(String assetPath)
197                {
198                    return assetSource.getAsset(baseResource, assetPath, locale);
199                }
200            }).toArray(Asset.class);
201        }
202    
203        private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
204                                                   final Worker<Asset> operation)
205        {
206            method.addAdvice(new MethodAdvice()
207            {
208                public void advise(MethodInvocation invocation)
209                {
210                    Asset[] assets = (Asset[]) access.get(invocation.getInstance());
211    
212                    F.flow(assets).each(operation);
213    
214                    invocation.proceed();
215                }
216            });
217        }
218    }