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