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.services.javascript;
014
015import org.apache.tapestry5.Asset;
016import org.apache.tapestry5.SymbolConstants;
017import org.apache.tapestry5.func.F;
018import org.apache.tapestry5.func.Mapper;
019import org.apache.tapestry5.internal.services.RequestConstants;
020import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
021import org.apache.tapestry5.ioc.annotations.Symbol;
022import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
023import org.apache.tapestry5.ioc.services.ThreadLocale;
024import org.apache.tapestry5.ioc.util.ExceptionUtils;
025import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
026import org.apache.tapestry5.services.assets.AssetPathConstructor;
027import org.apache.tapestry5.services.assets.StreamableResource;
028import org.apache.tapestry5.services.javascript.JavaScriptStack;
029import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
030
031import java.io.IOException;
032import java.util.List;
033
034public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathConstructor
035{
036    private final ThreadLocale threadLocale;
037
038    private final AssetPathConstructor assetPathConstructor;
039
040    private final JavaScriptStackSource javascriptStackSource;
041
042    private final JavaScriptStackAssembler assembler;
043
044    private final ResponseCompressionAnalyzer compressionAnalyzer;
045
046    private final boolean combineScripts;
047
048    private final Mapper<Asset, String> toPath = new Mapper<Asset, String>()
049    {
050        public String map(Asset input)
051        {
052            return input.toClientURL();
053        }
054    };
055
056    public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
057                                              JavaScriptStackSource javascriptStackSource,
058                                              JavaScriptStackAssembler assembler,
059                                              ResponseCompressionAnalyzer compressionAnalyzer,
060                                              @Symbol(SymbolConstants.COMBINE_SCRIPTS)
061                                              boolean combineScripts)
062    {
063        this.threadLocale = threadLocale;
064        this.assetPathConstructor = assetPathConstructor;
065        this.javascriptStackSource = javascriptStackSource;
066        this.assembler = assembler;
067        this.compressionAnalyzer = compressionAnalyzer;
068        this.combineScripts = combineScripts;
069    }
070
071    public List<String> constructPathsForJavaScriptStack(String stackName)
072    {
073        JavaScriptStack stack = javascriptStackSource.getStack(stackName);
074
075        List<Asset> assets = stack.getJavaScriptLibraries();
076
077        // When combine scripts is true, we want to build the virtual aggregated JavaScript ... but only
078        // if there is more than one library asset, or any modules.
079        if (combineScripts && stack.getJavaScriptAggregationStrategy().enablesCombine())
080        {
081            boolean needsVirtual = (assets.size() > 1) || (!stack.getModules().isEmpty());
082
083            if (needsVirtual)
084            {
085                return combinedStackURL(stackName, stack);
086            }
087        }
088
089        return toPaths(assets);
090    }
091
092    private List<String> toPaths(List<Asset> assets)
093    {
094        assert assets != null;
095
096        return F.flow(assets).map(toPath).toList();
097    }
098
099    private List<String> combinedStackURL(String stackName, JavaScriptStack stack)
100    {
101        try
102        {
103            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported(),
104                    stack.getJavaScriptAggregationStrategy());
105
106            String path = String.format("%s/%s.js",
107                    threadLocale.getLocale(),
108                    stackName);
109
110            String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, assembled);
111
112            return CollectionFactory.newList(stackURL);
113        } catch (IOException ex)
114        {
115            throw new RuntimeException(String.format("Unable to construct path for '%s' JavaScript stack: %s",
116                    stackName,
117                    ExceptionUtils.toMessage(ex)), ex);
118        }
119    }
120
121}