    001// Copyright 2006-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;
    016
    017import org.apache.tapestry5.SymbolConstants;
    018import org.apache.tapestry5.ioc.annotations.Marker;
    019import org.apache.tapestry5.ioc.annotations.Symbol;
    020import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
    021import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    022import org.apache.tapestry5.services.*;
    023import org.apache.tapestry5.services.assets.AssetRequestHandler;
    024
    025import javax.servlet.http.HttpServletResponse;
    026import java.io.IOException;
    027import java.util.Collections;
    028import java.util.Comparator;
    029import java.util.List;
    030import java.util.Map;
    031
    032/**
    033 * Recognizes requests where the path begins with "/asset/" (actually, as defined by the
    034 * {@link SymbolConstants#ASSET_PATH_PREFIX} symbol), and delivers the content therein as a bytestream. Also
    035 * handles requests that are simply polling for a change to the file (including checking the ETag in the
    036 * request against {@linkplain org.apache.tapestry5.services.assets.StreamableResource#getChecksum() the asset's checksum}.
    037 *
    038 * @see ResourceStreamer
    039 * @see ClasspathAssetAliasManager
    040 * @see AssetRequestHandler
    041 */
    042@UsesMappedConfiguration(AssetRequestHandler.class)
    043@Marker(AssetRequestDispatcher.class)
    044public class AssetDispatcher implements Dispatcher
    045{
    046    /**
    047     * Keyed on extended path name, which includes the pathPrefix first and a trailing slash.
    048     */
    049    private final Map<String, AssetRequestHandler> pathToHandler = CollectionFactory.newMap();
    050
    051    /**
    052     * List of path prefixes in the pathToHandler, sorted be descending length.
    053     */
    054    private final List<String> assetPaths = CollectionFactory.newList();
    055
    056    private final String requestPathPrefix;
    057
    058    public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
    059
    060                           PathConstructor pathConstructor,
    061
    062                           @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
    063                           String assetPathPrefix)
    064    {
    065        requestPathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
    066
    067        for (String path : configuration.keySet())
    068        {
    069            AssetRequestHandler handler = configuration.get(path);
    070
    071            addPath(requestPathPrefix, path, handler);
    072        }
    073
    074        // Sort by descending length
    075
    076        Collections.sort(assetPaths, new Comparator<String>()
    077        {
    078            public int compare(String o1, String o2)
    079            {
    080                return o2.length() - o1.length();
    081            }
    082        });
    083    }
    084
    085    private void addPath(String prefix, String path, AssetRequestHandler handler)
    086    {
    087        String extendedPath = buildPath(prefix, path);
    088
    089        pathToHandler.put(extendedPath, handler);
    090
    091        assetPaths.add(extendedPath);
    092    }
    093
    094    private String buildPath(String prefix, String path)
    095    {
    096        // TODO: Not sure when path would be length 0!
    097        return path.length() == 0
    098                ? prefix
    099                : prefix + path + "/";
    100    }
    101
    102    public boolean dispatch(Request request, Response response) throws IOException
    103    {
    104        String path = request.getPath();
    105
    106        // Remember that the request path does not include the context path, so we can simply start
    107        // looking for the asset path prefix right off the bat.
    108
    109        if (!path.startsWith(requestPathPrefix))
    110        {
    111            return false;
    112        }
    113
    114        for (String extendedPath : assetPaths)
    115        {
    116            if (path.startsWith(extendedPath))
    117            {
    118                AssetRequestHandler handler = pathToHandler.get(extendedPath);
    119
    120                String extraPath = path.substring(extendedPath.length());
    121
    122                boolean handled = handler.handleAssetRequest(request, response, extraPath);
    123
    124                if (handled)
    125                {
    126                    return true;
    127                }
    128            }
    129        }
    130
    131        response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
    132
    133        return true;
    134    }
    135}