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