    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.internal.services.AssetDispatcher;
    016import org.apache.tapestry5.internal.services.ResourceStreamer;
    017import org.apache.tapestry5.ioc.IOOperation;
    018import org.apache.tapestry5.ioc.OperationTracker;
    019import org.apache.tapestry5.ioc.Resource;
    020import org.apache.tapestry5.services.Dispatcher;
    021import org.apache.tapestry5.services.PathConstructor;
    022import org.apache.tapestry5.services.Request;
    023import org.apache.tapestry5.services.Response;
    024import org.apache.tapestry5.services.javascript.ModuleManager;
    025
    026import javax.servlet.http.HttpServletResponse;
    027import java.io.IOException;
    028import java.util.EnumSet;
    029import java.util.Set;
    030
    031/**
    032 * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name,
    033 * and searches for the corresponding JavaScript module.  Unlike normal assets, modules do not include any kind of checksum
    034 * in the URL, and do not set a far-future expires header.
    035 *
    036 * @see ModuleManager
    037 */
    038public class ModuleDispatcher implements Dispatcher
    039{
    040    private final ModuleManager moduleManager;
    041
    042    private final ResourceStreamer streamer;
    043
    044    private final OperationTracker tracker;
    045
    046    private final String requestPrefix;
    047
    048    private final boolean compress;
    049
    050    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
    051
    052    public ModuleDispatcher(ModuleManager moduleManager,
    053                            ResourceStreamer streamer,
    054                            OperationTracker tracker,
    055                            PathConstructor pathConstructor,
    056                            String prefix,
    057                            boolean compress)
    058    {
    059        this.moduleManager = moduleManager;
    060        this.streamer = streamer;
    061        this.tracker = tracker;
    062        this.compress = compress;
    063
    064        requestPrefix = pathConstructor.constructDispatchPath(compress ? prefix + ".gz" : prefix) + "/";
    065    }
    066
    067    public boolean dispatch(Request request, Response response) throws IOException
    068    {
    069        String path = request.getPath();
    070
    071        if (path.startsWith(requestPrefix))
    072        {
    073            String extraPath = path.substring(requestPrefix.length());
    074
    075            if (! handleModuleRequest(extraPath))
    076            {
    077                response.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("No module for path '%s'.", extraPath));
    078            }
    079
    080            return true;
    081        }
    082
    083        return false;
    084
    085    }
    086
    087    private boolean handleModuleRequest(String extraPath) throws IOException
    088    {
    089        // Ensure request ends with '.js'.  That's the extension tacked on by RequireJS because it expects there
    090        // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to
    091        // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath
    092        // as a module.
    093
    094        int dotx = extraPath.lastIndexOf('.');
    095
    096        if (dotx < 0)
    097        {
    098            return false;
    099        }
    100
    101        if (!extraPath.substring(dotx + 1).equals("js"))
    102        {
    103            return false;
    104        }
    105
    106        final String moduleName = extraPath.substring(0, dotx);
    107
    108        return tracker.perform(String.format("Streaming %s %s",
    109                compress ? "compressed module" : "module",
    110                moduleName), new IOOperation<Boolean>()
    111        {
    112            public Boolean perform() throws IOException
    113            {
    114                Resource resource = moduleManager.findResourceForModule(moduleName);
    115
    116                if (resource != null)
    117                {
    118                    // Slightly hacky way of informing the streamer whether to supply the
    119                    // compressed or default stream. May need to iterate the API on this a bit.
    120                    return streamer.streamResource(resource, compress ? "z" : "", omitExpiration);
    121                }
    122
    123                return false;
    124            }
    125        });
    126    }
    127}