    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 static enum ImportType
    063    {
    064        LIBRARY, STYLESHEET
    065    }
    066
    067    private final Worker<Asset> importLibrary = new Worker<Asset>()
    068    {
    069        public void work(Asset asset)
    070        {
    071            javascriptSupport.importJavaScriptLibrary(asset);
    072        }
    073    };
    074
    075    private final Worker<Asset> importStylesheet = new Worker<Asset>()
    076    {
    077        public void work(Asset asset)
    078        {
    079            javascriptSupport.importStylesheet(asset);
    080        }
    081    };
    082
    083    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
    084    {
    085        public String map(String element)
    086        {
    087            return symbolSource.expandSymbols(element);
    088        }
    089    };
    090
    091    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource,
    092            ResourceChangeTracker resourceChangeTracker, PropertyValueProviderWorker propertyValueProviderWorker)
    093    {
    094        this.javascriptSupport = javascriptSupport;
    095        this.symbolSource = symbolSource;
    096        this.assetSource = assetSource;
    097        this.resourceChangeTracker = resourceChangeTracker;
    098        this.propertyValueProviderWorker = propertyValueProviderWorker;
    099        this.multipleClassLoaders =
    100                !Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.PRODUCTION_MODE)) &&
    101                Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.MULTIPLE_CLASSLOADERS));
    102    }
    103
    104    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
    105    {
    106        resourceChangeTracker.setCurrentClassName(model.getComponentClassName());
    107
    108        Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? new HashSet<>() : null;
    109        processClassAnnotationAtSetupRenderPhase(componentClass, model, fieldInfos);
    110
    111        final List<PlasticMethod> methods = componentClass.getMethodsWithAnnotation(Import.class);
    112        for (PlasticMethod m : methods)
    113        {
    114            decorateMethod(componentClass, model, m, fieldInfos);
    115        }
    116
    117        if (multipleClassLoaders && !fieldInfos.isEmpty())
    118        {
    119            propertyValueProviderWorker.add(componentClass, fieldInfos);
    120        }
    121
    122        resourceChangeTracker.clearCurrentClassName();
    123    }
    124
    125    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model,
    126            Set<FieldInfo> fieldInfos)
    127    {
    128        Import annotation = componentClass.getAnnotation(Import.class);
    129
    130        if (annotation != null)
    131        {
    132            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
    133
    134            decorateMethod(componentClass, model, setupRender, annotation, fieldInfos);
    135
    136            model.addRenderPhase(SetupRender.class);
    137        }
    138    }
    139
    140    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method, Set<FieldInfo> fieldInfos)
    141    {
    142        Import annotation = method.getAnnotation(Import.class);
    143
    144        decorateMethod(componentClass, model, method, annotation, fieldInfos);
    145    }
    146
    147    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
    148                                Import annotation, Set<FieldInfo> fieldInfos)
    149    {
    150        importStacks(method, annotation.stack());
    151
    152        importLibraries(componentClass, model, method, annotation.library(), fieldInfos);
    153
    154        importStylesheets(componentClass, model, method, annotation.stylesheet(), fieldInfos);
    155
    156        importModules(method, annotation.module());
    157    }
    158
    159    private void importStacks(PlasticMethod method, String[] stacks)
    160    {
    161        if (stacks.length != 0)
    162        {
    163            method.addAdvice(createImportStackAdvice(stacks));
    164        }
    165    }
    166
    167    private MethodAdvice createImportStackAdvice(final String[] stacks)
    168    {
    169        return new MethodAdvice()
    170        {
    171            public void advise(MethodInvocation invocation)
    172            {
    173                for (String stack : stacks)
    174                {
    175                    javascriptSupport.importStack(stack);
    176                }
    177
    178                invocation.proceed();
    179            }
    180        };
    181    }
    182
    183    private void importModules(PlasticMethod method, String[] moduleNames)
    184    {
    185        if (moduleNames.length != 0)
    186        {
    187            method.addAdvice(createImportModulesAdvice(moduleNames));
    188        }
    189    }
    190
    191    class ModuleImport
    192    {
    193        final String moduleName, functionName;
    194
    195        ModuleImport(String moduleName, String functionName)
    196        {
    197            this.moduleName = moduleName;
    198            this.functionName = functionName;
    199        }
    200
    201        void apply(JavaScriptSupport javaScriptSupport)
    202        {
    203            Initialization initialization = javaScriptSupport.require(moduleName);
    204
    205            if (functionName != null)
    206            {
    207                initialization.invoke(functionName);
    208            }
    209        }
    210    }
    211
    212    private MethodAdvice createImportModulesAdvice(final String[] moduleNames)
    213    {
    214        final List<ModuleImport> moduleImports = new ArrayList<ModuleImport>(moduleNames.length);
    215
    216        for (String name : moduleNames)
    217        {
    218            int colonx = name.indexOf(':');
    219
    220            String moduleName = colonx < 0 ? name : name.substring(0, colonx);
    221            String functionName = colonx < 0 ? null : name.substring(colonx + 1);
    222
    223            moduleImports.add(new ModuleImport(moduleName, functionName));
    224        }
    225
    226        return new MethodAdvice()
    227        {
    228            public void advise(MethodInvocation invocation)
    229            {
    230                for (ModuleImport moduleImport : moduleImports)
    231                {
    232                    moduleImport.apply(javascriptSupport);
    233                }
    234
    235                invocation.proceed();
    236            }
    237        };
    238    }
    239
    240    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
    241                                 String[] paths, Set<FieldInfo> fieldInfos)
    242    {
    243        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary, fieldInfos, ImportType.LIBRARY);
    244    }
    245
    246    private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
    247                                   String[] paths, Set<FieldInfo> fieldInfos)
    248    {
    249        decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet, fieldInfos, ImportType.STYLESHEET);
    250    }
    251
    252    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
    253                                             PlasticMethod method, String[] paths, Worker<Asset> operation,
    254                                             Set<FieldInfo> fieldInfos, ImportType importType)
    255    {
    256        if (paths.length == 0)
    257        {
    258            return;
    259        }
    260
    261        String[] expandedPaths = expandPaths(paths);
    262
    263        final String fieldName = getFieldName(method, importType);
    264        PlasticField assetListField = componentClass.introduceField(Asset[].class, fieldName);
    265
    266        if (multipleClassLoaders)
    267        {
    268            fieldInfos.add(PlasticUtils.toFieldInfo(assetListField));
    269            assetListField.createAccessors(PropertyAccessType.READ_ONLY);
    270        }
    271
    272        initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName());
    273
    274        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation, importType);
    275    }
    276
    277    private String getFieldName(PlasticMethod method, ImportType importType)
    278    {
    279        final StringBuilder builder = new StringBuilder(FIELD_PREFIX);
    280        builder.append(method.getDescription().methodName);
    281        if (multipleClassLoaders)
    282        {
    283            builder.append("_");
    284            builder.append(method.getPlasticClass().getClassName().replace('.', '_'));
    285            builder.append("_");
    286            builder.append(importType.name().toLowerCase());
    287        }
    288        return builder.toString();
    289    }
    290
    291    private String[] expandPaths(String[] paths)
    292    {
    293        return F.flow(paths).map(expandSymbols).toArray(String.class);
    294    }
    295
    296    private void initializeAssetsFromPaths(final String[] expandedPaths, PlasticField assetsField, final String libraryName)
    297    {
    298        assetsField.injectComputed(new ComputedValue<Asset[]>()
    299        {
    300            public Asset[] get(InstanceContext context)
    301            {
    302                ComponentResources resources = context.get(ComponentResources.class);
    303
    304                return convertPathsToAssetArray(resources, expandedPaths, libraryName);
    305            }
    306        });
    307    }
    308
    309    private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths, final String libraryName)
    310    {
    311        return F.flow(assetPaths).map(new Mapper<String, Asset>()
    312        {
    313            public Asset map(String assetPath)
    314            {
    315                return assetSource.getComponentAsset(resources, assetPath, libraryName);
    316            }
    317        }).toArray(Asset.class);
    318    }
    319
    320    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
    321                                               final Worker<Asset> operation, ImportType importType)
    322    {
    323        final String className = method.getPlasticClass().getClassName();
    324        final String fieldName = getFieldName(method, importType);
    325        method.addAdvice(new MethodAdvice()
    326        {
    327            public void advise(MethodInvocation invocation)
    328            {
    329                invocation.proceed();
    330
    331                final Object instance = invocation.getInstance();
    332                Asset[] assets = (Asset[]) (multipleClassLoaders ?
    333                        PropertyValueProvider.get(instance, fieldName) :
    334                        access.get(instance));
    335
    336                if (multipleClassLoaders)
    337                {
    338                    resourceChangeTracker.setCurrentClassName(className);
    339                }
    340
    341                F.flow(assets).each(operation);
    342
    343                if (multipleClassLoaders)
    344                {
    345                    resourceChangeTracker.clearCurrentClassName();
    346                }
    347            }
    348        });
    349    }
    350
    351}