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 * <p/>
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}