001// Copyright 2010-2013 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.services.javascript;
016
017import org.apache.tapestry5.Asset;
018import org.apache.tapestry5.SymbolConstants;
019import org.apache.tapestry5.func.F;
020import org.apache.tapestry5.func.Mapper;
021import org.apache.tapestry5.internal.services.RequestConstants;
022import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
023import org.apache.tapestry5.ioc.annotations.Symbol;
024import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025import org.apache.tapestry5.ioc.services.ThreadLocale;
026import org.apache.tapestry5.ioc.util.ExceptionUtils;
027import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
028import org.apache.tapestry5.services.assets.AssetPathConstructor;
029import org.apache.tapestry5.services.assets.StreamableResource;
030import org.apache.tapestry5.services.javascript.JavaScriptStack;
031import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
032
033import java.io.IOException;
034import java.util.List;
035
036public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathConstructor
037{
038    private final ThreadLocale threadLocale;
039
040    private final AssetPathConstructor assetPathConstructor;
041
042    private final JavaScriptStackSource javascriptStackSource;
043
044    private final JavaScriptStackAssembler assembler;
045
046    private final ResponseCompressionAnalyzer compressionAnalyzer;
047
048    private final boolean combineScripts;
049
050    private final Mapper<Asset, String> toPath = new Mapper<Asset, String>()
051    {
052        public String map(Asset input)
053        {
054            return input.toClientURL();
055        }
056    };
057
058    public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
059                                              JavaScriptStackSource javascriptStackSource,
060                                              JavaScriptStackAssembler assembler,
061                                              ResponseCompressionAnalyzer compressionAnalyzer,
062                                              @Symbol(SymbolConstants.COMBINE_SCRIPTS)
063                                              boolean combineScripts)
064    {
065        this.threadLocale = threadLocale;
066        this.assetPathConstructor = assetPathConstructor;
067        this.javascriptStackSource = javascriptStackSource;
068        this.assembler = assembler;
069        this.compressionAnalyzer = compressionAnalyzer;
070        this.combineScripts = combineScripts;
071    }
072
073    public List<String> constructPathsForJavaScriptStack(String stackName)
074    {
075        JavaScriptStack stack = javascriptStackSource.getStack(stackName);
076
077        List<Asset> assets = stack.getJavaScriptLibraries();
078
079        // When combine scripts is true, we want to build the virtual aggregated JavaScript ... but only
080        // if there is more than one library asset, or any modules.
081        if (combineScripts)
082        {
083            boolean needsVirtual = (assets.size() > 1) || (!stack.getModules().isEmpty());
084
085            if (needsVirtual)
086            {
087                return combinedStackURL(stackName);
088            }
089        }
090
091        return toPaths(assets);
092    }
093
094    private List<String> toPaths(List<Asset> assets)
095    {
096        assert assets != null;
097
098        return F.flow(assets).map(toPath).toList();
099    }
100
101    private List<String> combinedStackURL(String stackName)
102    {
103        try
104        {
105            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported());
106
107            String path = String.format("%s/%s.js",
108                    threadLocale.getLocale(),
109                    stackName);
110
111            String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, assembled);
112
113            return CollectionFactory.newList(stackURL);
114        } catch (IOException ex)
115        {
116            throw new RuntimeException(String.format("Unable to construct path for '%s' JavaScript stack: %s",
117                    stackName,
118                    ExceptionUtils.toMessage(ex)), ex);
119        }
120    }
121
122}