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}