View Javadoc
1   /*
2    *  This file is part of the Wayback archival access software
3    *   (http://archive-access.sourceforge.net/projects/wayback/).
4    *
5    *  Licensed to the Internet Archive (IA) by one or more individual 
6    *  contributors. 
7    *
8    *  The IA licenses this file to You under the Apache License, Version 2.0
9    *  (the "License"); you may not use this file except in compliance with
10   *  the License.  You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   *  Unless required by applicable law or agreed to in writing, software
15   *  distributed under the License is distributed on an "AS IS" BASIS,
16   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   *  See the License for the specific language governing permissions and
18   *  limitations under the License.
19   */
20  package org.archive.wayback.util.webapp;
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import javax.servlet.http.HttpServletRequest;
28  
29  /**
30   * 
31   * Class which allows semi-efficient translation of requests on a specific local
32   * port to a RequestHandler object.
33   * 
34   * Mapping within a port is based on the HTTP 1.1 Host field and the first
35   * segment of the requested PATH, that is, whatever is after the context where
36   * the wayback webapp was deployed, and before the first '/'.
37   * 
38   * @author brad
39   */
40  public class PortMapper {
41  	private static final Logger LOGGER = Logger.getLogger(
42  			PortMapper.class.getName());
43  	private int port = -1;
44  	private HashMap<String, RequestHandler> pathMap = null;
45  	/**
46  	 * @param port which this PortMapper is responsible for handling
47  	 */
48  	public PortMapper(int port) {
49  		this.port = port;
50  		pathMap = new HashMap<String, RequestHandler>();
51  	}
52  	private String hostPathToKey(String host, String firstPath) {
53  		StringBuilder sb = null;
54  		if((host == null) && (firstPath == null)) {
55  			return null;
56  		}
57  		sb = new StringBuilder();
58  		if(host != null) {
59  			sb.append(host);
60  		}
61  		sb.append("/");
62  		if(firstPath != null) {
63  			sb.append(firstPath);
64  		}
65  		return sb.toString();
66  	}
67  	/**
68  	 * Register the RequestHandler to accept requests for the given host and 
69  	 * port.
70  	 * @param host the HTTP 1.1 "Host" header which the RequestHandler should 
71  	 * match. If null, the RequestHandler matches any "Host" header value.
72  	 * @param firstPath the first path of the GET request path which the
73  	 * RequestHandler should match. This is the first path AFTER the name the 
74  	 * Wayback webapp is deployed under. If null, the RequestHandler matches
75  	 * all paths.
76  	 * @param requestHandler The RequestHandler to register.
77  	 */
78  	public void addRequestHandler(String host, String firstPath, 
79  			RequestHandler requestHandler) {
80  		String key = hostPathToKey(host, firstPath);
81  		if (pathMap.containsKey(key)) {
82  			LOGGER.warning("Duplicate port:path map for " + port +
83  					":" + key);
84  		} else {
85  			pathMap.put(key, requestHandler);
86  			if (LOGGER.isLoggable(Level.FINE)) {
87  				LOGGER.fine("Registered requestHandler(port/host/path) (" +
88  						port + "/" + host + "/" + firstPath + "): " + key);
89  			}
90  		}
91  	}
92  	
93  	private String requestToFirstPath(HttpServletRequest request) {
94  		String firstPath = null;
95  		String requestPath = request.getRequestURI();
96  		String contextPath = request.getContextPath();
97  		if((contextPath.length() > 0) && requestPath.startsWith(contextPath)) {
98  			requestPath = requestPath.substring(contextPath.length());
99  		}
100 		while(requestPath.startsWith("/")) {
101 			requestPath = requestPath.substring(1);
102 		}
103 		
104 		int slashIdx = requestPath.indexOf("/",1);
105 		if(slashIdx == -1) {
106 			firstPath = requestPath;
107 		} else {
108 			firstPath = requestPath.substring(0,slashIdx);
109 		}
110 		return firstPath;
111 	}
112 
113 	private String requestToHost(HttpServletRequest request) {
114 		return request.getServerName();
115 	}	
116 	
117 	/**
118 	 * Attempts to locate the most strictly matching RequestHandler mapped to
119 	 * this port. Strictly matching means the lowest number in the following 
120 	 * list:
121 	 * 
122 	 * 1) request handler matching both HOST and PATH
123 	 * 2) request handler matching host, registered with an empty PATH
124 	 * 3) request handler matching path, registered with an empty HOST
125 	 * 4) request handler registered with empty HOST and PATH
126 	 * 
127 	 * @param request the HttpServletRequest to be mapped to a RequestHandler
128 	 * @return the RequestHandlerContext, containing the RequestHandler and the
129 	 * prefix of the original request path that indicated the RequestHandler,
130 	 * or null, if no RequestHandler matches.
131 	 */
132 	public RequestHandlerContext getRequestHandlerContext(
133 			HttpServletRequest request) {
134 		String host = requestToHost(request);
135 		String contextPath = request.getContextPath();
136 		StringBuilder pathPrefix = new StringBuilder(contextPath);
137 //		if(contextPath.length() == 0) {
138 			pathPrefix.append("/");
139 //		}
140 		String firstPath = requestToFirstPath(request);
141 		String key = hostPathToKey(host,firstPath);
142 		RequestHandler handler = pathMap.get(key);
143 		if(handler != null) {
144 			LOGGER.fine("Mapped to RequestHandler with " + key);
145 			return new RequestHandlerContext(handler,
146 					pathPrefix.append(firstPath).toString());
147 		} else {
148 			LOGGER.finer("No mapping for " + key);			
149 		}
150 		key = hostPathToKey(host,null);
151 		handler = pathMap.get(key);
152 		if(handler != null) {
153 			LOGGER.fine("Mapped to RequestHandler with " + key);
154 			return new RequestHandlerContext(handler,contextPath);
155 		} else {
156 			LOGGER.finer("No mapping for " + key);			
157 		}
158 		key = hostPathToKey(null,firstPath);
159 		handler = pathMap.get(key);
160 		if(handler != null) {
161 			LOGGER.fine("Mapped to RequestHandler with " + key);
162 
163 			return new RequestHandlerContext(handler,
164 					pathPrefix.append(firstPath).toString());
165 		} else {
166 			LOGGER.finer("No mapping for " + key);			
167 		}
168 		handler = pathMap.get(null);
169 		if(handler != null) {
170 			LOGGER.fine("Mapped to RequestHandler with null");
171 			return new RequestHandlerContext(handler,contextPath);
172 		}
173 		// Nothing matching this port:host:path. Try to help get user back on
174 		// track. Note this won't help with hostname mismatches at the moment:
175 		ArrayList<String> paths = new ArrayList<String>();
176 		for(String tmp : pathMap.keySet()) {
177 			// slice off last chunk:
178 			int idx = tmp.lastIndexOf('/');
179 			if(idx != -1) {
180 				String path = tmp.substring(idx+1);
181 				paths.add(path);
182 			}
183 		}
184 		if(paths.size() > 0) {
185 			request.setAttribute("AccessPointNames", paths);
186 		}
187 		return null;
188 	}
189 }