From: <bra...@us...> - 2010-04-27 22:12:38
|
Revision: 3074 http://archive-access.svn.sourceforge.net/archive-access/?rev=3074&view=rev Author: bradtofel Date: 2010-04-27 22:12:31 +0000 (Tue, 27 Apr 2010) Log Message: ----------- REFACTOR: moved all the glue functionality, which ties Wayback to a ServletContext, into this package, including: * Top-level RequestFilter Filter implementation * Spring XML parsing * setting up mapping between RequestHandler (formerly ServletRequestContext) and the RequestMapper (see below) * RequestMapper functionality ** Now provides for more flexible mapping of RequestHandler to host, port, and path ** includes registration of notification of ServletContext destruction ** includes ability to register a global RequestHandler to possibly handle incoming requests before normal mapping, and after normal mapping, should no RequestHandler match the incoming request Added Paths: ----------- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/AbstractRequestHandler.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/BeanNameRegistrar.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/PortMapper.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestFilter.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandler.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandlerContext.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestMapper.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ShutdownListener.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/SpringReader.java trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/StaticFileRequestHandler.java Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/AbstractRequestHandler.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/AbstractRequestHandler.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/AbstractRequestHandler.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,47 @@ +/** + * + */ +package org.archive.wayback.util.webapp; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + + +/** + * Abstract RequestHandler implementation which performs the minimal behavior + * for self registration with a RequestMapper, requiring subclasses to implement + * only handleRequest(). + * + * @author brad + * + */ +public abstract class AbstractRequestHandler implements RequestHandler { + private String beanName = null; + private ServletContext servletContext = null; + + public void setBeanName(final String beanName) { + this.beanName = beanName; + } + public String getBeanName() { + return beanName; + } + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + public ServletContext getServletContext() { + return servletContext; + } + + public void registerPortListener(RequestMapper requestMapper) { + BeanNameRegistrar.registerHandler(this, requestMapper); + } + + public String translateRequestPath(HttpServletRequest httpRequest) { + return RequestMapper.getRequestContextPath(httpRequest); + } + + public String translateRequestPathQuery(HttpServletRequest httpRequest) { + return RequestMapper.getRequestContextPathQuery(httpRequest); + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/AbstractRequestHandler.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/BeanNameRegistrar.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/BeanNameRegistrar.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/BeanNameRegistrar.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,150 @@ +/** + * + */ +package org.archive.wayback.util.webapp; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; + +/** + * Helper static methods to implement registration of a RequestHandler with a + * RequestMapper, based on the beanName() method. + * + * @author brad + * + */ +public class BeanNameRegistrar { + + private static final Logger LOGGER = Logger.getLogger( + BeanNameRegistrar.class.getName()); + + private static final String PORT_PATTERN_STRING = + "([0-9]+):?"; + private static final String PORT_PATH_PATTERN_STRING = + "([0-9]+):([0-9a-zA-Z_.-]+)"; + private static final String HOST_PORT_PATTERN_STRING = + "([0-9a-z_.-]+):([0-9]+):?"; + private static final String HOST_PORT_PATH_PATTERN_STRING = + "([0-9a-z_.-]+):([0-9]+):([0-9a-zA-Z_.-]+)"; + + private static final Pattern PORT_PATTERN = + Pattern.compile(PORT_PATTERN_STRING); + private static final Pattern PORT_PATH_PATTERN = + Pattern.compile(PORT_PATH_PATTERN_STRING); + private static final Pattern HOST_PORT_PATTERN = + Pattern.compile(HOST_PORT_PATTERN_STRING); + private static final Pattern HOST_PORT_PATH_PATTERN = + Pattern.compile(HOST_PORT_PATH_PATTERN_STRING); + + /* + * matches: + * 8080 + * 8080: + */ + private static boolean registerPort(String name, RequestHandler handler, + RequestMapper mapper) { + Matcher m = null; + m = PORT_PATTERN.matcher(name); + if(m.matches()) { + int port = Integer.parseInt(m.group(1)); + mapper.addRequestHandler(port, null, null, handler); + return true; + } + return false; + } + /* + * matches: + * 8080:blue + * 8080:fish + */ + private static boolean registerPortPath(String name, RequestHandler handler, + RequestMapper mapper) { + Matcher m = null; + m = PORT_PATH_PATTERN.matcher(name); + if(m.matches()) { + int port = Integer.parseInt(m.group(1)); + mapper.addRequestHandler(port, null, m.group(2), handler); + return true; + } + return false; + } + /* + * matches: + * localhost.archive.org:8080 + * static.localhost.archive.org:8080 + */ + private static boolean registerHostPort(String name, RequestHandler handler, + RequestMapper mapper) { + Matcher m = null; + m = HOST_PORT_PATTERN.matcher(name); + if(m.matches()) { + int port = Integer.parseInt(m.group(2)); + mapper.addRequestHandler(port, m.group(1), null, handler); + return true; + } + return false; + } + /* + * matches: + * localhost.archive.org:8080:two + * static.localhost.archive.org:8080:fish + */ + private static boolean registerHostPortPath(String name, + RequestHandler handler, RequestMapper mapper) { + Matcher m = null; + m = HOST_PORT_PATH_PATTERN.matcher(name); + if(m.matches()) { + int port = Integer.parseInt(m.group(2)); + mapper.addRequestHandler(port, m.group(1), m.group(3), handler); + return true; + } + return false; + } + + /** + * Extract the RequestHandler objects beanName, parse it, and register the + * RequestHandler with the RequestMapper according to the beanNames + * semantics. + * @param handler The RequestHandler to register + * @param mapper the RequestMapper where the RequestHandler should be + * registered. + */ + public static void registerHandler(RequestHandler handler, + RequestMapper mapper) { + String name = handler.getBeanName(); + if(name != null) { + if(name.equals(RequestMapper.GLOBAL_PRE_REQUEST_HANDLER)) { + + mapper.addGlobalPreRequestHandler(handler); + + } else if(name.equals(RequestMapper.GLOBAL_POST_REQUEST_HANDLER)) { + + mapper.addGlobalPostRequestHandler(handler); + + } else { + try { + + boolean registered = + registerPort(name, handler, mapper) || + registerPortPath(name, handler, mapper) || + registerHostPort(name, handler, mapper) || + registerHostPortPath(name, handler, mapper); + + if(!registered) { + LOGGER.error("Unable to register (" + name + ")"); + } + } catch(NumberFormatException e) { + LOGGER.error("FAILED parseInt(" + name + ")"); + } + } + } else { + LOGGER.info("Unable to register RequestHandler - null beanName"); + } + if(handler instanceof ShutdownListener) { + ShutdownListener s = (ShutdownListener) handler; + mapper.addShutdownListener(s); + } + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/BeanNameRegistrar.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/PortMapper.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/PortMapper.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/PortMapper.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,164 @@ +/* PortMapper + * + * $Id$: + * + * Created on Mar 23, 2010. + * + * Copyright (C) 2006 Internet Archive. + * + * This file is part of Wayback. + * + * Wayback is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * any later version. + * + * Wayback is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with Wayback; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.archive.wayback.util.webapp; + +import java.util.HashMap; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; + +/** + * + * Class which allows semi-efficient translation of requests on a specific local + * port to a RequestHandler object. + * + * Mapping within a port is based on the HTTP 1.1 Host field and the first + * segment of the requested PATH, that is, whatever is after the context where + * the wayback webapp was deployed, and before the first '/'. + * + * @author brad + */ +public class PortMapper { + private static final Logger LOGGER = Logger.getLogger( + PortMapper.class.getName()); + private int port = -1; + private HashMap<String, RequestHandler> pathMap = null; + /** + * @param port which this PortMapper is responsible for handling + */ + public PortMapper(int port) { + this.port = port; + pathMap = new HashMap<String, RequestHandler>(); + } + private String hostPathToKey(String host, String firstPath) { + StringBuilder sb = null; + if((host == null) && (firstPath == null)) { + return null; + } + sb = new StringBuilder(); + if(host != null) { + sb.append(host); + } + sb.append("/"); + if(firstPath != null) { + sb.append(firstPath); + } + return sb.toString(); + } + /** + * Register the RequestHandler to accept requests for the given host and + * port. + * @param host the HTTP 1.1 "Host" header which the RequestHandler should + * match. If null, the RequestHandler matches any "Host" header value. + * @param firstPath the first path of the GET request path which the + * RequestHandler should match. This is the first path AFTER the name the + * Wayback webapp is deployed under. If null, the RequestHandler matches + * all paths. + * @param requestHandler The RequestHandler to register. + */ + public void addRequestHandler(String host, String firstPath, + RequestHandler requestHandler) { + String key = hostPathToKey(host,firstPath); + if(pathMap.containsKey(key)) { + LOGGER.warn("Duplicate port:path map for " + port + + ":" + key); + } else { + LOGGER.info("Registered requestHandler(port/host/path) (" + + port + "/" + host + "/" + firstPath + "): " + key); + pathMap.put(key,requestHandler); + } + } + + private String requestToFirstPath(HttpServletRequest request) { + String firstPath = null; + String requestPath = request.getRequestURI(); + String contextPath = request.getContextPath(); + if((contextPath.length() > 0) && requestPath.startsWith(contextPath)) { + requestPath = requestPath.substring(contextPath.length()); + } + while(requestPath.startsWith("/")) { + requestPath = requestPath.substring(1); + } + + int slashIdx = requestPath.indexOf("/",1); + if(slashIdx == -1) { + firstPath = requestPath; + } else { + firstPath = requestPath.substring(0,slashIdx); + } + return firstPath; + } + + private String requestToHost(HttpServletRequest request) { + return request.getServerName(); + } + + /** + * Attempts to locate the most strictly matching RequestHandler mapped to + * this port. Strictly matching means the lowest number in the following + * list: + * + * 1) request handler matching both HOST and PATH + * 2) request handler matching host, registered with an empty PATH + * 3) request handler matching path, registered with an empty HOST + * 4) request handler registered with empty HOST and PATH + * + * @param request the HttpServletRequest to be mapped to a RequestHandler + * @return the RequestHandlerContext, containing the RequestHandler and the + * prefix of the original request path that indicated the RequestHandler, + * or null, if no RequestHandler matches. + */ + public RequestHandlerContext getRequestHandlerContext( + HttpServletRequest request) { + String host = requestToHost(request); + String contextPath = request.getContextPath(); + StringBuilder pathPrefix = new StringBuilder(contextPath); + if(contextPath.length() == 0) { + pathPrefix.append("/"); + } + String firstPath = requestToFirstPath(request); + RequestHandler handler = pathMap.get(hostPathToKey(host,firstPath)); + if(handler != null) { + return new RequestHandlerContext(handler, + pathPrefix.append(firstPath).toString()); + } + handler = pathMap.get(hostPathToKey(host,null)); + if(handler != null) { + return new RequestHandlerContext(handler,contextPath); + } + handler = pathMap.get(hostPathToKey(null,firstPath)); + if(handler != null) { + return new RequestHandlerContext(handler, + pathPrefix.append(firstPath).toString()); + } + handler = pathMap.get(null); + if(handler != null) { + return new RequestHandlerContext(handler,contextPath); + } + return null; + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/PortMapper.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestFilter.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestFilter.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestFilter.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,91 @@ +/* RequestHandler + * + * $Id$ + * + * Created on 4:24:06 PM Apr 20, 2007. + * + * Copyright (C) 2007 Internet Archive. + * + * This file is part of wayback-webapp. + * + * wayback-webapp is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * any later version. + * + * wayback-webapp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with wayback-webapp; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.archive.wayback.util.webapp; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; + +/** + * Top-Level integration point between a series of RequestHandler mappings and + * a generic ServletContext. This filter is assumed to be responsible for + * matching ALL requests received by the webapp ("*") and uses a RequestMapper + * to delegate incoming HttpServletRequests to the appropriate RequestHandler, + * via the doFilter() method. + * + * @author brad + */ +public class RequestFilter implements Filter { + private static final Logger LOGGER = Logger.getLogger(RequestFilter.class + .getName()); + private RequestMapper mapper = null; + private final static String CONFIG_PATH = "config-path"; + + public void init(FilterConfig config) throws ServletException { + ServletContext servletContext = config.getServletContext(); + + String configPath = servletContext.getInitParameter(CONFIG_PATH); + if(configPath == null) { + throw new ServletException("Missing " + CONFIG_PATH + + " parameter"); + } + String resolvedPath = servletContext.getRealPath(configPath); + + LOGGER.info("Initializing Spring config at: " + resolvedPath); + mapper = SpringReader.readSpringConfig(resolvedPath,servletContext); + LOGGER.info("Initialized Spring config at: " + resolvedPath); + } + + public void destroy() { + LOGGER.info("Shutdown starting."); + mapper.shutdown(); + LOGGER.info("Shutdown complete."); + } + + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + boolean handled = false; + + if(request instanceof HttpServletRequest) { + if(response instanceof HttpServletResponse) { + handled = mapper.handleRequest((HttpServletRequest) request, + (HttpServletResponse) response); + } + } + if(!handled) { + chain.doFilter(request,response); + } + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestFilter.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandler.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandler.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandler.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,85 @@ +/** + * + */ +package org.archive.wayback.util.webapp; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.BeanNameAware; + +/** + * A generic handler of HttpServletRequests. very similar to an HttpServlet, but + * the handleRequest() method returns a boolean indicating if the RequestHandler + * returned data to the user. + * + * This interface further defines methods to facilitate automatic registration + * when loaded as a Spring configuration, and maintains a reference to the + * ServletContext under which it accepts incoming requests. + * + * @author brad + * + */ +public interface RequestHandler extends BeanNameAware { + + /** + * Possibly handle an incoming HttpServletRequest, much like a normal + * HttpServlet, but includes a return value. + * @param httpRequest the incoming HttpServletRequest + * @param httpResponse the HttpServletResponse to return data to the client. + * @return true if the RequestHandler returned a response to the client, + * false otherwise + * @throws ServletException for usual reasons. + * @throws IOException for usual reasons. + */ + public boolean handleRequest(HttpServletRequest httpRequest, + HttpServletResponse httpResponse) + throws ServletException, IOException; + + /** + * @return the "name" property of the bean from the SpringConfiguration + */ + public String getBeanName(); + + /** + * Called before registerPortListener(), to enable the registration process + * and subsequent handleRequest() calls to access the ServletContext, via + * the getServletContext() method. + * @param servletContext the ServletContext where the RequestHandler is + * registered. + */ + public void setServletContext(ServletContext servletContext); + + /** + * @return the ServletContext where the RequestHandler is registered. + */ + public ServletContext getServletContext(); + + /** + * Called at webapp context initialization, to allow the RequestHandler to + * register itself with the RequestMapper, which will delegate request + * handling to the appropriate RequestHandler. + * @param requestMapper the RequestMapper on which this RequestHandler + * should register itself, including to register for notification of context + * shutdown. + */ + public void registerPortListener(RequestMapper requestMapper); + + /** + * @param httpRequest the HttpServletRequest being handled + * @return the portion of the original incoming request that falls within + * this RequestHandler, not including any query information + */ + public String translateRequestPath(HttpServletRequest httpRequest); + + /** + * @param httpRequest the HttpServletRequest being handled + * @return the portion of the original incoming request that falls within + * this RequestHandler, including any query information + */ + public String translateRequestPathQuery(HttpServletRequest httpRequest); +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandler.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandlerContext.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandlerContext.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandlerContext.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,66 @@ +/* RequestHandlerContext + * + * $Id$: + * + * Created on Apr 26, 2010. + * + * Copyright (C) 2006 Internet Archive. + * + * This file is part of Wayback. + * + * Wayback is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * any later version. + * + * Wayback is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with Wayback; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.archive.wayback.util.webapp; + +/** + * A simple composition of the RequestHandler which an HttpServletRequest was + * mapped to, and the path prefix which indicated the RequestHandler. This + * allows computing the portion of the original request path within the + * RequestHandler. + * + * @author brad + * + */ +public class RequestHandlerContext { + + private RequestHandler handler = null; + private String pathPrefix = null; + + /** + * Constructor + * @param handler the RequestHandler to which the incoming request was + * mapped + * @param pathPrefix the leading portion of the original request path that + * indicated the RequestHandler + */ + public RequestHandlerContext(RequestHandler handler, String pathPrefix) { + this.handler = handler; + this.pathPrefix = pathPrefix; + } + /** + * @return the RequestHandler to which the incoming request was mapped. + */ + public RequestHandler getRequestHandler() { + return handler; + } + /** + * @return the leading portion of the original request path that + * indicated the RequestHandler + */ + public String getPathPrefix() { + return pathPrefix; + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestHandlerContext.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestMapper.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestMapper.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestMapper.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,266 @@ +/* RequestMapper + * + * $Id$: + * + * Created on Mar 23, 2010. + * + * Copyright (C) 2006 Internet Archive. + * + * This file is part of Wayback. + * + * Wayback is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * any later version. + * + * Wayback is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with Wayback; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.archive.wayback.util.webapp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; + +/** + * This class maintains a mapping of RequestHandlers and ShutDownListeners, to + * allow (somewhat) efficient mapping and delegation of incoming requests to + * the appropriate RequestHandler. + * + * This class uses PortMapper to delegate some of the responsibility of mapping + * requests received on a particular port, and also allows configuration of a + * global PRE RequestHandler, which gets first dibs on EVERY incoming request, + * as well as a global POST RequestHandler, which may attempt to handle any + * incoming request not handled by the normal RequestHandler mapping. + * + * @author brad + * + */ +public class RequestMapper { + + private static final Logger LOGGER = Logger.getLogger( + RequestMapper.class.getName()); + + private ArrayList<ShutdownListener> shutdownListeners = null; + + private HashMap<Integer,PortMapper> portMap = null; + private RequestHandler globalPreRequestHandler = null; + private RequestHandler globalPostRequestHandler = null; + + private final static String REQUEST_CONTEXT_PREFIX = + "webapp-request-context-path-prefix"; + + /** + * Bean name used to register the special global PRE RequestHandler. + */ + public final static String GLOBAL_PRE_REQUEST_HANDLER = "-"; + /** + * Bean name used to register the special global POST RequestHandler. + */ + public final static String GLOBAL_POST_REQUEST_HANDLER = "+"; + + /** + * Construct a RequestMapper, for the given RequestHandler objects, on the + * specified ServletContext. This method will call setServletContext() on + * each RequestMapper, followed immediately by registerPortListener() + * + * @param requestHandlers Collection of RequestHandlers which handle + * requests + * @param servletContext the webapp ServletContext where this RequestMapper + * is configured. + */ + public RequestMapper(Collection<RequestHandler> requestHandlers, + ServletContext servletContext) { + portMap = new HashMap<Integer, PortMapper>(); + shutdownListeners = new ArrayList<ShutdownListener>(); + Iterator<RequestHandler> itr = requestHandlers.iterator(); + LOGGER.info("Registering handlers."); + while(itr.hasNext()) { + RequestHandler requestHandler = itr.next(); + requestHandler.setServletContext(servletContext); + requestHandler.registerPortListener(this); + } + LOGGER.info("Registering handlers complete."); + } + + /** + * Request the shutdownListener object to be notified of ServletContext + * shutdown. + * @param shutdownListener the object which needs to have shutdown() called + * when the ServletContext is destroyed. + */ + public void addShutdownListener(ShutdownListener shutdownListener) { + shutdownListeners.add(shutdownListener); + } + /** + * Configure the specified RequestHandler to handle ALL incoming requests + * before any other normal mapping. + * @param requestHandler the global PRE RequestHandler + */ + public void addGlobalPreRequestHandler(RequestHandler requestHandler) { + globalPreRequestHandler = requestHandler; + } + /** + * Configure the specified RequestHandler to handle ALL incoming requests + * after all other normal mapping has been attempted + * @param requestHandler the global POST RequestHandler + */ + public void addGlobalPostRequestHandler(RequestHandler requestHandler) { + globalPostRequestHandler = requestHandler; + } + /** + * Register the RequestHandler to accept requests on the given port, for the + * specified host and path. + * @param port the integer port on which the RequestHandler gets requests. + * @param host the String Host which the RequestHandler matches, or null, if + * the RequestHandler should match ALL hosts. + * @param path the String path which the RequestHandler matches, or null, if + * the RequestHandler should match ALL paths. + * @param requestHandler the RequestHandler to register. + */ + public void addRequestHandler(int port, String host, String path, + RequestHandler requestHandler) { + LOGGER.info("Registered:" + port + ":" + + (host == null ? "(null)" : host) + ":" + + (path == null ? "(null)" : path)); + Integer portInt = Integer.valueOf(port); + PortMapper portMapper = portMap.get(portInt); + if(portMapper == null) { + portMapper = new PortMapper(portInt); + portMap.put(portInt, portMapper); + } + portMapper.addRequestHandler(host, path, requestHandler); + } + + private RequestHandlerContext mapRequest(HttpServletRequest request) { + RequestHandlerContext handlerContext = null; + + int port = request.getLocalPort(); + Integer portInt = Integer.valueOf(port); + PortMapper portMapper = portMap.get(portInt); + if(portMapper != null) { + handlerContext = portMapper.getRequestHandlerContext(request); + } + return handlerContext; + } + + /** + * Map the incoming request to the appropriate RequestHandler, including + * the PRE and POST RequestHandlers, if configured. + * @param request the incoming HttpServletRequest + * @param response the HttpServletResponse to return data to the client + * @return true if a response was returned to the client. + * @throws ServletException for usual reasons. + * @throws IOException for usual reasons. + */ + public boolean handleRequest(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + boolean handled = false; + if(globalPreRequestHandler != null) { + handled = + globalPreRequestHandler.handleRequest(request, response); + } + if(handled == false) { + RequestHandlerContext handlerContext = mapRequest(request); + if(handlerContext != null) { + RequestHandler requestHandler = + handlerContext.getRequestHandler(); + request.setAttribute(REQUEST_CONTEXT_PREFIX, + handlerContext.getPathPrefix() + "/"); + handled = requestHandler.handleRequest(request, response); + } + } + if(handled == false) { + if(globalPostRequestHandler != null) { + handled = + globalPostRequestHandler.handleRequest(request, response); + } + } + return handled; + } + + /** + * notify all registered ShutdownListener objects that the ServletContext is + * being destroyed. + */ + public void shutdown() { + for(ShutdownListener shutdownListener : shutdownListeners) { + try { + shutdownListener.shutdown(); + } catch(Exception e) { + LOGGER.error("failed shutdown", e); + } + } + } + + /** + * Extract the request path prefix, as computed at RequestHandler mapping, + * from the HttpServletRequest object. + * + * @param request HttpServlet request object being handled + * @return the portion of the original request path which indicated the + * RequestHandler, including the trailing '/'. + */ + public static String getRequestPathPrefix(HttpServletRequest request) { + return (String) request.getAttribute(REQUEST_CONTEXT_PREFIX); + } + + /** + * @param request HttpServlet request object being handled + * @return the portion of the incoming path within the RequestHandler + * handling the request, not including a leading "/", and not including + * query arguments. + */ + public static String getRequestContextPath(HttpServletRequest request) { + String prefix = (String) request.getAttribute(REQUEST_CONTEXT_PREFIX); + String requestUrl = request.getRequestURI(); + if(prefix == null) { + return requestUrl; + } + if(requestUrl.startsWith(prefix)) { + return requestUrl.substring(prefix.length()); + } + return requestUrl; + } + + /** + * @param request HttpServlet request object being handled + * @return the portion of the incoming path within the RequestHandler + * handling the request, not including a leading "/", including query + * arguments. + */ + public static String getRequestContextPathQuery(HttpServletRequest request) { + String prefix = (String) request.getAttribute(REQUEST_CONTEXT_PREFIX); + StringBuilder sb = new StringBuilder(request.getRequestURI()); + String requestUrl = null; + String query = request.getQueryString(); + if(query != null) { + requestUrl = sb.append("?").append(query).toString(); + } else { + requestUrl = sb.toString(); + } + if(prefix == null) { + return requestUrl; + } + if(requestUrl.startsWith(prefix)) { + return requestUrl.substring(prefix.length()); + } + return requestUrl; + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/RequestMapper.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ShutdownListener.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ShutdownListener.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ShutdownListener.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,18 @@ +/** + * + */ +package org.archive.wayback.util.webapp; + +/** + * Interface representing a desire to be notified when the containing + * ServletContext is being destroyed. + * + * @author brad + * + */ +public interface ShutdownListener { + /** + * Called when the ServletContext is being destroyed. + */ + public void shutdown(); +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/ShutdownListener.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/SpringReader.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/SpringReader.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/SpringReader.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,61 @@ +/** + * + */ +package org.archive.wayback.util.webapp; +import java.util.Collection; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.beans.factory.xml.XmlBeanFactory; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +/** + * Single static method to read a Spring XML configuration, extract + * RequestHandlers, and return a RequestMapper which delegates requests to + * those RequestHandlers. + * + * @author brad + * + */ +public class SpringReader { + private static final Logger LOGGER = Logger.getLogger( + SpringReader.class.getName()); + + /** + * Read the single Spring XML configuration file located at the specified + * path, performing PropertyPlaceHolder interpolation, extracting all beans + * which implement the RequestHandler interface, and construct a + * RequestMapper for those RequestHandlers, on the specified ServletContext. + * @param configPath the path to the Spring XML file containing the + * configuration. + * @param servletContext the ServletContext where the RequestHandlers should + * be mapped + * @return a new ReqeustMapper which delegates requests for the + * ServletContext + */ + @SuppressWarnings("unchecked") + public static RequestMapper readSpringConfig(String configPath, + ServletContext servletContext) { + LOGGER.info("Loading from config file " + configPath); + + Resource resource = new FileSystemResource(configPath); + XmlBeanFactory factory = new XmlBeanFactory(resource); + Map map = factory.getBeansOfType(PropertyPlaceholderConfigurer.class); + if(map != null) { + Collection<PropertyPlaceholderConfigurer> macros = map.values(); + for(PropertyPlaceholderConfigurer macro : macros) { + macro.postProcessBeanFactory(factory); + } + } + LOGGER.info("Pre-instanting Singletons starting"); + factory.preInstantiateSingletons(); + LOGGER.info("Pre-instanting Singletons complete"); + Map<String,RequestHandler> beans = + factory.getBeansOfType(RequestHandler.class,false,false); + return new RequestMapper(beans.values(), servletContext); + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/SpringReader.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id Added: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/StaticFileRequestHandler.java =================================================================== --- trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/StaticFileRequestHandler.java (rev 0) +++ trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/StaticFileRequestHandler.java 2010-04-27 22:12:31 UTC (rev 3074) @@ -0,0 +1,49 @@ +/** + * + */ +package org.archive.wayback.util.webapp; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; + +/** + * RequestHandler implementation which allows serving of static files, and + * .jsp files within a ServletContext. + * + * @author brad + */ +public class StaticFileRequestHandler extends AbstractRequestHandler { + + private static final Logger LOGGER = Logger.getLogger( + StaticFileRequestHandler.class.getName()); + + public boolean handleRequest(HttpServletRequest httpRequest, + HttpServletResponse httpResponse) throws ServletException, IOException { + boolean handled = false; + String contextRelativePath = httpRequest.getServletPath(); + String absPath = getServletContext().getRealPath(contextRelativePath); + File test = new File(absPath); + // TODO: check for index.jsp(or configurable equivalent), + // if it's a directory? + if(test.isFile()) { + LOGGER.trace("static path:" + absPath); + RequestDispatcher dispatcher = + httpRequest.getRequestDispatcher(contextRelativePath); +// try { + dispatcher.forward(httpRequest, httpResponse); + handled = true; +// } catch(Exception e) { +// } + } else { + LOGGER.trace("Not-static path:" + absPath); + } + return handled; + } +} Property changes on: trunk/archive-access/projects/wayback/wayback-core/src/main/java/org/archive/wayback/util/webapp/StaticFileRequestHandler.java ___________________________________________________________________ Added: svn:keywords + Author Date Revision Id This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |