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}