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