    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}