    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.services.javascript;
    014
    015import org.apache.tapestry5.Asset;
    016import org.apache.tapestry5.func.F;
    017import org.apache.tapestry5.func.Flow;
    018import org.apache.tapestry5.func.Mapper;
    019import org.apache.tapestry5.func.Predicate;
    020import org.apache.tapestry5.ioc.ServiceBinder;
    021import org.apache.tapestry5.ioc.ServiceBindingOptions;
    022import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
    023import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    024import org.apache.tapestry5.services.AssetSource;
    025
    026import java.util.List;
    027
    028/**
    029 * An extensible implementation of {@link JavaScriptStack} that can be used as the implementation of a service.
    030 * The contributions to the service are used to supply the libraries, stylesheets, and initialization for a
    031 * JavaScriptStack, allowing the stack to be more dynamically configured. In practice, one will use
    032 * {@link ServiceBinder#bind(Class, Class)} and {@link ServiceBindingOptions#withMarker(Class...)} to construct the
    033 * service, then use the marker annotation to inject the service when contributing the service into to the
    034 * {@link JavaScriptStackSource}.
    035 *
    036 * A limitation of this implementation is that the contributed assets are not localized at all.
    037 *
    038 * @see StackExtension
    039 * @since 5.3
    040 */
    041@UsesOrderedConfiguration(StackExtension.class)
    042public class ExtensibleJavaScriptStack implements JavaScriptStack
    043{
    044    private final AssetSource assetSource;
    045
    046    private final List<Asset> libraries;
    047
    048    private final List<StylesheetLink> stylesheets;
    049
    050    private final List<String> stacks;
    051
    052    private final List<String> modules;
    053
    054    private final String initialization;
    055
    056    private final JavaScriptAggregationStrategy strategy;
    057
    058    private final Predicate<StackExtension> by(final StackExtensionType type)
    059    {
    060        return new Predicate<StackExtension>()
    061        {
    062            public boolean accept(StackExtension element)
    063            {
    064                return element.type == type;
    065            }
    066        };
    067    }
    068
    069    private final Mapper<StackExtension, String> extractValue = new Mapper<StackExtension, String>()
    070    {
    071        public String map(StackExtension element)
    072        {
    073            return element.value;
    074        }
    075
    076        ;
    077    };
    078
    079    private final Mapper<String, Asset> stringToAsset = new Mapper<String, Asset>()
    080    {
    081        public Asset map(String value)
    082        {
    083            return assetSource.getExpandedAsset(value);
    084        }
    085
    086        ;
    087    };
    088
    089    private final Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>()
    090    {
    091        public StylesheetLink map(Asset asset)
    092        {
    093            return new StylesheetLink(asset);
    094        }
    095
    096        ;
    097    };
    098
    099    private final Mapper<String, JavaScriptAggregationStrategy> stringToStrategy = new Mapper<String, JavaScriptAggregationStrategy>()
    100    {
    101        @Override
    102        public JavaScriptAggregationStrategy map(String name)
    103        {
    104            return JavaScriptAggregationStrategy.valueOf(name);
    105        }
    106    };
    107
    108    public ExtensibleJavaScriptStack(AssetSource assetSource, List<StackExtension> configuration)
    109    {
    110        this.assetSource = assetSource;
    111
    112        Flow<StackExtension> extensions = F.flow(configuration);
    113
    114        libraries = extensions.filter(by(StackExtensionType.LIBRARY)).map(extractValue).map(stringToAsset).toList();
    115
    116        stacks = extensions.filter(by(StackExtensionType.STACK)).map(extractValue).toList();
    117
    118        modules = extensions.filter(by(StackExtensionType.MODULE)).map(extractValue).toList();
    119
    120        stylesheets = extensions.filter(by(StackExtensionType.STYLESHEET)).map(extractValue).map(stringToAsset)
    121                .map(assetToStylesheetLink).toList();
    122
    123        List<String> initializations = extensions.filter(by(StackExtensionType.INITIALIZATION)).map(extractValue)
    124                .toList();
    125
    126        initialization = initializations.isEmpty() ? null : InternalUtils.join(initializations, "\n");
    127
    128        strategy = toStrategy(extensions);
    129    }
    130
    131    private JavaScriptAggregationStrategy toStrategy(Flow<StackExtension> extensions)
    132    {
    133        List<JavaScriptAggregationStrategy> values = extensions.filter(by(StackExtensionType.AGGREGATION_STRATEGY)).map(extractValue).map(stringToStrategy).toList();
    134
    135        switch (values.size())
    136        {
    137            case 0:
    138                return JavaScriptAggregationStrategy.COMBINE_AND_MINIMIZE;
    139
    140            case 1:
    141
    142                return values.get(0);
    143
    144            default:
    145                throw new IllegalStateException(String.format("Could not handle %d contribution(s) of JavaScriptAggregation Strategy. There should be at most one.",
    146                        values.size()));
    147        }
    148    }
    149
    150    public List<String> getStacks()
    151    {
    152        return stacks;
    153    }
    154
    155    public List<Asset> getJavaScriptLibraries()
    156    {
    157        return libraries;
    158    }
    159
    160    public List<StylesheetLink> getStylesheets()
    161    {
    162        return stylesheets;
    163    }
    164
    165    public String getInitialization()
    166    {
    167        return initialization;
    168    }
    169
    170    public List<String> getModules()
    171    {
    172        return modules;
    173    }
    174
    175    @Override
    176    public JavaScriptAggregationStrategy getJavaScriptAggregationStrategy()
    177    {
    178        return strategy;
    179    }
    180}