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.assets;
014
015import org.apache.tapestry5.internal.services.ResourceStreamer;
016import org.apache.tapestry5.ioc.IOOperation;
017import org.apache.tapestry5.ioc.OperationTracker;
018import org.apache.tapestry5.services.LocalizationSetter;
019import org.apache.tapestry5.services.Request;
020import org.apache.tapestry5.services.Response;
021import org.apache.tapestry5.services.assets.AssetRequestHandler;
022import org.apache.tapestry5.services.assets.StreamableResource;
023import org.apache.tapestry5.services.javascript.JavaScriptStack;
024import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
025import org.slf4j.Logger;
026
027import java.io.IOException;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031public class StackAssetRequestHandler implements AssetRequestHandler
032{
033    private final Logger logger;
034
035    private final LocalizationSetter localizationSetter;
036
037    private final ResourceStreamer resourceStreamer;
038
039    // Group 1: checksum
040    // Group 2: locale
041    // Group 3: path
042    private final Pattern pathPattern = Pattern.compile("^(.+)/(.+)/(.+)\\.js$");
043
044    private final OperationTracker tracker;
045
046    private final JavaScriptStackAssembler javaScriptStackAssembler;
047
048    private final JavaScriptStackSource stackSource;
049
050    public StackAssetRequestHandler(Logger logger, LocalizationSetter localizationSetter,
051                                    ResourceStreamer resourceStreamer,
052                                    OperationTracker tracker,
053                                    JavaScriptStackAssembler javaScriptStackAssembler,
054                                    JavaScriptStackSource stackSource)
055    {
056        this.logger = logger;
057        this.localizationSetter = localizationSetter;
058        this.resourceStreamer = resourceStreamer;
059        this.tracker = tracker;
060        this.javaScriptStackAssembler = javaScriptStackAssembler;
061        this.stackSource = stackSource;
062    }
063
064    public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
065    {
066        return tracker.perform(String.format("Streaming JavaScript asset stack %s", extraPath),
067                new IOOperation<Boolean>()
068                {
069                    public Boolean perform() throws IOException
070                    {
071                        return streamStackResource(extraPath);
072                    }
073                });
074    }
075
076    private boolean streamStackResource(String extraPath) throws IOException
077    {
078        Matcher matcher = pathPattern.matcher(extraPath);
079
080        if (!matcher.matches())
081        {
082            logger.warn("Unable to parse '{}' as an asset stack path", extraPath);
083
084            return false;
085        }
086
087        String checksum = matcher.group(1);
088        String localeName = matcher.group(2);
089        final String stackName = matcher.group(3);
090
091        final boolean compressed = checksum.startsWith("z");
092
093        if (compressed)
094        {
095            checksum = checksum.substring(1);
096        }
097
098        final JavaScriptStack stack = stackSource.findStack(stackName);
099
100        if (stack == null)
101        {
102            logger.warn("JavaScript stack '{}' not found.", stackName);
103            return false;
104        }
105
106
107        // Yes, I have a big regret that the JavaScript stack stuff relies on this global, rather than
108        // having it passed around properly.
109
110        localizationSetter.setNonPersistentLocaleFromLocaleName(localeName);
111
112        StreamableResource resource =
113                tracker.perform(String.format("Assembling JavaScript asset stack '%s' (%s)",
114                                stackName, localeName),
115                        new IOOperation<StreamableResource>()
116                        {
117                            public StreamableResource perform() throws IOException
118                            {
119
120                                return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed,
121                                        stack.getJavaScriptAggregationStrategy());
122
123                            }
124                        });
125
126
127        if (resource == null)
128        {
129            return false;
130        }
131
132        return resourceStreamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
133    }
134}