From: costin Date: Thu, 26 Nov 2009 06:45:13 +0000 (+0000) Subject: Based on the code in tomcat.lite, but using the new http connector instead of coyote. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=5b7cad7d4ef41905ef41baee749a613659b14bab;p=tomcat7.0 Based on the code in tomcat.lite, but using the new http connector instead of coyote. Also the ObjectManager and properties are no longer used. This can be compiled against both 3.0 and 2.5 ( with proper exclude ), since one of the goals of tomcat-lite is to be useable against current servers. Note that this is mostly for testing and to allow existing servlets to be used _outside_ of a servlet engine - this is not a servlet engine. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@884415 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterChainImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterChainImpl.java new file mode 100644 index 000000000..5f9034dfd --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterChainImpl.java @@ -0,0 +1,168 @@ +/* + * Copyright 2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.lite.servlet; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/** + * Wraps the list of filters for the current request. One instance + * associated with each RequestImpl, reused. + * + * Populated by the mapper ( WebappFilterMapper for example ), which + * determines the filters for the current request. + * + * Not thread safe. + */ +public final class FilterChainImpl implements FilterChain { + + private List filters = new ArrayList(); + + /** + * The int which is used to maintain the current position + * in the filter chain. + */ + private int pos = 0; + + /** + * The servlet instance to be executed by this chain. + */ + private Servlet servlet = null; + + + private ServletConfigImpl wrapper; + + + public FilterChainImpl() { + super(); + } + + + /** + * Invoke the next filter in this chain, passing the specified request + * and response. If there are no more filters in this chain, invoke + * the service() method of the servlet itself. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + + + // Call the next filter if there is one + if (pos < filters.size()) { + FilterConfigImpl filterConfig = filters.get(pos++); + Filter filter = null; + try { + filter = filterConfig.getFilter(); + filter.doFilter(request, response, this); + } catch (IOException e) { + throw e; + } catch (ServletException e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + e.printStackTrace(); + throw new ServletException("Throwable", e); + } + return; + } + + // We fell off the end of the chain -- call the servlet instance + try { + if (servlet != null) + servlet.service(request, response); + } catch (IOException e) { + throw e; + } catch (ServletException e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new ServletException("Throwable", e); + } + } + + + // -------------------------------------------------------- Package Methods + + + + /** + * Add a filter to the set of filters that will be executed in this chain. + * + * @param filterConfig The FilterConfig for the servlet to be executed + */ + public void addFilter(FilterConfigImpl filterConfig) { + filters.add(filterConfig); + } + + + /** + * Release references to the filters and wrapper executed by this chain. + */ + public void release() { + filters.clear(); + pos = 0; + servlet = null; + } + + + /** + * Set the servlet that will be executed at the end of this chain. + * Set by the mapper filter + */ + public void setServlet(ServletConfigImpl wrapper, Servlet servlet) { + this.wrapper = wrapper; + this.servlet = servlet; + } + + // ------ Getters for information ------------ + + public int getSize() { + return filters.size(); + } + + public FilterConfigImpl getFilter(int i) { + return filters.get(i); + } + + public Servlet getServlet() { + return servlet; + } + + public ServletConfigImpl getServletConfig() { + return wrapper; + } + + public int getPos() { + return pos; + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterConfigImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterConfigImpl.java new file mode 100644 index 000000000..13c08b290 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterConfigImpl.java @@ -0,0 +1,181 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Map; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.tomcat.servlets.util.Enumerator; + + +/** + * A Filter is configured in web.xml by: + * - name - used in mappings + * - className - used to instantiate the filter + * - init params + * - other things not used in the servlet container ( icon, descr, etc ) + * + * Alternatively, in API mode you can pass the actual filter. + * + * @see ServletConfigImpl + */ +public class FilterConfigImpl implements FilterConfig { + + public FilterConfigImpl(ServletContextImpl context) { + this.ctx = context; + } + + boolean asyncSupported; + + ServletContextImpl ctx = null; + + /** + * The application Filter we are configured for. + */ + private transient Filter filter = null; + + String descryption; + + private String filterName; + + private String filterClassName; + + Map initParams; + + private Class filterClass; + + private boolean initDone = false; + + public void setData(String filterName, String filterClass, + Map params) { + this.filterName = filterName; + this.filterClassName = filterClass; + this.initParams = params; + } + + public void setFilter(Filter f) { + filter = f; + } + + public String getFilterName() { + return filterName; + } + + public void setFilterClass(Class filterClass2) { + this.filterClass = filterClass2; + } + + + public String getInitParameter(String name) { + if (initParams == null) return null; + return initParams.get(name); + } + + /** + * Return an Enumeration of the names of the initialization + * parameters for this Filter. + */ + public Enumeration getInitParameterNames() { + if (initParams == null) + return (new Enumerator(new ArrayList())); + else + return (new Enumerator(initParams.keySet())); + } + + + /** + * Return the ServletContext of our associated web application. + */ + public ServletContext getServletContext() { + return ctx; + } + + /** + * Return the application Filter we are configured for. + */ + public Filter createFilter() throws ClassCastException, ClassNotFoundException, + IllegalAccessException, InstantiationException, ServletException { + + // Return the existing filter instance, if any + if (filter != null) + return filter; + + ClassLoader classLoader = ctx.getClassLoader(); + + ClassLoader oldCtxClassLoader = + Thread.currentThread().getContextClassLoader(); + if (classLoader != oldCtxClassLoader) { + Thread.currentThread().setContextClassLoader(classLoader); + } + try { + if (filterClass == null) { + filterClass = (Class) classLoader.loadClass(filterClassName); + } + this.filter = (Filter) filterClass.newInstance(); + } finally { + if (classLoader != oldCtxClassLoader) { + Thread.currentThread().setContextClassLoader(oldCtxClassLoader); + } + } + + // TODO: resource injection + + return filter; + } + + public Filter getFilter() throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException { + Filter filter = createFilter(); + if (!initDone ) { + filter.init(this); + initDone = true; + } + return (this.filter); + } + + + /** + * Release the Filter instance associated with this FilterConfig, + * if there is one. + */ + public void release() { + if (this.filter != null){ + filter.destroy(); + } + this.filter = null; + } + + public boolean setInitParameter(String name, String value) + throws IllegalArgumentException, IllegalStateException { + return ServletContextImpl.setInitParameter(ctx, initParams, + name, value); + } + + public Set setInitParameters(Map initParameters) + throws IllegalArgumentException, IllegalStateException { + return ServletContextImpl.setInitParameters(ctx, initParams, + initParameters); + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/JspLoader.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/JspLoader.java new file mode 100644 index 000000000..63a301de3 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/JspLoader.java @@ -0,0 +1,51 @@ +/* + */ +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.tomcat.lite.http.HttpChannel; +import org.apache.tomcat.lite.http.HttpRequest; +import org.apache.tomcat.servlets.jsp.BaseJspLoader; + +public class JspLoader extends BaseJspLoader { + + public ClassLoader getClassLoader(ServletContext ctx) { + return ((ServletContextImpl) ctx).getClassLoader(); + } + + public String getClassPath(ServletContext ctx) { + return ((ServletContextImpl) ctx).getClassPath(); + } + + protected void compileAndInitPage(ServletContext ctx, + String jspUri, + ServletConfig cfg, + String classPath) + throws ServletException, IOException { + + ServletContextImpl ctxI = (ServletContextImpl)ctx; + HttpChannel server = ctxI.getEngine().getLocalConnector().getServer(); + + HttpRequest req = server.getRequest(); + + req.addParameter("uriroot", ctx.getRealPath("/")); + req.addParameter("jspFiles", jspUri.substring(1)); + req.addParameter("classPath", classPath); + req.addParameter("pkg", getPackage(ctx, jspUri)); + + // TODO: init params to specify + // TODO: remote request + RequestDispatcher disp = ctx.getNamedDispatcher("jspc"); + + ServletRequestImpl sreq = + TomcatLite.getFacade(req); + sreq.setContext((ServletContextImpl) ctx); + disp.forward(sreq, sreq.getResponse()); + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/Locale2Charset.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/Locale2Charset.java new file mode 100644 index 000000000..649967bd0 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/Locale2Charset.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tomcat.lite.servlet; + + +import java.io.InputStream; +import java.util.Locale; +import java.util.Properties; + + + +/** + * One instance per Context. Holds the + * + * Utility class that attempts to map from a Locale to the corresponding + * character set to be used for interpreting input text (or generating + * output text) when the Content-Type header does not include one. You + * can customize the behavior of this class by modifying the mapping data + * it loads, or by subclassing it (to change the algorithm) and then using + * your own version for a particular web application. + * + * @author Craig R. McClanahan + */ +public class Locale2Charset { + + + // shared for all instances - they can add more in a copy + private static Properties defaultMap = new Properties(); + + static { + defaultMap.put("en", "ISO-8859-1"); + } + + Locale2Charset() { + map = defaultMap; + } + + + // ---------------------------------------------------- Instance Variables + + /** + * The mapping properties that have been initialized from the specified or + * default properties resource. + */ + private Properties map; + + + // ------------------------------------------------------- Public Methods + + + /** + * Calculate the name of a character set to be assumed, given the specified + * Locale and the absence of a character set specified as part of the + * content type header. + * + * @param locale The locale for which to calculate a character set + */ + public String getCharset(Locale locale) { + // Match full language_country_variant first, then language_country, + // then language only + String charset = map.getProperty(locale.toString()); + if (charset == null) { + charset = map.getProperty(locale.getLanguage() + "_" + + locale.getCountry()); + if (charset == null) { + charset = map.getProperty(locale.getLanguage()); + } + } + return (charset); + } + + + /** + * The deployment descriptor can have a + * locale-encoding-mapping-list element which describes the + * webapp's desired mapping from locale to charset. This method + * gets called when processing the web.xml file for a context + * + * @param locale The locale for a character set + * @param charset The charset to be associated with the locale + */ + public void addCharsetMapping(String locale, String charset) { + if (map == defaultMap) { + // new copy, don't modify original + map = new Properties(defaultMap); + } + map.put(locale, charset); + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/RequestDispatcherImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/RequestDispatcherImpl.java new file mode 100644 index 000000000..91d37b766 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/RequestDispatcherImpl.java @@ -0,0 +1,848 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tomcat.lite.http.MappingData; +import org.apache.tomcat.lite.io.CBuffer; + + +/** + * + */ +public final class RequestDispatcherImpl implements RequestDispatcher { + /** + * The request attribute under which the original servlet path is stored + * on an forwarded dispatcher request. + */ + public static final String FORWARD_SERVLET_PATH_ATTR = + "javax.servlet.forward.servlet_path"; + + + /** + * The request attribute under which the original query string is stored + * on an forwarded dispatcher request. + */ + public static final String FORWARD_QUERY_STRING_ATTR = + "javax.servlet.forward.query_string"; + + /** + * The request attribute under which the original request URI is stored + * on an forwarded dispatcher request. + */ + public static final String FORWARD_REQUEST_URI_ATTR = + "javax.servlet.forward.request_uri"; + + + /** + * The request attribute under which the original context path is stored + * on an forwarded dispatcher request. + */ + public static final String FORWARD_CONTEXT_PATH_ATTR = + "javax.servlet.forward.context_path"; + + + /** + * The request attribute under which the original path info is stored + * on an forwarded dispatcher request. + */ + public static final String FORWARD_PATH_INFO_ATTR = + "javax.servlet.forward.path_info"; + + /** + * The request attribute under which we store the servlet name on a + * named dispatcher request. + */ + public static final String NAMED_DISPATCHER_ATTR = + "org.apache.catalina.NAMED"; + + /** + * The request attribute under which the request URI of the included + * servlet is stored on an included dispatcher request. + */ + public static final String INCLUDE_REQUEST_URI_ATTR = + "javax.servlet.include.request_uri"; + + + /** + * The request attribute under which the context path of the included + * servlet is stored on an included dispatcher request. + */ + public static final String INCLUDE_CONTEXT_PATH_ATTR = + "javax.servlet.include.context_path"; + + + /** + * The request attribute under which the path info of the included + * servlet is stored on an included dispatcher request. + */ + public static final String INCLUDE_PATH_INFO_ATTR = + "javax.servlet.include.path_info"; + + + /** + * The request attribute under which the servlet path of the included + * servlet is stored on an included dispatcher request. + */ + public static final String INCLUDE_SERVLET_PATH_ATTR = + "javax.servlet.include.servlet_path"; + + + /** + * The request attribute under which the query string of the included + * servlet is stored on an included dispatcher request. + */ + public static final String INCLUDE_QUERY_STRING_ATTR = + "javax.servlet.include.query_string"; + + /** + * The request attribute under which we expose the value of the + * <jsp-file> value associated with this servlet, + * if any. + */ + public static final String JSP_FILE_ATTR = + "org.apache.catalina.jsp_file"; + + + // ----------------------------------------------------- Instance Variables + + private static Logger log = Logger.getLogger(RequestDispatcherImpl.class.getName()); + + private ServletContextImpl ctx = null; + + /** + * The servlet name for a named dispatcher. + */ + private String name = null; + + // Path for a path dispatcher + private String path; + + /** + * MappingData object - per thread for buffering. + */ + private transient ThreadLocal localMappingData = + new ThreadLocal(); + + /* + OrigRequest(ServletRequestImpl) -> include/forward * -> this include + + On the path: user-defined RequestWrapper or our ServletRequestWrapper + + include() is called with a RequestWrapper(->...->origRequest) or origRequest + + Based on params, etc -> we wrap the req / response in ServletRequestWrapper, + call filters+servlet. Inside, the req can be wrapped again in + userReqWrapper, and other include called. + + + */ + + /** + * The outermost request that will be passed on to the invoked servlet. + */ + private ServletRequest outerRequest = null; + + /** + * The outermost response that will be passed on to the invoked servlet. + */ + private ServletResponse outerResponse = null; + + /** + * The request wrapper we have created and installed (if any). + */ + private ServletRequest wrapRequest = null; + + /** + * The response wrapper we have created and installed (if any). + */ + private ServletResponse wrapResponse = null; + + // Parameters used when constructing the dispatcvher + /** + * The extra path information for this RequestDispatcher. + */ + private String pathInfo = null; + /** + * The query string parameters for this RequestDispatcher. + */ + private String queryString = null; + /** + * The request URI for this RequestDispatcher. + */ + private String requestURI = null; + /** + * The servlet path for this RequestDispatcher. + */ + private String servletPath = null; + + // + private String origServletPath = null; + + /** + * The Wrapper associated with the resource that will be forwarded to + * or included. + */ + private ServletConfigImpl wrapper = null; + + private Servlet servlet; + + /** Named dispatcher + */ + public RequestDispatcherImpl(ServletConfigImpl wrapper, String name) { + this.wrapper = wrapper; + this.name = name; + this.ctx = (ServletContextImpl) wrapper.getServletContext(); + + } + + public RequestDispatcherImpl(ServletContextImpl ctx, String path) { + this.path = path; + this.ctx = ctx; + } + + + + /** + * Forward this request and response to another resource for processing. + * Any runtime exception, IOException, or ServletException thrown by the + * called servlet will be propogated to the caller. + * + * @param request The servlet request to be forwarded + * @param response The servlet response to be forwarded + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + // Reset any output that has been buffered, but keep headers/cookies + if (response.isCommitted()) { + throw new IllegalStateException("forward(): response.isComitted()"); + } + + try { + response.resetBuffer(); + } catch (IllegalStateException e) { + throw e; + } + + // Set up to handle the specified request and response + setup(request, response, false); + + // Identify the HTTP-specific request and response objects (if any) + HttpServletRequest hrequest = (HttpServletRequest) request; + + ServletRequestWrapperImpl wrequest = + (ServletRequestWrapperImpl) wrapRequest(); + + + if (name != null) { + wrequest.setRequestURI(hrequest.getRequestURI()); + wrequest.setContextPath(hrequest.getContextPath()); + wrequest.setServletPath(hrequest.getServletPath()); + wrequest.setPathInfo(hrequest.getPathInfo()); + wrequest.setQueryString(hrequest.getQueryString()); + + + } else { // path based + mapPath(); + if (wrapper == null) { + throw new ServletException("Forward not found " + + path); + } + String contextPath = ctx.getContextPath(); + if (hrequest.getAttribute(FORWARD_REQUEST_URI_ATTR) == null) { + wrequest.setAttribute(FORWARD_REQUEST_URI_ATTR, + hrequest.getRequestURI()); + wrequest.setAttribute(FORWARD_CONTEXT_PATH_ATTR, + hrequest.getContextPath()); + wrequest.setAttribute(FORWARD_SERVLET_PATH_ATTR, + hrequest.getServletPath()); + wrequest.setAttribute(FORWARD_PATH_INFO_ATTR, + hrequest.getPathInfo()); + wrequest.setAttribute(FORWARD_QUERY_STRING_ATTR, + hrequest.getQueryString()); + } + + wrequest.setContextPath(contextPath); + wrequest.setRequestURI(requestURI); + wrequest.setServletPath(servletPath); + wrequest.setPathInfo(pathInfo); + if (queryString != null) { + wrequest.setQueryString(queryString); + wrequest.setQueryParams(queryString); + } + } + processRequest(outerRequest, outerResponse); + + wrequest.recycle(); + unwrapRequest(); + + // This is not a real close in order to support error processing +// if ( log.isDebugEnabled() ) +// log.debug(" Disabling the response for futher output"); + + if (response instanceof ServletResponseImpl) { + ((ServletResponseImpl) response).flushBuffer(); + ((ServletResponseImpl) response).setSuspended(true); + } else { + // Servlet SRV.6.2.2. The Resquest/Response may have been wrapped + // and may no longer be instance of RequestFacade + if (log.isLoggable(Level.FINE)){ + log.fine( " The Response is vehiculed using a wrapper: " + + response.getClass().getName() ); + } + + // Close anyway + try { + PrintWriter writer = response.getWriter(); + writer.close(); + } catch (IllegalStateException e) { + try { + ServletOutputStream stream = response.getOutputStream(); + stream.close(); + } catch (IllegalStateException f) { + ; + } catch (IOException f) { + ; + } + } catch (IOException e) { + ; + } + } + } + + + + /** + * Include the response from another resource in the current response. + * Any runtime exception, IOException, or ServletException thrown by the + * called servlet will be propogated to the caller. + * + * @param request The servlet request that is including this one + * @param response The servlet response to be appended to + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + + // Set up to handle the specified request and response + setup(request, response, true); + + // Create a wrapped response to use for this request + // this actually gets inserted somewhere in the chain - it's not + // the last one, but first non-user response + wrapResponse(); + ServletRequestWrapperImpl wrequest = + (ServletRequestWrapperImpl) wrapRequest(); + + + // Handle an HTTP named dispatcher include + if (name != null) { + wrequest.setAttribute(NAMED_DISPATCHER_ATTR, name); + if (servletPath != null) wrequest.setServletPath(servletPath); + wrequest.setAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR, + new Integer(WebappFilterMapper.INCLUDE)); + wrequest.setAttribute(WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR, + origServletPath); + } else { + mapPath(); + String contextPath = ctx.getContextPath(); + if (requestURI != null) + wrequest.setAttribute(INCLUDE_REQUEST_URI_ATTR, + requestURI); + if (contextPath != null) + wrequest.setAttribute(INCLUDE_CONTEXT_PATH_ATTR, + contextPath); + if (servletPath != null) + wrequest.setAttribute(INCLUDE_SERVLET_PATH_ATTR, + servletPath); + if (pathInfo != null) + wrequest.setAttribute(INCLUDE_PATH_INFO_ATTR, + pathInfo); + if (queryString != null) { + wrequest.setAttribute(INCLUDE_QUERY_STRING_ATTR, + queryString); + wrequest.setQueryParams(queryString); + } + + wrequest.setAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR, + new Integer(WebappFilterMapper.INCLUDE)); + wrequest.setAttribute(WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR, + origServletPath); + } + + invoke(outerRequest, outerResponse); + + wrequest.recycle(); + unwrapRequest(); + unwrapResponse(); + } + + + // -------------------------------------------------------- Private Methods + + public void mapPath() { + if (path == null || servletPath != null) return; + + // Retrieve the thread local URI, used for mapping + // TODO: recycle RequestDispatcher stack and associated objects + // instead of this object + + // Retrieve the thread local mapping data + MappingData mappingData = (MappingData) localMappingData.get(); + if (mappingData == null) { + mappingData = new MappingData(); + localMappingData.set(mappingData); + } + + // Get query string + int pos = path.indexOf('?'); + if (pos >= 0) { + queryString = path.substring(pos + 1); + } else { + pos = path.length(); + } + + // Map the URI + CBuffer uriMB = CBuffer.newInstance(); + //mappingData.localURIBytes; + uriMB.recycle(); + //CharChunk uriCC = uriMB.getCharChunk(); + try { + /* + * Ignore any trailing path params (separated by ';') for mapping + * purposes. + * This is sometimes broken - path params can be on any path + * component, not just last. + */ + int semicolon = path.indexOf(';'); + if (pos >= 0 && semicolon > pos) { + semicolon = -1; + } + if (ctx.getContextPath().length() > 1 ) { + uriMB.append(ctx.getContextPath()); + } + uriMB.append(path, 0, + semicolon > 0 ? semicolon : pos); + + // TODO: make charBuffer part of request or something + ctx.getEngine().getDispatcher().map(ctx.getContextMap(), uriMB, mappingData); + + // at least default wrapper must be returned + +// /* +// * Append any trailing path params (separated by ';') that were +// * ignored for mapping purposes, so that they're reflected in the +// * RequestDispatcher's requestURI +// */ +// if (semicolon > 0) { +// // I don't think this will be used in future +// charBuffer.append(path, +// semicolon, pos - semicolon); +// } + } catch (Exception e) { + log.log(Level.SEVERE, "getRequestDispatcher()", e); + } + + wrapper = (ServletConfigImpl) mappingData.getServiceObject(); + servletPath = mappingData.wrapperPath.toString(); + pathInfo = mappingData.pathInfo.toString(); + + mappingData.recycle(); + + } + + + /** + * Prepare the request based on the filter configuration. + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet error occurs + */ + private void processRequest(ServletRequest request, + ServletResponse response) + throws IOException, ServletException { + Integer disInt = + (Integer) request.getAttribute(WebappFilterMapper.DISPATCHER_TYPE_ATTR); + if (disInt != null) { + if (disInt.intValue() != WebappFilterMapper.ERROR) { + outerRequest.setAttribute + (WebappFilterMapper.DISPATCHER_REQUEST_PATH_ATTR, + origServletPath); + outerRequest.setAttribute + (WebappFilterMapper.DISPATCHER_TYPE_ATTR, + new Integer(WebappFilterMapper.FORWARD)); + } + invoke(outerRequest, response); + } + + } + + + + + /** + * Ask the resource represented by this RequestDispatcher to process + * the associated request, and create (or append to) the associated + * response. + *

+ * IMPLEMENTATION NOTE: This implementation assumes + * that no filters are applied to a forwarded or included resource, + * because they were already done for the original request. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet error occurs + */ + private void invoke(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + + // Checking to see if the context classloader is the current context + // classloader. If it's not, we're saving it, and setting the context + // classloader to the Context classloader + ClassLoader oldCCL = Thread.currentThread().getContextClassLoader(); + ClassLoader contextClassLoader = ctx.getClassLoader(); + + if (oldCCL != contextClassLoader) { + Thread.currentThread().setContextClassLoader(contextClassLoader); + } else { + oldCCL = null; + } + + // Initialize local variables we may need + HttpServletResponse hresponse = (HttpServletResponse) response; + IOException ioException = null; + ServletException servletException = null; + RuntimeException runtimeException = null; + + servletException = allocateServlet(hresponse, servletException); + + // Get the FilterChain Here + WebappFilterMapper factory = + ((ServletContextImpl)wrapper.getServletContext()).getFilterMapper(); + + FilterChainImpl filterChain = factory.createFilterChain(request, + wrapper, + servlet); + + // Call the service() method for the allocated servlet instance + try { + String jspFile = wrapper.getJspFile(); + if (jspFile != null) + request.setAttribute(JSP_FILE_ATTR, jspFile); + else + request.removeAttribute(JSP_FILE_ATTR); + // for includes/forwards + if ((servlet != null) && (filterChain != null)) { + filterChain.doFilter(request, response); + } + } catch (IOException e) { + ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + + wrapper.getServletName(), e); + ioException = e; + } catch (UnavailableException e) { + ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + + wrapper.getServletName(), e); + servletException = e; + wrapper.unavailable(e); + } catch (ServletException e) { + servletException = e; + } catch (RuntimeException e) { + ctx.getLogger().log(Level.WARNING, "RequestDispatcherImpl error " + + wrapper.getServletName(), e); + runtimeException = e; + } + request.removeAttribute(JSP_FILE_ATTR); + + // Release the filter chain (if any) for this request + if (filterChain != null) + filterChain.release(); + + servletException = servletDealocate(servletException); + + // Reset the old context class loader + if (oldCCL != null) + Thread.currentThread().setContextClassLoader(oldCCL); + + // Unwrap request/response if needed + unwrapRequest(); + unwrapResponse(); + + // Rethrow an exception if one was thrown by the invoked servlet + if (ioException != null) + throw ioException; + if (servletException != null) + throw servletException; + if (runtimeException != null) + throw runtimeException; + + } + + private ServletException servletDealocate(ServletException servletException) + { + if (servlet != null) { + wrapper.deallocate(servlet); + } + return servletException; + } + + private ServletException allocateServlet(HttpServletResponse hresponse, + ServletException servletException) + throws IOException + { + boolean unavailable = false; + + // Check for the servlet being marked unavailable + if (wrapper.isUnavailable()) { + ctx.getLogger().log(Level.WARNING, "isUnavailable() " + wrapper.getServletName()); + long available = wrapper.getAvailable(); + if ((available > 0L) && (available < Long.MAX_VALUE)) + hresponse.setDateHeader("Retry-After", available); + hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, + "Unavailable"); // No need to include internal info: wrapper.getServletName(); + unavailable = true; + } + + // Allocate a servlet instance to process this request + try { + if (!unavailable) { + servlet = wrapper.allocate(); + } + } catch (ServletException e) { + ctx.getLogger().log(Level.WARNING, "RequestDispatcher: allocate " + + wrapper.toString()); + servletException = e; + servlet = null; + } catch (Throwable e) { + ctx.getLogger().log(Level.WARNING, "allocate() error " + wrapper.getServletName(), e); + servletException = new ServletException + ("Allocate error " + wrapper.getServletName(), e); + servlet = null; + } + return servletException; + } + + + /** + * Set up to handle the specified request and response + * + * @param request The servlet request specified by the caller + * @param response The servlet response specified by the caller + * @param including Are we performing an include() as opposed to + * a forward()? + */ + private void setup(ServletRequest request, ServletResponse response, + boolean including) { + + this.outerRequest = request; + this.outerResponse = response; + } + + + /** + * Unwrap the request if we have wrapped it. Not sure how it could end + * up in the middle. + */ + private void unwrapRequest() { + if (wrapRequest == null) + return; + + ServletRequest previous = null; + ServletRequest current = outerRequest; + while (current != null) { + // If we run into the container request we are done + if (current instanceof ServletRequestImpl) + break; + + // Remove the current request if it is our wrapper + if (current == wrapRequest) { + ServletRequest next = + ((ServletRequestWrapper) current).getRequest(); + if (previous == null) + outerRequest = next; + else + ((ServletRequestWrapper) previous).setRequest(next); + break; + } + + // Advance to the next request in the chain + previous = current; + current = ((ServletRequestWrapper) current).getRequest(); + } + } + + + /** + * Unwrap the response if we have wrapped it. + */ + private void unwrapResponse() { + if (wrapResponse == null) + return; + + ServletResponse previous = null; + ServletResponse current = outerResponse; + while (current != null) { + // If we run into the container response we are done + if (current instanceof ServletResponseImpl) + break; + + // Remove the current response if it is our wrapper + if (current == wrapResponse) { + ServletResponse next = + ((ServletResponseWrapper) current).getResponse(); + if (previous == null) + outerResponse = next; + else + ((ServletResponseWrapper) previous).setResponse(next); + break; + } + // Advance to the next response in the chain + previous = current; + current = ((ServletResponseWrapper) current).getResponse(); + } + } + + + /** + * Create and return a request wrapper that has been inserted in the + * appropriate spot in the request chain. + */ + private ServletRequest wrapRequest() { + // Locate the request we should insert in front of + ServletRequest previous = null; + ServletRequest current = outerRequest; + while (current != null) { + if (!(current instanceof ServletRequestWrapper)) + break; + if (current instanceof ServletRequestWrapperImpl) + break; + if (current instanceof ServletRequestImpl) + break; + // user-specified + previous = current; + current = ((ServletRequestWrapper) current).getRequest(); + } + // now previous will be a user-specified wrapper, + // and current one of our own wrappers ( deeper in stack ) + // ... current USER_previous USER USER + // previous is null if the top request is ours. + + // Instantiate a new wrapper at this point and insert it in the chain + ServletRequest wrapper = null; + + // Compute a crossContext flag + boolean crossContext = isCrossContext(); + wrapper = + new ServletRequestWrapperImpl((HttpServletRequest) current, + ctx, crossContext); + + if (previous == null) { + // outer becomes the wrapper, includes orig wrapper inside + outerRequest = wrapper; + } else { + // outer remains user-specified sersvlet, delegating to + // our wrapper, which delegates to real request or our wrapper. + ((ServletRequestWrapper) previous).setRequest(wrapper); + } + wrapRequest = wrapper; + return (wrapper); + } + + private boolean isCrossContext() { + boolean crossContext = false; + if ((outerRequest instanceof ServletRequestWrapperImpl) || + (outerRequest instanceof ServletRequestImpl) || + (outerRequest instanceof HttpServletRequest)) { + HttpServletRequest houterRequest = + (HttpServletRequest) outerRequest; + Object contextPath = + houterRequest.getAttribute(INCLUDE_CONTEXT_PATH_ATTR); + if (contextPath == null) { + // Forward + contextPath = houterRequest.getContextPath(); + } + crossContext = !(ctx.getContextPath().equals(contextPath)); + } + return crossContext; + } + + + /** + * Create and return a response wrapper that has been inserted in the + * appropriate spot in the response chain. + * + * Side effect: updates outerResponse, wrapResponse. + * The chain is updated with a wrapper below lowest user wrapper + */ + private ServletResponse wrapResponse() { + // Locate the response we should insert in front of + ServletResponse previous = null; + ServletResponse current = outerResponse; + while (current != null) { + if (!(current instanceof ServletResponseWrapper)) + break; + if (current instanceof ServletResponseImpl) + break; + previous = current; + current = ((ServletResponseWrapper) current).getResponse(); + } + + // Instantiate a new wrapper at this point and insert it in the chain + ServletResponse wrapper = + new ServletResponseIncludeWrapper(current); + + if (previous == null) { + // outer is ours, we can wrap on top + outerResponse = wrapper; + } else { + // outer is user-specified, leave it alone. + // we insert ourself below the lowest user-specified response + ((ServletResponseWrapper) previous).setResponse(wrapper); + } + wrapResponse = wrapper; + return (wrapper); + + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi.java new file mode 100644 index 000000000..2290bb6c5 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi.java @@ -0,0 +1,37 @@ +/* + */ +package org.apache.tomcat.lite.servlet; + +import org.apache.tomcat.lite.http.HttpRequest; + +public class ServletApi { + + public ServletContextImpl newContext() { + return null; + } + + public ServletRequestImpl newRequest(HttpRequest req) { + return null; + } + + + public static ServletApi get() { + Class cls = null; + try { + Class.forName("javax.servlet.http.Part"); + cls = Class.forName("org.apache.tomcat.lite.servlet.ServletApi30"); + } catch (Throwable t) { + try { + cls = Class.forName("org.apache.tomcat.lite.servlet.ServletApi25"); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Can't load servlet api", e); + } + } + try { + return (ServletApi) cls.newInstance(); + } catch (Throwable e) { + throw new RuntimeException("Can't load servlet api", e); + } + } + +} \ No newline at end of file diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi25.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi25.java new file mode 100644 index 000000000..1e2fe5334 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi25.java @@ -0,0 +1,19 @@ +/* + */ +package org.apache.tomcat.lite.servlet; + +import org.apache.tomcat.lite.http.HttpRequest; + +public class ServletApi25 extends ServletApi { + public ServletContextImpl newContext() { + return new ServletContextImpl() { + + }; + } + + public ServletRequestImpl newRequest(HttpRequest req) { + return new ServletRequestImpl(req) { + + }; + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java new file mode 100644 index 000000000..684b85c7e --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java @@ -0,0 +1,386 @@ +/* + */ +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.naming.NamingException; +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.http.Part; + +import org.apache.tomcat.InstanceManager; +import org.apache.tomcat.integration.ObjectManager; +import org.apache.tomcat.lite.http.HttpRequest; + +public class ServletApi30 extends ServletApi { + public ServletContextImpl newContext() { + return new ServletContextImpl() { + protected void initEngineDefaults() throws ServletException { + super.initEngineDefaults(); + setAttribute(InstanceManager.class.getName(), + new LiteInstanceManager(getObjectManager())); + } + @Override + public Dynamic addFilter(String filterName, String className) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setData(filterName, className, new HashMap()); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + @Override + public Dynamic addFilter(String filterName, Filter filter) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilter(filter); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + @Override + public Dynamic addFilter(String filterName, + Class filterClass) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, null, new HashMap()); + fc.setFilterClass(filterClass); + filters.put(filterName, fc); + return new DynamicFilterRegistration(fc); + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, String className) { + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Servlet servlet) { + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String servletName, Class servletClass) { + return null; + } + + + @Override + public Set getDefaultSessionTrackingModes() { + return null; + } + + + @Override + public Set getEffectiveSessionTrackingModes() { + return null; + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) { + return null; + } + + @Override + public Map getFilterRegistrations() { + return null; + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + return null; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) { + return null; + } + + @Override + public Map getServletRegistrations() { + return null; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() { + return null; + } + + @Override + public void setSessionTrackingModes( + EnumSet sessionTrackingModes) + throws IllegalStateException, IllegalArgumentException { + } + + public int getMajorVersion() { + return 3; + } + + public int getMinorVersion() { + return 0; + } + + }; + } + + public ServletRequestImpl newRequest(HttpRequest req) { + return new ServletRequestImpl(req) { + + @Override + public Part getPart(String name) { + return null; + } + + @Override + public Collection getParts() throws IOException, + ServletException { + return null; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public AsyncContext startAsync() { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, + ServletResponse servletResponse) { + return null; + } + + }; + } + + private final class LiteInstanceManager implements InstanceManager { + private ObjectManager om; + + public LiteInstanceManager(ObjectManager objectManager) { + this.om = objectManager; + } + + @Override + public void destroyInstance(Object o) + throws IllegalAccessException, + InvocationTargetException { + } + + @Override + public Object newInstance(String className) + throws IllegalAccessException, + InvocationTargetException, NamingException, + InstantiationException, + ClassNotFoundException { + return om.get(className); + } + + @Override + public Object newInstance(String fqcn, + ClassLoader classLoader) + throws IllegalAccessException, + InvocationTargetException, NamingException, + InstantiationException, + ClassNotFoundException { + return om.get(fqcn); + } + + @Override + public void newInstance(Object o) + throws IllegalAccessException, + InvocationTargetException, NamingException { + om.bind(o.getClass().getName(), o); + } + } + + public static class DynamicFilterRegistration implements Dynamic { + FilterConfigImpl fcfg; + + public DynamicFilterRegistration( + org.apache.tomcat.lite.servlet.FilterConfigImpl fc) { + } + + @Override + public void addMappingForServletNames(EnumSet dispatcherTypes, + boolean isMatchAfter, + String... servletNames) { + if (fcfg.ctx.startDone) { + // Use the context method instead of the servlet API to + // add mappings after context init. + throw new IllegalStateException(); + } + ArrayList dispatchers = new ArrayList(); + for (DispatcherType dt: dispatcherTypes) { + dispatchers.add(dt.name()); + } + for (String servletName: servletNames) { + fcfg.ctx.getFilterMapper().addMapping(fcfg.getFilterName(), + null, servletName, (String[]) dispatchers.toArray(), isMatchAfter); + } + } + + @Override + public void addMappingForUrlPatterns(EnumSet dispatcherTypes, + boolean isMatchAfter, + String... urlPatterns) { + if (fcfg.ctx.startDone) { + // Use the context method instead of the servlet API to + // add mappings after context init. + throw new IllegalStateException(); + } + ArrayList dispatchers = new ArrayList(); + for (DispatcherType dt: dispatcherTypes) { + dispatchers.add(dt.name()); + } + for (String url: urlPatterns) { + fcfg.ctx.getFilterMapper().addMapping(fcfg.getFilterName(), + url, null, (String[]) dispatchers.toArray(), isMatchAfter); + } + } + + @Override + public boolean setInitParameter(String name, String value) + throws IllegalArgumentException, IllegalStateException { + return fcfg.ctx.setInitParameter(fcfg.ctx, fcfg.initParams, + name, value); + } + + @Override + public Set setInitParameters(Map initParameters) + throws IllegalArgumentException, IllegalStateException { + return ServletContextImpl.setInitParameters(fcfg.ctx, fcfg.initParams, + initParameters); + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + throws IllegalStateException { + fcfg.asyncSupported = isAsyncSupported; + } + + @Override + public Collection getServletNameMappings() { + return null; + } + + @Override + public Collection getUrlPatternMappings() { + return null; + } + + @Override + public String getClassName() { + return null; + } + + @Override + public String getInitParameter(String name) { + return null; + } + + @Override + public Map getInitParameters() { + return null; + } + + @Override + public String getName() { + return null; + } + } + + class ServletDynamicRegistration implements Dynamic { + ServletConfigImpl sc; + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + throws IllegalStateException { + sc.asyncSupported = isAsyncSupported; + } + + @Override + public boolean setInitParameter(String name, String value) + throws IllegalArgumentException, IllegalStateException { + return sc.setInitParameter(name, value); + } + + @Override + public Set setInitParameters(Map initParameters) + throws IllegalArgumentException, IllegalStateException { + return setInitParameters(initParameters); + } + + @Override + public void addMappingForServletNames( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... servletNames) { + } + + @Override + public void addMappingForUrlPatterns( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... urlPatterns) { + } + + @Override + public Collection getServletNameMappings() { + return null; + } + + @Override + public Collection getUrlPatternMappings() { + return null; + } + + @Override + public String getClassName() { + return null; + } + + @Override + public String getInitParameter(String name) { + return null; + } + + @Override + public Map getInitParameters() { + return null; + } + + @Override + public String getName() { + return null; + } + + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java new file mode 100644 index 000000000..7a0d5aa9d --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java @@ -0,0 +1,863 @@ +/* + * Copyright 1999-2002,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.SingleThreadModel; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tomcat.lite.http.HttpChannel; +import org.apache.tomcat.lite.http.HttpRequest; +import org.apache.tomcat.lite.http.HttpResponse; +import org.apache.tomcat.lite.http.MappingData; +import org.apache.tomcat.servlets.jsp.BaseJspLoader; +import org.apache.tomcat.servlets.util.Enumerator; + +/** + * Based on Wrapper. + * + * Standard implementation of the Wrapper interface that represents + * an individual servlet definition. No child Containers are allowed, and + * the parent Container must be a Context. + * + * @author Craig R. McClanahan + * @author Remy Maucherat + */ +@SuppressWarnings("deprecation") +public class ServletConfigImpl implements ServletConfig, HttpChannel.HttpService { + + protected boolean asyncSupported; + + private static Logger log= + Logger.getLogger(ServletConfigImpl.class.getName()); + + private static final String[] DEFAULT_SERVLET_METHODS = new String[] { + "GET", "HEAD", "POST" }; + + // TODO: refactor all 'stm' to separate class (not implemented) + // public static final String SINGLE_THREADED_PROXY = + // "org.apache.tomcat.servlets.jsp.SingleThreadedProxyServlet"; + + protected String description; + protected Map initParams = new HashMap(); + protected String servletName; + protected String servletClassName; + protected String jspFile; + protected int loadOnStartup = -1; + protected String runAs; + + protected Map securityRoleRef = new HashMap(); // roleName -> [roleLink] + + /** + * The date and time at which this servlet will become available (in + * milliseconds since the epoch), or zero if the servlet is available. + * If this value equals Long.MAX_VALUE, the unavailability of this + * servlet is considered permanent. + */ + private transient long available = 0L; + + private ServletContextImpl ctx; + + /** + * The (single) initialized instance of this servlet. + */ + private transient Servlet instance = null; + + /** + * Are we unloading our servlet instance at the moment? + */ + private transient boolean unloading = false; + + private Class servletClass = null; + + // Support for SingleThreaded + /** + * The count of allocations that are currently active (even if they + * are for the same instance, as will be true on a non-STM servlet). + */ + private transient int countAllocated = 0; + + private transient boolean singleThreadModel = false; + /** + * Stack containing the STM instances. + */ + private transient Stack instancePool = null; + + + // Statistics + private transient long loadTime=0; + private transient int classLoadTime=0; + + public AtomicLong processingTime = new AtomicLong(); + public AtomicInteger maxTime = new AtomicInteger(); + public AtomicInteger requestCount = new AtomicInteger(); + public AtomicInteger errorCount = new AtomicInteger(); + + // ------------------------------------------------------------- Properties + public ServletConfigImpl(ServletContextImpl ctx, String name, + String classname) { + this.servletName = name; + this.servletClassName = classname; + this.ctx = ctx; + ctx.lite.notifyAdd(this); + } + + /** + * Return the available date/time for this servlet, in milliseconds since + * the epoch. If this date/time is Long.MAX_VALUE, it is considered to mean + * that unavailability is permanent and any request for this servlet will return + * an SC_NOT_FOUND error. If this date/time is in the future, any request for + * this servlet will return an SC_SERVICE_UNAVAILABLE error. If it is zero, + * the servlet is currently available. + */ + public long getAvailable() { + return (this.available); + } + + /** + * Set the available date/time for this servlet, in milliseconds since the + * epoch. If this date/time is Long.MAX_VALUE, it is considered to mean + * that unavailability is permanent and any request for this servlet will return + * an SC_NOT_FOUND error. If this date/time is in the future, any request for + * this servlet will return an SC_SERVICE_UNAVAILABLE error. + * + * @param available The new available date/time + */ + public void setAvailable(long available) { + + long oldAvailable = this.available; + if (available > System.currentTimeMillis()) + this.available = available; + else + this.available = 0L; + + } + + + /** + * Return the number of active allocations of this servlet, even if they + * are all for the same instance (as will be true for servlets that do + * not implement SingleThreadModel. + */ + public int getCountAllocated() { + return (this.countAllocated); + } + + /** + * Return the jsp-file setting for this servlet. + */ + public String getJspFile() { + return jspFile; + } + + public void setJspFile(String s) { + this.jspFile = s; + } + + /** + * Return the load-on-startup order value (negative value means + * load on first call). + */ + public int getLoadOnStartup() { + return loadOnStartup; + } + + /** + * Return the fully qualified servlet class name for this servlet. + */ + public String getServletClass() { + return servletClassName; + } + + /** + * Is this servlet currently unavailable? + */ + public boolean isUnavailable() { + if (available == 0L) + return (false); + else if (available <= System.currentTimeMillis()) { + available = 0L; + return (false); + } else + return (true); + + } + + + /** + * Gets the names of the methods supported by the underlying servlet. + * + * This is the same set of methods included in the Allow response header + * in response to an OPTIONS request method processed by the underlying + * servlet. + * + * @return Array of names of the methods supported by the underlying + * servlet + * @throws IOException + */ + public String[] getServletMethods() throws ServletException, IOException { + + Class servletClazz = loadServlet().getClass(); + if (!javax.servlet.http.HttpServlet.class.isAssignableFrom( + servletClazz)) { + return DEFAULT_SERVLET_METHODS; + } + + HashSet allow = new HashSet(); + allow.add("TRACE"); + allow.add("OPTIONS"); + + Method[] methods = getAllDeclaredMethods(servletClazz); + for (int i=0; methods != null && inull + * to mark this servlet as permanently unavailable + */ + public void unavailable(UnavailableException unavailable) { + getServletContext().log("UnavailableException:" + getServletName()); + if (unavailable == null) + setAvailable(Long.MAX_VALUE); + else if (unavailable.isPermanent()) + setAvailable(Long.MAX_VALUE); + else { + int unavailableSeconds = unavailable.getUnavailableSeconds(); + if (unavailableSeconds <= 0) + unavailableSeconds = 60; // Arbitrary default + setAvailable(System.currentTimeMillis() + + (unavailableSeconds * 1000L)); + } + + } + + + /** + * Unload all initialized instances of this servlet, after calling the + * destroy() method for each instance. This can be used, + * for example, prior to shutting down the entire servlet engine, or + * prior to reloading all of the classes from the Loader associated with + * our Loader's repository. + * + * @exception ServletException if an exception is thrown by the + * destroy() method + */ + public synchronized void unload() throws ServletException { + setAvailable(Long.MAX_VALUE); + + // Nothing to do if we have never loaded the instance + if (!singleThreadModel && (instance == null)) + return; + unloading = true; + + // Loaf a while if the current instance is allocated + // (possibly more than once if non-STM) + if (countAllocated > 0) { + int nRetries = 0; + long delay = ctx.getUnloadDelay() / 20; + while ((nRetries < 21) && (countAllocated > 0)) { + if ((nRetries % 10) == 0) { + log.info("Servlet.unload() timeout " + + countAllocated); + } + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + ; + } + nRetries++; + } + } + + ClassLoader oldCtxClassLoader = + Thread.currentThread().getContextClassLoader(); + if (instance != null) { + ClassLoader classLoader = instance.getClass().getClassLoader(); + + PrintStream out = System.out; + // Call the servlet destroy() method + try { + Thread.currentThread().setContextClassLoader(classLoader); + instance.destroy(); + } catch (Throwable t) { + instance = null; + //instancePool = null; + unloading = false; + throw new ServletException("Servlet.destroy() " + + getServletName(), t); + } finally { + // restore the context ClassLoader + Thread.currentThread().setContextClassLoader(oldCtxClassLoader); + } + + // Deregister the destroyed instance + instance = null; + } + if (singleThreadModel && (instancePool != null)) { + try { + ClassLoader classLoader = ctx.getClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader); + while (!instancePool.isEmpty()) { + ((Servlet) instancePool.pop()).destroy(); + } + } catch (Throwable t) { + instancePool = null; + unloading = false; + throw new ServletException("Servlet.destroy() " + getServletName(), t); + } finally { + // restore the context ClassLoader + Thread.currentThread().setContextClassLoader + (oldCtxClassLoader); + } + instancePool = null; + } + + singleThreadModel = false; + + unloading = false; + } + + + /** + * Return the initialization parameter value for the specified name, + * if any; otherwise return null. + * + * @param name Name of the initialization parameter to retrieve + */ + public String getInitParameter(String name) { + return initParams.get(name); + } + + + /** + * Return the set of initialization parameter names defined for this + * servlet. If none are defined, an empty Enumeration is returned. + */ + public Enumeration getInitParameterNames() { + synchronized (initParams) { + return (new Enumerator(initParams.keySet())); + } + } + + + /** + * Return the servlet context with which this servlet is associated. + */ + public ServletContext getServletContext() { + return ctx; + } + + + /** + * Return the name of this servlet. + */ + public String getServletName() { + return servletName; + } + + private Method[] getAllDeclaredMethods(Class c) { + + if (c.equals(javax.servlet.http.HttpServlet.class)) { + return null; + } + + Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass()); + + Method[] thisMethods = c.getDeclaredMethods(); + if (thisMethods == null) { + return parentMethods; + } + + if ((parentMethods != null) && (parentMethods.length > 0)) { + Method[] allMethods = + new Method[parentMethods.length + thisMethods.length]; + System.arraycopy(parentMethods, 0, allMethods, 0, + parentMethods.length); + System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, + thisMethods.length); + + thisMethods = allMethods; + } + + return thisMethods; + } + + /** Specify the instance. Avoids the class lookup, disables unloading. + * Use for embedded case, or to control the allocation. + * + * @param servlet + */ + public void setServlet(Servlet servlet) { + instance = servlet; + ctx.getObjectManager().bind("Servlet:" + + ctx.getContextPath() + ":" + getServletName(), + this); + } + + public String getSecurityRoleRef(String role) { + return (String)securityRoleRef.get(role); + } + + public void setSecurityRoleRef(Map securityRoles) { + this.securityRoleRef = securityRoles; + } + + public void setConfig(Map initParams) { + this.initParams = initParams; + } + + public void setLoadOnStartup(int loadOnStartup) { + this.loadOnStartup = loadOnStartup; + } + + public Set addMapping(String... urlPatterns) { + if (ctx.startDone) { + // Use the context method instead of the servlet API to + // add mappings after context init. + throw new IllegalStateException(); + } + Set failed = new HashSet(); + for (String url: urlPatterns) { + if (url == null) { + throw new IllegalArgumentException(); + } + if (ctx.contextConfig.servletMapping.get(url) != null) { + failed.add(url); + } else { + ctx.contextConfig.servletMapping.put(url, getServletName()); + ctx.addMapping(url, this); + } + } + return failed; + } + + public boolean setInitParameter(String name, String value) + throws IllegalArgumentException, IllegalStateException { + return ServletContextImpl.setInitParameter(ctx, initParams, + name, value); + } + + public Set setInitParameters(Map initParameters) + throws IllegalArgumentException, IllegalStateException { + return ServletContextImpl.setInitParameters(ctx, initParams, + initParameters); + } + + public void setServletClass(Class servletClass2) { + servletClass = servletClass2; + } + + @Override + public void service(HttpRequest httpReq, HttpResponse httpRes) + throws IOException { + + HttpChannel client = httpReq.getHttpChannel(); + ServletRequestImpl req = TomcatLite.getFacade(client.getRequest()); + ServletResponseImpl res = req.getResponse(); + + // TODO + } + + /** Coyote / mapper adapter. Result of the mapper. + * + * This replaces the valve chain, the path is: + * 1. coyote calls mapper -> result Adapter + * 2. service is called. Additional filters are set on the wrapper. + * @param mapRes + */ + void serviceServlet(ServletContextImpl ctx, + ServletRequestImpl req, + ServletResponseImpl res, + ServletConfigImpl servletConfig, + MappingData mapRes) + throws IOException { + + requestCount.incrementAndGet(); + Servlet servlet = null; + long t0 = System.currentTimeMillis(); + + try { + if (servletConfig.isUnavailable()) { + handleUnavailable(res, servletConfig); + return; + } + try { + servlet = servletConfig.allocate(); + } catch(ServletException ex) { + handleUnavailable(res, servletConfig); + return; + } + WebappFilterMapper filterMap = ctx.getFilterMapper(); + FilterChainImpl chain = + filterMap.createFilterChain(req, servletConfig, servlet); + + try { + if (chain == null) { + if (servlet != null) { + servlet.service(req, res); + } else { + System.err.println("No servlet " + req.getRequestURI()); + res.sendError(404); + } + } else { + chain.doFilter(req, res); + } + } catch(UnavailableException ex) { + servletConfig.unavailable(ex); + handleUnavailable(res, servletConfig); + return; + } + + // servlet completed without exception. Check status + int status = res.getStatus(); + if (status != 200 && !res.isCommitted()) { + String statusPage = ctx.findStatusPage(status); + + if (statusPage != null) { + ctx.handleStatusPage(req, res, status, statusPage); + } else { + // Send a default message body. + // TODO: maybe other mechanism to customize default. + res.defaultStatusPage(status, res.getMessage()); + } + } + + } catch (Throwable t) { + errorCount.incrementAndGet(); + ctx.handleError(req, res, t); + } finally { + int time = (int) (System.currentTimeMillis() - t0); + if (time > maxTime.get()) { + maxTime.set(time); + } + processingTime.addAndGet(time); + if (servlet != null) { // single-thread servlet + servletConfig.deallocate(servlet); + } + } + } + + private void handleUnavailable(ServletResponseImpl response, + ServletConfigImpl servletConfig) + throws IOException { + long available = servletConfig.getAvailable(); + if ((available > 0L) && (available < Long.MAX_VALUE)) + response.setDateHeader("Retry-After", available); + // TODO: handle via error pages ! + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, + "Service unavailable"); + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java new file mode 100644 index 000000000..78e96cce0 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java @@ -0,0 +1,1563 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.lite.servlet; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.Filter; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; + +import org.apache.tomcat.integration.ObjectManager; +import org.apache.tomcat.lite.http.BaseMapper; +import org.apache.tomcat.lite.io.FileConnectorJavaIo; +import org.apache.tomcat.servlets.config.ConfigLoader; +import org.apache.tomcat.servlets.config.ServletContextConfig; +import org.apache.tomcat.servlets.config.ServletContextConfig.FilterData; +import org.apache.tomcat.servlets.config.ServletContextConfig.FilterMappingData; +import org.apache.tomcat.servlets.config.ServletContextConfig.ServletData; +import org.apache.tomcat.servlets.session.UserSessionManager; +import org.apache.tomcat.servlets.util.Enumerator; +import org.apache.tomcat.servlets.util.MimeMap; +import org.apache.tomcat.servlets.util.RequestUtil; +import org.apache.tomcat.servlets.util.UrlUtils; + + +/** + * Context - initialized from web.xml or using APIs. + * + * Initialization order: + * + * - add all listeners + * - add all filters + * - add all servlets + * + * - session parameters + * - + * + * @author Craig R. McClanahan + * @author Remy Maucherat + * @version $Revision: 793797 $ $Date: 2009-07-13 22:38:02 -0700 (Mon, 13 Jul 2009) $ + */ + +public abstract class ServletContextImpl implements ServletContext { + + /** + * Empty collection to serve as the basis for empty enumerations. + */ + private static final ArrayList empty = new ArrayList(); + + private Logger log; + + /** + * Base path - the directory root of the webapp + */ + protected String basePath = null; + + protected String contextPath; + + // All config from web.xml and other sources + protected ServletContextConfig contextConfig = null; + + // Includes the default values, will be merged with contextConfig + MimeMap contentTypes = new MimeMap(); + + /** + * The context attributes for this context. + */ + protected transient Map attributes = new HashMap(); + + protected transient ArrayList lifecycleListeners = + new ArrayList(); + + protected UserSessionManager manager; + + HashMap filters = new HashMap(); + + HashMap servlets = new HashMap(); + + /** Mapper for filters. + */ + protected WebappFilterMapper webappFilterMapper; + + /** Internal mapper for request dispatcher, must have all + * context mappings. + */ + protected BaseMapper.ContextMapping mapper; + + // From localeEncodingMapping + Locale2Charset charsetMapper = new Locale2Charset(); + + TomcatLite lite; + + // Can use a separate injection config and framework + ObjectManager om; + + private String hostname; + + boolean initDone = false; + + boolean startDone = false; + + String defaultServlet = "org.apache.tomcat.servlets.file.WebdavServlet"; + String jspWildcardServlet = "org.apache.tomcat.servlets.jsp.WildcardTemplateServlet"; + String userSessionManager = "org.apache.tomcat.servlets.session.SimpleSessionManager"; + String jspcServlet = "org.apache.tomcat.servlets.jspc.JspcServlet"; + + // ------------------------------------------------- ServletContext Methods + public ServletContextImpl() { + } + + public void setTomcat(TomcatLite facade) { + this.lite = facade; + } + + /** + * Registry/framework interface associated with the context. + * Also available as a context attribute. + * @return + */ + public ObjectManager getObjectManager() { + if (om == null) { + om = lite.getObjectManager(); + } + return om; + } + + public void setObjectManager(ObjectManager om) { + this.om = om; + } + + public Locale2Charset getCharsetMapper() { + return charsetMapper; + } + + /** + * Set the context path, starting with "/" - "/" for ROOT + * @param path + */ + public void setContextPath(String path) { + this.contextPath = path; + log = Logger.getLogger("webapp" + path.replace('/', '.')); + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public String getHostname() { + return hostname; + } + + /** The directory where this app is based. May be null. + * + * @param basePath + */ + public void setBasePath(String basePath) { + this.basePath = basePath; + } + + public ServletContextConfig getContextConfig() { + return contextConfig; + } + + /** The directory where this app is based. + * + * @param basePath + */ + public String getBasePath() { + return basePath; + } + + public String getEncodedPath() { + return null; + } + + + public boolean getCookies() { + return false; + } + + + public ServletContext getServletContext() { + return this; + } + + public List getListeners() { + return lifecycleListeners; + } + + public void addListener(T listener) { + lifecycleListeners.add(listener); + } + + public void removeListener(EventListener listener) { + lifecycleListeners.remove(listener); + } + + public void addListener(Class listenerClass) { + } + + public void addListener(String className) { + } + + public T createListener(Class c) + throws ServletException { + return null; + } + + + public void declareRoles(String... roleNames) { + } + + public int getEffectiveMajorVersion() { + return 0; + } + + public int getEffectiveMinorVersion() { + return 0; + } + + + public Logger getLogger() { + return log; + } + + public long getUnloadDelay() { + return 0; + } + + public ServletConfigImpl getServletConfig(String jsp_servlet_name) { + return (ServletConfigImpl)servlets.get(jsp_servlet_name); + } + + public Map getServletConfigs() { + return servlets; + } + + /** + * Add a servlet to the context. + * Called from processWebAppData() + * + * @param servletConfig + */ + public void addServletConfig(ServletConfigImpl servletConfig) { + servlets.put(servletConfig.getServletName(), servletConfig); + } + + public boolean getPrivileged() { + return false; + } + + + public Map getFilters() { + return filters; + } + + + protected boolean getCrossContext() { + return true; + } + + public void addMimeType(String ext, String type) { + contentTypes.addContentType(ext, type); + } + + public WebappFilterMapper getFilterMapper() { + if (webappFilterMapper == null) { + Object customMapper = getObjectManager().get(WebappFilterMapper.class); + if (customMapper == null) { + webappFilterMapper = new WebappFilterMapper(); + } else { + webappFilterMapper = (WebappFilterMapper) customMapper; + } + webappFilterMapper.setServletContext(this); + } + + return webappFilterMapper ; + } + + public FilterConfigImpl getFilter(String name) { + return (FilterConfigImpl)filters.get(name); + } + + /** + * Return the value of the specified context attribute, if any; + * otherwise return null. + * + * @param name Name of the context attribute to return + */ + public Object getAttribute(String name) { + if ("ObjectManager".equals(name)) { + return getObjectManager(); + } + if ("context-listeners".equals(name)) { + return lifecycleListeners; + } + return (attributes.get(name)); + } + + /** + * Return an enumeration of the names of the context attributes + * associated with this context. + */ + public Enumeration getAttributeNames() { + return new Enumerator(attributes.keySet(), true); + } + + /** + * Return a ServletContext object that corresponds to a + * specified URI on the server. This method allows servlets to gain + * access to the context for various parts of the server, and as needed + * obtain RequestDispatcher objects or resources from the + * context. The given path must be absolute (beginning with a "/"), + * and is interpreted based on our virtual host's document root. + * + * @param uri Absolute URI of a resource on the server + */ + public ServletContext getContext(String uri) { + // TODO: support real uri ( http://host/path ) + // Validate the format of the specified argument + if ((uri == null) || (!uri.startsWith("/"))) + return (null); + + ServletContextImpl child = null; + try { + child = lite.getContext(this, uri); + } catch (IOException e) { + } catch (ServletException e) { + } + + if (child == null) + return (null); + + if (this.getCrossContext()) { + // If crossContext is enabled, can always return the context + return child.getServletContext(); + } else if (child == this) { + // Can still return the current context + return this.getServletContext(); + } else { + // Nothing to return + return (null); + } + } + + + /** + * Return the main path associated with this context. + */ + public String getContextPath() { + return contextPath; + } + + + /** + * Return the value of the specified initialization parameter, or + * null if this parameter does not exist. + * + * @param name Name of the initialization parameter to retrieve + */ + public String getInitParameter(final String name) { + return ((String) contextConfig.contextParam.get(name)); + } + + + /** + * Return the names of the context's initialization parameters, or an + * empty enumeration if the context has no initialization parameters. + */ + public Enumeration getInitParameterNames() { + return (new Enumerator(contextConfig.contextParam.keySet())); + } + + public void setContextParams(Map newParams) { + contextConfig.contextParam = (HashMap) newParams; + } + + /** + * Return the major version of the Java Servlet API that we implement. + */ + public int getMajorVersion() { + return 2; + } + + + /** + * Return the minor version of the Java Servlet API that we implement. + */ + public int getMinorVersion() { + return 5; + } + + + /** + * Return the MIME type of the specified file, or null if + * the MIME type cannot be determined. + * + * @param file Filename for which to identify a MIME type + */ + public String getMimeType(String file) { + return contentTypes.getMimeType(file); + } + + /** + * Return the real path for a given virtual path, if possible; otherwise + * return null. + * + * @param path The path to the desired resource + */ + public String getRealPath(String path) { + if (path == null) { + return null; + } + + File file = new File(basePath, path); + return (file.getAbsolutePath()); + } + + /** + * Return a RequestDispatcher object that acts as a + * wrapper for the named servlet. + * + * @param name Name of the servlet for which a dispatcher is requested + */ + public RequestDispatcher getNamedDispatcher(String name) { + if (name == null) return null; + ServletConfigImpl wrapper = + (ServletConfigImpl) this.getServletConfig(name); + if (wrapper == null) return null; + + return new RequestDispatcherImpl(wrapper, name); + } + + + /** + * Return a RequestDispatcher instance that acts as a + * wrapper for the resource at the given path. The path must begin + * with a "/" and is interpreted as relative to the current context root. + * + * @param path The path to the desired resource. + */ + public RequestDispatcher getRequestDispatcher(String path) { + if (path == null) return null; + + if (!path.startsWith("/")) + throw new IllegalArgumentException(path); + + path = UrlUtils.normalize(path); + if (path == null) return (null); + + + return new RequestDispatcherImpl(this, path); + } + + public RequestDispatcher getRequestDispatcher(String path, + int type, + String dispatcherPath) { + RequestDispatcher dispatcher = getRequestDispatcher(path); + //((RequestDispatcherImpl)dispatcher); + return dispatcher; + } + + ThreadLocal requestDispatcherStack = new ThreadLocal(); + + protected ClassLoader classLoader; + + private String classPath; + + +// protected RequestDispatcherImpl getRequestDispatcher() { +// ArrayList/**/ list = +// (ArrayList)requestDispatcherStack.get(); +// if (list == null) { +// list = new ArrayList(); +// requestDispatcherStack.set(list); +// } +// +// +// return null; +// } + + public void resetDispatcherStack() { + + } + + /** + * Return the URL to the resource that is mapped to a specified path. + * The path must begin with a "/" and is interpreted as relative to the + * current context root. + * + * @param path The path to the desired resource + * + * @exception MalformedURLException if the path is not given + * in the correct form + */ + public URL getResource(String path) + throws MalformedURLException { + + if (path == null || !path.startsWith("/")) { + throw new MalformedURLException("getResource() " + path); + } + + path = UrlUtils.normalize(path); + if (path == null) + return (null); + + String libPath = "/WEB-INF/lib/"; + if ((path.startsWith(libPath)) && (path.endsWith(".jar"))) { + File jarFile = null; + jarFile = new File(basePath, path); + if (jarFile.exists()) { + return jarFile.toURL(); + } else { + return null; + } + } else { + File resFile = new File(basePath + path); + if (resFile.exists()) { + return resFile.toURL(); + } + } + + return (null); + + } + + /** + * Return the requested resource as an InputStream. The + * path must be specified according to the rules described under + * getResource. If no such resource can be identified, + * return null. + * + * @param path The path to the desired resource. + */ + public InputStream getResourceAsStream(String path) { + + path = UrlUtils.normalize(path); + if (path == null) + return (null); + + File resFile = new File(basePath + path); + if (!resFile.exists()) + return null; + + try { + return new FileInputStream(resFile); + } catch (FileNotFoundException e) { + return null; + } + + } + + + /** + * Return a Set containing the resource paths of resources member of the + * specified collection. Each path will be a String starting with + * a "/" character. The returned set is immutable. + * + * @param path Collection path + */ + public Set getResourcePaths(String path) { + + // Validate the path argument + if (path == null) { + return null; + } + if (!path.startsWith("/")) { + throw new IllegalArgumentException("getResourcePaths() " + path); + } + + path = UrlUtils.normalize(path); + if (path == null) + return (null); + + File f = new File(basePath + path); + File[] files = f.listFiles(); + if (files == null) return null; + if (!path.endsWith("/")) { + path = path + "/"; + } + + HashSet result = new HashSet(); + for (int i=0; i < files.length; i++) { + if (files[i].isDirectory() ) { + result.add(path + files[i].getName() + "/"); + } else { + result.add(path + files[i].getName()); + } + } + return result; + } + + + + /** + * Return the name and version of the servlet container. + */ + public String getServerInfo() { + return "Apache Tomcat Lite"; + } + + /** + * @deprecated As of Java Servlet API 2.1, with no direct replacement. + */ + public Servlet getServlet(String name) { + return (null); + } + + + /** + * Return the display name of this web application. + */ + public String getServletContextName() { + return contextConfig.displayName; + } + + + /** + * @deprecated As of Java Servlet API 2.1, with no direct replacement. + */ + public Enumeration getServletNames() { + return (new Enumerator(empty)); + } + + + /** + * @deprecated As of Java Servlet API 2.1, with no direct replacement. + */ + public Enumeration getServlets() { + return (new Enumerator(empty)); + } + + + /** + * Writes the specified message to a servlet log file. + * + * @param message Message to be written + */ + public void log(String message) { + this.getLogger().info(message); + } + + + /** + * Writes the specified exception and message to a servlet log file. + * + * @param exception Exception to be reported + * @param message Message to be written + * + * @deprecated As of Java Servlet API 2.1, use + * log(String, Throwable) instead + */ + public void log(Exception exception, String message) { + this.getLogger().log(Level.INFO, message, exception); + } + + + /** + * Writes the specified message and exception to a servlet log file. + * + * @param message Message to be written + * @param throwable Exception to be reported + */ + public void log(String message, Throwable throwable) { + this.getLogger().log(Level.INFO, message, throwable); + } + + /** + * Remove the context attribute with the specified name, if any. + * + * @param name Name of the context attribute to be removed + */ + public void removeAttribute(String name) { + + Object value = null; + boolean found = false; + + // Remove the specified attribute + // Check for read only attribute + found = attributes.containsKey(name); + if (found) { + value = attributes.get(name); + attributes.remove(name); + } else { + return; + } + + // Notify interested application event listeners + List listeners = this.getListeners(); + if (listeners.size() == 0) + return; + ServletContextAttributeEvent event = null; + for (int i = 0; i < listeners.size(); i++) { + if (!(listeners.get(i) instanceof ServletContextAttributeListener)) + continue; + ServletContextAttributeListener listener = + (ServletContextAttributeListener) listeners.get(i); + try { + if (event == null) { + event = new ServletContextAttributeEvent(this.getServletContext(), + name, value); + + } + listener.attributeRemoved(event); + } catch (Throwable t) { + // FIXME - should we do anything besides log these? + log("ServletContextAttributeListener", t); + } + } + } + + + /** + * Bind the specified value with the specified context attribute name, + * replacing any existing value for that name. + * + * @param name Attribute name to be bound + * @param value New attribute value to be bound + */ + public void setAttribute(String name, Object value) { + // Name cannot be null + if (name == null) + throw new IllegalArgumentException + ("name == null"); + + // Null value is the same as removeAttribute() + if (value == null) { + removeAttribute(name); + return; + } + + Object oldValue = null; + boolean replaced = false; + + // Add or replace the specified attribute + synchronized (attributes) { + // Check for read only attribute + oldValue = attributes.get(name); + if (oldValue != null) + replaced = true; + attributes.put(name, value); + } + + // Notify interested application event listeners + List listeners = this.getListeners(); + if (listeners.size() == 0) + return; + ServletContextAttributeEvent event = null; + for (int i = 0; i < listeners.size(); i++) { + if (!(listeners.get(i) instanceof ServletContextAttributeListener)) + continue; + ServletContextAttributeListener listener = + (ServletContextAttributeListener) listeners.get(i); + try { + if (event == null) { + if (replaced) + event = + new ServletContextAttributeEvent(this.getServletContext(), + name, oldValue); + else + event = + new ServletContextAttributeEvent(this.getServletContext(), + name, value); + + } + if (replaced) { + listener.attributeReplaced(event); + } else { + listener.attributeAdded(event); + } + } catch (Throwable t) { + // FIXME - should we do anything besides log these? + log("ServletContextAttributeListener error", t); + } + } + + } + + /** + * Clear all application-created attributes. + */ + void clearAttributes() { + // Create list of attributes to be removed + ArrayList list = new ArrayList(); + synchronized (attributes) { + Iterator iter = attributes.keySet().iterator(); + while (iter.hasNext()) { + list.add(iter.next()); + } + } + + // Remove application originated attributes + // (read only attributes will be left in place) + Iterator keys = list.iterator(); + while (keys.hasNext()) { + String key = (String) keys.next(); + removeAttribute(key); + } + } + + public void initFilters() throws ServletException { + Iterator fI = getFilters().values().iterator(); + while (fI.hasNext()) { + FilterConfigImpl fc = (FilterConfigImpl)fI.next(); + try { + fc.getFilter(); // will triger init() + } catch (Throwable e) { + log.log(Level.WARNING, getContextPath() + " Filter.init() " + + fc.getFilterName(), e); + } + + } + } + + public void initServlets() throws ServletException { + Iterator fI = getServletConfigs().values().iterator(); + Map/*>*/ onStartup = + new TreeMap/*>*/(); + while (fI.hasNext()) { + ServletConfigImpl fc = (ServletConfigImpl)fI.next(); + if (fc.getLoadOnStartup() > 0 ) { + Integer i = new Integer(fc.getLoadOnStartup()); + List/**/ old = (List)onStartup.get(i); + if (old == null) { + old = new ArrayList/**/(); + onStartup.put(i, old); + } + old.add(fc); + } + } + Iterator keys = onStartup.keySet().iterator(); + while (keys.hasNext()) { + Integer key = (Integer)keys.next(); + List/**/ servlets = (List)onStartup.get(key); + Iterator servletsI = servlets.iterator(); + while (servletsI.hasNext()) { + ServletConfigImpl fc = (ServletConfigImpl) servletsI.next(); + try { + fc.loadServlet(); + } catch (Throwable e) { + log.log(Level.WARNING, "Error initializing " + fc.getServletName(), e); + } + } + } + } + + public void initListeners() throws ServletException { + Iterator fI = contextConfig.listenerClass.iterator(); + while (fI.hasNext()) { + String listenerClass = (String)fI.next(); + try { + Object l = newInstance(listenerClass, "EventListener-" + listenerClass); + lifecycleListeners.add((EventListener) l); + } catch (Throwable e) { + log.log(Level.WARNING, "Error initializing listener " + listenerClass, e); + } + } + } + + public Object newInstance(String className, String bindName) throws ServletException { + try { + Class cls = getClassLoader().loadClass(className); + Object l = cls.newInstance(); + + // Injections and JMX support + if (bindName != null) { + getObjectManager().bind("Context=" + getContextPath() + "," + + bindName, l); + } + return l; + } catch (Throwable e) { + log.log(Level.WARNING, "Error initializing listener " + className, e); + throw new ServletException(e); + } + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void addMapping(String path, String name) { + ServletConfigImpl wrapper = getServletConfig(name); + addMapping(path, wrapper); + } + + public void addMapping(String path, ServletConfigImpl wrapper) { + getEngine().getDispatcher().addWrapper(getContextMap(), path, + wrapper); + } + + + + public void setWelcomeFiles(String[] name) { + getContextMap().welcomeResources = name; + } + + public String[] getWelcomeFiles() { + return getContextMap().welcomeResources; + } + + public BaseMapper.ContextMapping getContextMap() { + if (mapper == null) { + mapper = new BaseMapper.ContextMapping(); + mapper.name = this.getContextPath(); + mapper.welcomeResources = getWelcomeFiles(); + } + return mapper; + } + + public void setSessionTimeout(int to) { + getManager().setSessionTimeout(to); + } + + /** + * Initialize the context from the parsed config. + * + * Note that WebAppData is serializable. + */ + public void processWebAppData(ServletContextConfig d) throws ServletException { + this.contextConfig = d; + + for (String k: d.mimeMapping.keySet()) { + addMimeType(k, d.mimeMapping.get(k)); + } + + String[] wFiles = (String[])d.welcomeFileList.toArray(new String[0]); + if (wFiles.length == 0) { + wFiles = new String[] {"index.html" }; + } + if (basePath != null) { + // TODO: configurable filesystem + getContextMap().resources = + new FileConnectorJavaIo(new File(getBasePath())); + } + setWelcomeFiles(wFiles); + + Iterator i2 = d.filters.values().iterator(); + while (i2.hasNext()) { + FilterData fd = (FilterData)i2.next(); + addFilter(fd.name, fd.className, fd.initParams); + } + + Iterator i3 = d.servlets.values().iterator(); + while (i3.hasNext()) { + ServletData sd = (ServletData) i3.next(); + // jsp-file + if (sd.className == null) { + if (sd.jspFile == null) { + log.log(Level.WARNING, "Missing servlet class for " + sd.name); + continue; + } + } + + ServletConfigImpl sw = + new ServletConfigImpl(this, sd.name, sd.className); + sw.setConfig(sd.initParams); + sw.setJspFile(sd.jspFile); + sw.setLoadOnStartup(sd.loadOnStartup); + //sw.setRunAs(sd.runAs); + sw.setSecurityRoleRef(sd.securityRoleRef); + + addServletConfig(sw); + } + + for (String k: d.servletMapping.keySet()) { + addMapping(k, d.servletMapping.get(k)); + } + + Iterator i5 = d.filterMappings.iterator(); + while (i5.hasNext()) { + FilterMappingData k = (FilterMappingData) i5.next(); + String[] disp = new String[k.dispatcher.size()]; + if (k.urlPattern != null) { + addFilterMapping(k.urlPattern, + k.filterName, + (String[])k.dispatcher.toArray(disp)); + } + if (k.servletName != null) { + addFilterServletMapping(k.servletName, + k.filterName, + (String[])k.dispatcher.toArray(disp)); + } + } + + for (String n: d.localeEncodingMapping.keySet()) { + getCharsetMapper().addCharsetMapping(n, + d.localeEncodingMapping.get(n)); + } + } + + public void addServlet(String servletName, String servletClass, + String jspFile, Map params) { + ServletConfigImpl sc = new ServletConfigImpl(this, servletName, + servletClass); + sc.setJspFile(jspFile); + sc.setConfig(params); + addServletConfig(sc); + } + + public ServletConfigImpl add(String servletName, Servlet servlet) { + ServletConfigImpl sc = new ServletConfigImpl(this, servletName, null); + sc.setServlet(servlet); + addServletConfig(sc); + return sc; + } + + public void addServletSec(String serlvetName, String runAs, Map roles) { + // TODO + } + + + + public void addFilterMapping(String path, String filterName, + String[] dispatcher) { + getFilterMapper().addMapping(filterName, + path, null, dispatcher, true); + + } + + public void addFilterServletMapping(String servlet, + String filterName, + String[] dispatcher) { + getFilterMapper().addMapping(filterName, + null, servlet, + dispatcher, true); + } + + /** + * Called from TomcatLite.init(), required before start. + * + * Will initialize defaults and load web.xml unless webAppData is + * already set and recent. No other processing is done except reading + * the config - you can add or alter it before start() is called. + * + * @throws ServletException + */ + public ServletContextImpl loadConfig() throws ServletException { + long t0 = System.currentTimeMillis(); + if (initDone) { + return this; + } + initDone = true; + // Load global init params from the facade + initEngineDefaults(); + + initTempDir(); + + if (getBasePath() == null || getBasePath().length() == 0) { + // dynamic context - no files or base path + contextConfig = new ServletContextConfig(); + } else { + ConfigLoader cfgLoader = null; + + initClassLoader(getBasePath()); + + if (lite.getDeployListener() != null) { + cfgLoader = (ConfigLoader) newInstance(lite.getDeployListener(), null); + } else { + cfgLoader = new ConfigLoader(); + } + + contextConfig = cfgLoader.loadConfig(getBasePath()); + if (contextConfig == null) { + String msg = "No configuration found, run " + + "'java -jar WarDeploy.jar " + getBasePath() + "'"; + System.err.println(msg); + throw new ServletException(msg); + } + + processWebAppData(contextConfig); + } + // if not defined yet: + addDefaultServlets(); + + long t1 = System.currentTimeMillis(); + + // At this point all config is loaded. Contexts are not yet init() + // - this will happen on start. + log.fine("Context.loadConfig() " + contextPath + " " + (t1-t0)); + return this; + } + + + protected void initTempDir() throws ServletException { + // We need a base path - at least for temp files, req. by spec + if (basePath == null) { + basePath = ("/".equals(contextPath)) ? + lite.getWork().getAbsolutePath() + "/ROOT" : + lite.getWork().getAbsolutePath() + contextPath; + } + + File f = new File(basePath + "/WEB-INF/tmp"); + f.mkdirs(); + setAttribute("javax.servlet.context.tempdir", f); + } + + /** + * Static file handler ( default ) + * *.jsp support + * + */ + protected void addDefaultServlets() throws ServletException { + if (servlets.get("default") == null) { + ServletConfigImpl fileS = new ServletConfigImpl(this, + "default", defaultServlet); + addServletConfig(fileS); + addMapping("/", fileS); + } + + // *.jsp support + if (servlets.get("jspwildcard") == null) { + ServletConfigImpl fileS = new ServletConfigImpl(this, + "jspwildcard", jspWildcardServlet); + fileS.initParams.put("mapper", JspLoader.class.getName()); + addServletConfig(fileS); + addMapping("*.jsp", fileS); + } + + ServletConfigImpl jspcS = new ServletConfigImpl(this, + "jspc", jspcServlet); + addServletConfig(jspcS); + } + + protected void initEngineDefaults() throws ServletException { + + // TODO: make this customizable, avoid loading it on startup + // Set the class name as default in the addon support + for (String sname: lite.ctxDefaultInitParam.keySet()) { + String path = lite.ctxDefaultInitParam.get(sname); + contextConfig.contextParam.put(sname, path); + } + + for (String sname: lite.preloadServlets.keySet()) { + String sclass = lite.preloadServlets.get(sname); + ServletConfigImpl fileS = new ServletConfigImpl(this, sname, sclass); + addServletConfig(fileS); + } + + for (String sname: lite.preloadMappings.keySet()) { + String path = lite.preloadMappings.get(sname); + ServletConfigImpl servletConfig = getServletConfig(sname); + addMapping(path, servletConfig); + } + } + + + private void addClasspathLib(ArrayList res, File directory) { + + if (!directory.isDirectory() || !directory.exists() + || !directory.canRead()) { + return; + } + + File[] jars = directory.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".jar"); + } + }); + + for (int j = 0; j < jars.length; j++) { + try { + URL url = jars[j].toURL(); + res.add(url); + } catch (MalformedURLException e) { + } + } + } + + private void addClasspathDir(ArrayList res, File classesDir) { + + if (classesDir.isDirectory() && classesDir.exists() && + classesDir.canRead()) { + try { + URL url = classesDir.toURL(); + res.add(url); + } catch (MalformedURLException e) { + } + } + } + + + public void start() throws ServletException { + if (startDone) { + return; + } + String base = getBasePath(); + + // JMX should know about us ( TODO: is it too early ? ) + lite.notifyAdd(this); + + initListeners(); + + List listeners = this.getListeners(); + ServletContextEvent event = null; + for (int i = 0; i < listeners.size(); i++) { + if (!(listeners.get(i) instanceof ServletContextListener)) + continue; + ServletContextListener listener = + (ServletContextListener) listeners.get(i); + if (event == null) { + event = new ServletContextEvent(this); + } + try { + // May add servlets/filters + listener.contextInitialized(event); + } catch (Throwable t) { + log.log(Level.WARNING, "Context.init() contextInitialized() error:", t); + } + } + + + initFilters(); + initServlets(); + + startDone = true; + } + + public String getClassPath() { + return classPath; + } + + private void initClassLoader(String base) { + ArrayList urls = new ArrayList(); + + addClasspathDir(urls, new File(base + "/WEB-INF/classes")); + addClasspathDir(urls, new File(base + "/WEB-INF/tmp")); + addClasspathLib(urls, new File(base + "/WEB-INF/lib")); + + URL[] urlsA = new URL[urls.size()]; + urls.toArray(urlsA); + StringBuilder cp = new StringBuilder(); + + for (URL cpUrl : urlsA) { + cp.append(":").append(cpUrl.getFile()); + } + classPath = cp.toString(); + URLClassLoader parentLoader = + getEngine().getContextParentLoader(); + // create a class loader. + // TODO: reimplement special 'deploy' dirs + + /* + Repository ctxRepo = new Repository(); + ctxRepo.setParentClassLoader(parentLoader); + ctxRepo.addURL(urlsA); + repository = ctxRepo; + */ + + classLoader = new URLClassLoader(urlsA, parentLoader); + } + + public UserSessionManager getManager() { + if (manager == null) { + try { + manager = (UserSessionManager) getObjectManager().get( + UserSessionManager.class); + } catch (Throwable t) { + t.printStackTrace(); + manager = null; + } + if (manager == null) { + try { + manager = (UserSessionManager) newInstance(userSessionManager, "UserSessionManager"); + } catch (ServletException e) { + log.log(Level.SEVERE, "Error creating session manager", e); + return null; + } + } + manager.setContext(this); + if (contextConfig.sessionTimeout > 0 ) { + manager.setSessionTimeout(contextConfig.sessionTimeout); + } + } + return manager; + } + + + // TODO: configurable ? init-params + public String getSessionCookieName() { + return "JSESSIONID"; + } + + + + public void destroy() throws ServletException { + // destroy filters + Iterator fI = filters.values().iterator(); + while(fI.hasNext()) { + FilterConfigImpl fc = (FilterConfigImpl) fI.next(); + try { + fc.getFilter().destroy(); + } catch (Exception e) { + e.printStackTrace(); + } + } + // destroy servlets + fI = servlets.values().iterator(); + while(fI.hasNext()) { + ServletConfigImpl fc = (ServletConfigImpl) fI.next(); + try { + fc.unload(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public TomcatLite getEngine() { + return lite; + } + + public String toString() { + return "{Context_path: " + getContextPath() + + ", dir=" + getBasePath() + "}"; + } + + public String findStatusPage(int status) { + if (contextConfig.errorPageCode.size() == 0) { + return null; + } + if (status == 200) { + return null; + } + + return (String) contextConfig.errorPageCode.get(Integer.toString(status)); + } + + public void handleStatusPage(ServletRequestImpl req, + ServletResponseImpl res, + int status, + String statusPage) { + String message = RequestUtil.filter(res.getMessage()); + if (message == null) + message = ""; + setErrorAttributes(req, status, message); + dispatchError(req, res, statusPage); + } + + protected void setErrorAttributes(ServletRequestImpl req, + int status, + String message) { + req.setAttribute("javax.servlet.error.status_code", + new Integer(status)); + if (req.getWrapper() != null) { + req.setAttribute("javax.servlet.error.servlet_name", + req.getWrapper().servletName); + } + req.setAttribute("javax.servlet.error.request_uri", + req.getRequestURI()); + req.setAttribute("javax.servlet.error.message", + message); + + } + + public void handleError(ServletRequestImpl req, + ServletResponseImpl res, + Throwable t) { + Throwable realError = t; + if (realError instanceof ServletException) { + realError = ((ServletException) realError).getRootCause(); + if (realError == null) { + realError = t; + } + } + //if (realError instanceof ClientAbortException ) { + + String errorPage = findErrorPage(t); + if ((errorPage == null) && (realError != t)) { + errorPage = findErrorPage(realError); + } + + if (errorPage != null) { + setErrorAttributes(req, 500, t.getMessage()); + req.setAttribute("javax.servlet.error.exception", realError); + req.setAttribute("javax.servlet.error.exception_type", + realError.getClass()); + dispatchError(req, res, errorPage); + } else { + log("Unhandled error", t); + if (t instanceof ServletException && + ((ServletException)t).getRootCause() != null) { + log("RootCause:", ((ServletException)t).getRootCause()); + } + if (res.getStatus() < 500) { + res.setStatus(500); + } + } + } + + protected void dispatchError(ServletRequestImpl req, + ServletResponseImpl res, + String errorPage) { + RequestDispatcher rd = + getRequestDispatcher(errorPage); + try { + // will clean up the buffer + rd.forward(req, res); + return; // handled + } catch (ServletException e) { + // TODO + } catch (IOException e) { + // TODO + } + } + + protected String findErrorPage(Throwable exception) { + if (contextConfig.errorPageException.size() == 0) { + return null; + } + if (exception == null) + return (null); + Class clazz = exception.getClass(); + String name = clazz.getName(); + while (!Object.class.equals(clazz)) { + String page = (String)contextConfig.errorPageException.get(name); + if (page != null) + return (page); + clazz = clazz.getSuperclass(); + if (clazz == null) + break; + name = clazz.getName(); + } + return (null); + + } + + public void addFilter(String filterName, String filterClass, + Map params) { + FilterConfigImpl fc = new FilterConfigImpl(this); + fc.setData(filterName, filterClass, params); + filters.put(filterName, fc); + } + + // That's tricky - this filter will have no name. We need to generate one + // because our code relies on names. + AtomicInteger autoName = new AtomicInteger(); + + public T createFilter(Class c) throws ServletException { + FilterConfigImpl fc = new FilterConfigImpl(this); + String filterName = "_tomcat_auto_filter_" + autoName.incrementAndGet(); + fc.setData(filterName, null, new HashMap()); + fc.setFilterClass(c); + filters.put(filterName, fc); + + try { + return (T) fc.createFilter(); + } catch (ClassCastException e) { + throw new ServletException(e); + } catch (ClassNotFoundException e) { + throw new ServletException(e); + } catch (IllegalAccessException e) { + throw new ServletException(e); + } catch (InstantiationException e) { + throw new ServletException(e); + } + } + + public T createServlet(Class c) throws ServletException { + String filterName = "_tomcat_auto_servlet_" + autoName.incrementAndGet(); + ServletConfigImpl fc = new ServletConfigImpl(this, filterName, null); + fc.setServletClass(c); + servlets.put(filterName, fc); + + try { + return (T) fc.newInstance(); + } catch (ClassCastException e) { + throw new ServletException(e); + } catch (IOException e) { + throw new ServletException(e); + } + } + + public boolean setInitParameter(String name, String value) { + HashMap params = contextConfig.contextParam; + return setInitParameter(this, params, name, value); + } + + static Set setInitParameters(ServletContextImpl ctx, + Map params, + Map initParameters) + throws IllegalArgumentException, IllegalStateException { + if (ctx.startDone) { + throw new IllegalStateException(); + } + Set result = new HashSet(); + for (String name: initParameters.keySet()) { + String value = initParameters.get(name); + if (name == null || value == null) { + throw new IllegalArgumentException(); + } + if (!setInitParameter(ctx, params, name, value)) { + result.add(name); + } + } + return result; + } + + /** + * true if the context initialization parameter with the given name and value was set successfully on this ServletContext, and false if it was not set because this ServletContext already contains a context initialization parameter with a matching name + * Throws: + * java.lang.IllegalStateException - if this ServletContext has already been initialized + */ + static boolean setInitParameter(ServletContextImpl ctx, Map params, + String name, String value) { + if (name == null || value == null) { + throw new IllegalArgumentException(); + } + if (ctx.startDone) { + throw new IllegalStateException(); + } + String oldValue = params.get(name); + if (oldValue != null) { + return false; + } else { + params.put(name, value); + return true; + } + } + +} + diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletInputStreamImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletInputStreamImpl.java new file mode 100644 index 000000000..8f06314aa --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletInputStreamImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; + +import javax.servlet.ServletInputStream; + +import org.apache.tomcat.lite.io.IOInputStream; + + +/** + * Wrapper around BufferInputStream. + */ +public final class ServletInputStreamImpl extends ServletInputStream { + private IOInputStream ib; + + public ServletInputStreamImpl(IOInputStream ib) { + this.ib = ib; + } + + public long skip(long n) + throws IOException { + return ib.skip(n); + } + + public void mark(int readAheadLimit) + { + ib.mark(readAheadLimit); + } + + + public void reset() + throws IOException { + ib.reset(); + } + + public int read() + throws IOException { + return ib.read(); + } + + public int available() throws IOException { + return ib.available(); + } + + public int read(final byte[] b) throws IOException { + return ib.read(b, 0, b.length); + } + + + public int read(final byte[] b, final int off, final int len) + throws IOException { + return ib.read(b, off, len); + } + + + public int readLine(byte[] b, int off, int len) throws IOException { + return ib.readLine(b, off, len); + } + + public void close() throws IOException { + // no call to super.close ! + ib.close(); + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletOutputStreamImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletOutputStreamImpl.java new file mode 100644 index 000000000..e65717441 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletOutputStreamImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; + +import org.apache.tomcat.lite.io.IOOutputStream; + + +/** + * Coyote implementation of the servlet output stream. + * + * @author Costin Manolache + * @author Remy Maucherat + */ +public final class ServletOutputStreamImpl extends ServletOutputStream { + + private IOOutputStream ob; + + public ServletOutputStreamImpl(IOOutputStream ob) { + this.ob = ob; + } + + public void write(int i) + throws IOException { + ob.write(i); + } + + + public void write(byte[] b) + throws IOException { + write(b, 0, b.length); + } + + + public void write(byte[] b, int off, int len) + throws IOException { + ob.write(b, off, len); + } + + + /** + * Will send the buffer to the client. + */ + public void flush() + throws IOException { + ob.flush(); + } + + public void close() + throws IOException { + ob.close(); + } + + public void print(String s) + throws IOException { + ob.print(s); + } +} + diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletReaderImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletReaderImpl.java new file mode 100644 index 000000000..109a51c3e --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletReaderImpl.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + +import java.io.BufferedReader; +import java.io.IOException; + + + +/** + * Coyote implementation of the buffred reader. + * + * @author Remy Maucherat + */ +public final class ServletReaderImpl extends BufferedReader { + + private BufferedReader ib; + + public ServletReaderImpl(BufferedReader ib) { + super(ib, 1); + this.ib = ib; + } + + public void close() + throws IOException { + ib.close(); + } + + + public int read() + throws IOException { + return ib.read(); + } + + + public int read(char[] cbuf) + throws IOException { + return ib.read(cbuf, 0, cbuf.length); + } + + + public int read(char[] cbuf, int off, int len) + throws IOException { + return ib.read(cbuf, off, len); + } + + + public long skip(long n) + throws IOException { + return ib.skip(n); + } + + + public boolean ready() + throws IOException { + return ib.ready(); + } + + + public boolean markSupported() { + return true; + } + + + public void mark(int readAheadLimit) + throws IOException { + ib.mark(readAheadLimit); + } + + + public void reset() + throws IOException { + ib.reset(); + } + + + // TODO: move the readLine functionality to base coyote IO + public String readLine() + throws IOException { + return ib.readLine(); + + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java new file mode 100644 index 000000000..1a1f740c8 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java @@ -0,0 +1,2004 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tomcat.lite.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.logging.Level; + +import javax.security.auth.Subject; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.tomcat.lite.http.HttpRequest; +import org.apache.tomcat.lite.http.MappingData; +import org.apache.tomcat.lite.http.MultiMap; +import org.apache.tomcat.lite.http.ServerCookie; +import org.apache.tomcat.lite.http.MultiMap.Entry; +import org.apache.tomcat.lite.io.BBuffer; +import org.apache.tomcat.lite.io.CBuffer; +import org.apache.tomcat.lite.io.FastHttpDateFormat; +import org.apache.tomcat.servlets.session.UserSessionManager; +import org.apache.tomcat.servlets.util.Enumerator; +import org.apache.tomcat.servlets.util.LocaleParser; +import org.apache.tomcat.servlets.util.RequestUtil; + + +/** + * + * Wrapper object for the request. + * + * @author Remy Maucherat + * @author Craig R. McClanahan + */ +public abstract class ServletRequestImpl implements HttpServletRequest { + + /** + * The request attribute under which we store the array of X509Certificate + * objects representing the certificate chain presented by our client, + * if any. + */ + public static final String CERTIFICATES_ATTR = + "javax.servlet.request.X509Certificate"; + + /** + * The request attribute under which we store the name of the cipher suite + * being used on an SSL connection (as an object of type + * java.lang.String). + */ + public static final String CIPHER_SUITE_ATTR = + "javax.servlet.request.cipher_suite"; + + /** + * Request dispatcher state. + */ + public static final String DISPATCHER_TYPE_ATTR = + "org.apache.catalina.core.DISPATCHER_TYPE"; + + /** + * Request dispatcher path. + */ + public static final String DISPATCHER_REQUEST_PATH_ATTR = + "org.apache.catalina.core.DISPATCHER_REQUEST_PATH"; + + /** + * The servlet context attribute under which we store the class path + * for our application class loader (as an object of type String), + * delimited with the appropriate path delimiter for this platform. + */ + public static final String CLASS_PATH_ATTR = + "org.apache.catalina.jsp_classpath"; + + + /** + * The request attribute under which we forward a Java exception + * (as an object of type Throwable) to an error page. + */ + public static final String EXCEPTION_ATTR = + "javax.servlet.error.exception"; + + + /** + * The request attribute under which we forward the request URI + * (as an object of type String) of the page on which an error occurred. + */ + public static final String EXCEPTION_PAGE_ATTR = + "javax.servlet.error.request_uri"; + + + /** + * The request attribute under which we forward a Java exception type + * (as an object of type Class) to an error page. + */ + public static final String EXCEPTION_TYPE_ATTR = + "javax.servlet.error.exception_type"; + + + /** + * The request attribute under which we forward an HTTP status message + * (as an object of type STring) to an error page. + */ + public static final String ERROR_MESSAGE_ATTR = + "javax.servlet.error.message"; + + + /** + * The request attribute under which we expose the value of the + * <jsp-file> value associated with this servlet, + * if any. + */ + public static final String JSP_FILE_ATTR = + "org.apache.catalina.jsp_file"; + + + /** + * The request attribute under which we store the key size being used for + * this SSL connection (as an object of type java.lang.Integer). + */ + public static final String KEY_SIZE_ATTR = + "javax.servlet.request.key_size"; + + /** + * The request attribute under which we store the session id being used + * for this SSL connection (as an object of type java.lang.String). + */ + public static final String SSL_SESSION_ID_ATTR = + "javax.servlet.request.ssl_session"; + + /** + * The request attribute under which we forward a servlet name to + * an error page. + */ + public static final String SERVLET_NAME_ATTR = + "javax.servlet.error.servlet_name"; + + + /** + * The name of the cookie used to pass the session identifier back + * and forth with the client. + */ + public static final String SESSION_COOKIE_NAME = "JSESSIONID"; + + + /** + * The name of the path parameter used to pass the session identifier + * back and forth with the client. + */ + public static final String SESSION_PARAMETER_NAME = "jsessionid"; + + + /** + * The request attribute under which we forward an HTTP status code + * (as an object of type Integer) to an error page. + */ + public static final String STATUS_CODE_ATTR = + "javax.servlet.error.status_code"; + + + /** + * The subject under which the AccessControlContext is running. + */ + public static final String SUBJECT_ATTR = + "javax.security.auth.subject"; + + + /** + * The servlet context attribute under which we store a temporary + * working directory (as an object of type File) for use by servlets + * within this web application. + */ + public static final String WORK_DIR_ATTR = + "javax.servlet.context.tempdir"; + + protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); + + + /** + * The default Locale if none are specified. + */ + protected static Locale defaultLocale = Locale.getDefault(); + + // ApplicationFilterFactory. What's the use ??? + private static Integer REQUEST_INTEGER = new Integer(8); + + /** + * The match string for identifying a session ID parameter. + */ + private static final String match = ";" + SESSION_PARAMETER_NAME + "="; + + /** + * The set of cookies associated with this Request. + */ + protected Cookie[] cookies = null; + + + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + * + * Notice that because SimpleDateFormat is not thread-safe, we can't + * declare formats[] as a static variable. + */ + protected SimpleDateFormat formats[] = null; + + + /** + * The attributes associated with this Request, keyed by attribute name. + */ + protected HashMap attributes = new HashMap(); + + /** + * The preferred Locales assocaited with this Request. + */ + protected ArrayList locales = new ArrayList(); + + + /** + * Authentication type. + */ + protected String authType = null; + + /** + * User principal. + */ + protected Principal userPrincipal = null; + + + /** + * The Subject associated with the current AccessControllerContext + */ + protected transient Subject subject = null; + + + /** + * The current dispatcher type. + */ + protected Object dispatcherType = null; + + /** + * ServletInputStream. + */ + protected ServletInputStreamImpl inputStream; + + + /** + * Using stream flag. + */ + protected boolean usingInputStream = false; + + + /** + * Using writer flag. + */ + protected boolean usingReader = false; + + + /** + * Session parsed flag. + */ + protected boolean sessionParsed = false; + + + /** + * Secure flag. + */ + protected boolean secure = false; + + + /** + * The currently active session for this request. + */ + protected HttpSession session = null; + + + /** + * The current request dispatcher path. + */ + protected Object requestDispatcherPath = null; + + + /** + * Was the requested session ID received in a cookie? + */ + protected boolean requestedSessionCookie = false; + + + /** + * The requested session ID (if any) for this request. + */ + protected String requestedSessionId = null; + + + /** + * Was the requested session ID received in a URL? + */ + protected boolean requestedSessionURL = false; + + + /** + * Parse locales. + */ + protected boolean localesParsed = false; + + + /** + * Associated context. + */ + private ServletContextImpl context = null; + + + + // --------------------------------------------------------- Public Methods + + /** + * Filter chain associated with the request. + */ + protected FilterChainImpl filterChain = new FilterChainImpl(); + + + // -------------------------------------------------------- Request Methods + + /** + * The response with which this request is associated. + */ + protected ServletResponseImpl response = new ServletResponseImpl(); + + /** + * URI byte to char converter (not recycled). + */ + // protected B2CConverter URIConverter = null; + + /** + * Associated wrapper. + */ + protected ServletConfigImpl wrapper = null; + + private HttpRequest httpRequest; + + /** New IO/buffer model + * @param req + */ + //protected Http11Connection con; + + ServletRequestImpl(HttpRequest req) { + setHttpRequest(req); + response.setRequest(this); + } + + +// /** +// * Return the Host within which this Request is being processed. +// */ +// public Host getHost() { +// if (getContext() == null) +// return null; +// return (Host) getContext().getParent(); +// //return ((Host) mappingData.host); +// } +// +// +// /** +// * Set the Host within which this Request is being processed. This +// * must be called as soon as the appropriate Host is identified, and +// * before the Request is passed to a context. +// * +// * @param host The newly associated Host +// */ +// public void setHost(Host host) { +// mappingData.host = host; +// } + + /** + * Add a Header to the set of Headers associated with this Request. + * + * @param name The new header name + * @param value The new header value + */ + public void addHeader(String name, String value) { + // Not used + } + + /** + * Add a Locale to the set of preferred Locales for this Request. The + * first added Locale will be the first one returned by getLocales(). + * + * @param locale The new preferred Locale + */ + public void addLocale(Locale locale) { + locales.add(locale); + } + + + /** + * Add a parameter name and corresponding set of values to this Request. + * (This is used when restoring the original request on a form based + * login). + * + * @param name Name of this request parameter + * @param values Corresponding values for this request parameter + */ + public void addParameter(String name, String values) { + httpRequest.addParameter(name, values); + } + + /** + * Clear the collection of Headers associated with this Request. + */ + public void clearHeaders() { + // Not used + } + + /** + * Clear the collection of Locales associated with this Request. + */ + public void clearLocales() { + locales.clear(); + } + + /** + * Clear the collection of parameters associated with this Request. + */ + public void clearParameters() { + // Not used + } + + + /** + * Create and return a ServletInputStream to read the content + * associated with this Request. + * + * @exception IOException if an input/output error occurs + */ + public ServletInputStream createInputStream() + throws IOException { + return inputStream; + } + + /** + * Perform whatever actions are required to flush and close the input + * stream or reader, in a single operation. + * + * @exception IOException if an input/output error occurs + */ + public void finishRequest() throws IOException { + // The reader and input stream don't need to be closed + } + + + /** + * Return the specified request attribute if it exists; otherwise, return + * null. + * + * @param name Name of the request attribute to return + */ + public Object getAttribute(String name) { + + if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) { + return (dispatcherType == null) + ? REQUEST_INTEGER + : dispatcherType; + } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) { + return (requestDispatcherPath == null) + ? getMappingData().requestPath.toString() + : requestDispatcherPath.toString(); + } + + Object attr=attributes.get(name); + + if(attr!=null) + return(attr); + +// attr = reqB.getAttribute(name); +// if(attr != null) +// return attr; +// if( isSSLAttribute(name) ) { +// reqB.action(ActionCode.ACTION_REQ_SSL_ATTRIBUTE, +// reqB); +// attr = reqB.getAttribute(ServletRequestImpl.CERTIFICATES_ATTR); +// if( attr != null) { +// attributes.put(ServletRequestImpl.CERTIFICATES_ATTR, attr); +// } +// attr = reqB.getAttribute(ServletRequestImpl.CIPHER_SUITE_ATTR); +// if(attr != null) { +// attributes.put(ServletRequestImpl.CIPHER_SUITE_ATTR, attr); +// } +// attr = reqB.getAttribute(ServletRequestImpl.KEY_SIZE_ATTR); +// if(attr != null) { +// attributes.put(ServletRequestImpl.KEY_SIZE_ATTR, attr); +// } +// attr = reqB.getAttribute(ServletRequestImpl.SSL_SESSION_ID_ATTR); +// if(attr != null) { +// attributes.put(ServletRequestImpl.SSL_SESSION_ID_ATTR, attr); +// } +// attr = attributes.get(name); +// } + return attr; + } + + /** + * Return the names of all request attributes for this Request, or an + * empty Enumeration if there are none. + */ + public Enumeration getAttributeNames() { + if (isSecure()) { + getAttribute(ServletRequestImpl.CERTIFICATES_ATTR); + } + return new Enumerator(attributes.keySet(), true); + } + + + /** + * Return the authentication type used for this Request. + */ + public String getAuthType() { + return (authType); + } + + + // ------------------------------------------------- Request Public Methods + + + /** + * Return the character encoding for this Request. + */ + public String getCharacterEncoding() { + return (httpRequest.getCharacterEncoding()); + } + + + /** + * Return the content length for this Request. + */ + public int getContentLength() { + return ((int) httpRequest.getContentLength()); + } + + +// /** +// * Return the object bound with the specified name to the internal notes +// * for this request, or null if no such binding exists. +// * +// * @param name Name of the note to be returned +// */ +// public Object getNote(String name) { +// return (notes.get(name)); +// } +// +// +// /** +// * Return an Iterator containing the String names of all notes bindings +// * that exist for this request. +// */ +// public Iterator getNoteNames() { +// return (notes.keySet().iterator()); +// } +// +// +// /** +// * Remove any object bound to the specified name in the internal notes +// * for this request. +// * +// * @param name Name of the note to be removed +// */ +// public void removeNote(String name) { +// notes.remove(name); +// } +// +// +// /** +// * Bind an object to a specified name in the internal notes associated +// * with this request, replacing any existing binding for this name. +// * +// * @param name Name to which the object should be bound +// * @param value Object to be bound to the specified name +// */ +// public void setNote(String name, Object value) { +// notes.put(name, value); +// } +// + + /** + * Return the content type for this Request. + */ + public String getContentType() { + return (httpRequest.getContentType()); + } + + + /** + * Return the Context within which this Request is being processed. + */ + public ServletContextImpl getContext() { + if (context == null) { + context = (ServletContextImpl) httpRequest.getMappingData().context; + } + return (this.context); + } + + + /** + * Return the portion of the request URI used to select the Context + * of the Request. + */ + public String getContextPath() { + return (getMappingData().contextPath.toString()); + } + + + /** + * Return the set of Cookies received with this Request. + */ + public Cookie[] getCookies() { + if (cookies == null) { + List serverCookies = httpRequest.getServerCookies(); + if (serverCookies.size() == 0) { + return null; + } + cookies = new Cookie[serverCookies.size()]; + for (int i = 0; i < serverCookies.size(); i++) { + ServerCookie scookie = serverCookies.get(i); + try { + // TODO: we could override all methods and + // return recyclable cookies, if we really wanted + Cookie cookie = new Cookie(scookie.getName().toString(), + scookie.getValue().toString()); + cookie.setPath(scookie.getPath().toString()); + cookie.setVersion(scookie.getVersion()); + String domain = scookie.getDomain().toString(); + if (domain != null) { + cookie.setDomain(scookie.getDomain().toString()); + } + cookies[i] = cookie; + } catch(IllegalArgumentException e) { + // Ignore bad cookie + } + } + } + return cookies; + } + + + /** + * Return the value of the specified date header, if any; otherwise + * return -1. + * + * @param name Name of the requested date header + * + * @exception IllegalArgumentException if the specified header value + * cannot be converted to a date + */ + public long getDateHeader(String name) { + + String value = getHeader(name); + if (value == null) + return (-1L); + if (formats == null) { + formats = new SimpleDateFormat[] { + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + formats[0].setTimeZone(GMT_ZONE); + formats[1].setTimeZone(GMT_ZONE); + formats[2].setTimeZone(GMT_ZONE); + } + + // Attempt to convert the date header in a variety of formats + long result = FastHttpDateFormat.parseDate(value, formats); + if (result != (-1L)) { + return result; + } + throw new IllegalArgumentException(value); + + } + + /** + * Get filter chain associated with the request. + */ + public FilterChainImpl getFilterChain() { + return (this.filterChain); + } + + + // ------------------------------------------------- ServletRequest Methods + + /** + * Return the first value of the specified header, if any; otherwise, + * return null + * + * @param name Name of the requested header + */ + public String getHeader(String name) { + return httpRequest.getHeader(name); + } + + /** + * Return the names of all headers received with this request. + */ + public Enumeration getHeaderNames() { + return httpRequest.getMimeHeaders().names(); + } + + + /** + * Return all of the values of the specified header, if any; otherwise, + * return an empty enumeration. + * + * @param name Name of the requested header + */ + public Enumeration getHeaders(String name) { + Entry entry = httpRequest.getMimeHeaders().getEntry(name); + if (entry == null) { + return MultiMap.EMPTY; + } + return new MultiMap.IteratorEnumerator(entry.values.iterator()); + } + + /** + * Return the servlet input stream for this Request. The default + * implementation returns a servlet input stream created by + * createInputStream(). + * + * @exception IllegalStateException if getReader() has + * already been called for this request + * @exception IOException if an input/output error occurs + */ + public ServletInputStream getInputStream() throws IOException { + + if (usingReader) + throw new IllegalStateException + ("usingReader"); + + usingInputStream = true; + return inputStream; + + } + + + /** + * Return the value of the specified header as an integer, or -1 if there + * is no such header for this request. + * + * @param name Name of the requested header + * + * @exception IllegalArgumentException if the specified header value + * cannot be converted to an integer + */ + public int getIntHeader(String name) { + + String value = getHeader(name); + if (value == null) { + return (-1); + } else { + return (Integer.parseInt(value)); + } + + } + + + /** + * Returns the Internet Protocol (IP) address of the interface on + * which the request was received. + */ + public String getLocalAddr(){ + return httpRequest.localAddr().toString(); + } + + + /** + * Return the preferred Locale that the client will accept content in, + * based on the value for the first Accept-Language header + * that was encountered. If the request did not specify a preferred + * language, the server's default Locale is returned. + */ + public Locale getLocale() { + + if (!localesParsed) + parseLocales(); + + if (locales.size() > 0) { + return ((Locale) locales.get(0)); + } else { + return (defaultLocale); + } + + } + + + /** + * Return the set of preferred Locales that the client will accept + * content in, based on the values for any Accept-Language + * headers that were encountered. If the request did not specify a + * preferred language, the server's default Locale is returned. + */ + public Enumeration getLocales() { + + if (!localesParsed) + parseLocales(); + + if (locales.size() > 0) + return (new Enumerator(locales)); + ArrayList results = new ArrayList(); + results.add(defaultLocale); + return (new Enumerator(results)); + + } + + + /** + * Returns the host name of the Internet Protocol (IP) interface on + * which the request was received. + */ + public String getLocalName(){ + return httpRequest.localName().toString(); + } + + + /** + * Returns the Internet Protocol (IP) port number of the interface + * on which the request was received. + */ + public int getLocalPort(){ + return httpRequest.getLocalPort(); + } + + /** + * Return the server port responding to this Request. + */ + public int getServerPort() { + return (httpRequest.getServerPort()); + } + + /** + * Return mapping data. + */ + public MappingData getMappingData() { + return (httpRequest.getMappingData()); + } + + + + /** + * Return the HTTP request method used in this Request. + */ + public String getMethod() { + return httpRequest.method().toString(); + } + + + /** + * Return the value of the specified request parameter, if any; otherwise, + * return null. If there is more than one value defined, + * return only the first one. + * + * @param name Name of the desired request parameter + */ + public String getParameter(String name) { + return httpRequest.getParameter(name); + + } + + + /** + * Returns a Map of the parameters of this request. + * Request parameters are extra information sent with the request. + * For HTTP servlets, parameters are contained in the query string + * or posted form data. + * + * @return A Map containing parameter names as keys + * and parameter values as map values. + */ + public Map getParameterMap() { + return httpRequest.getParameterMap(); + } + + + /** + * Return the names of all defined request parameters for this request. + */ + public Enumeration getParameterNames() { + return httpRequest.getParameterNames(); + + } + + + /** + * Return the defined values for the specified request parameter, if any; + * otherwise, return null. + * + * @param name Name of the desired request parameter + */ + public String[] getParameterValues(String name) { + return httpRequest.getParameterValues(name); + + } + + + /** + * Return the path information associated with this Request. + */ + public String getPathInfo() { + CBuffer pathInfo = getMappingData().pathInfo; + if (pathInfo.length() == 0) { + return null; + } + return (getMappingData().pathInfo.toString()); + } + + + /** + * Return the extra path information for this request, translated + * to a real path. + */ + public String getPathTranslated() { + + if (getContext() == null) + return (null); + + if (getPathInfo() == null) { + return (null); + } else { + return (getContext().getServletContext().getRealPath(getPathInfo())); + } + + } + + /** + * Return the principal that has been authenticated for this Request. + */ + public Principal getPrincipal() { + return (userPrincipal); + } + + /** + * Return the protocol and version used to make this Request. + */ + public String getProtocol() { + return httpRequest.protocol().toString(); + } + + /** + * Return the query string associated with this request. + */ + public String getQueryString() { + String queryString = httpRequest.queryString().toString(); + if (queryString == null || queryString.equals("")) { + return (null); + } else { + return queryString; + } + } + + + /** + * Read the Reader wrapping the input stream for this Request. The + * default implementation wraps a BufferedReader around the + * servlet input stream returned by createInputStream(). + * + * @exception IllegalStateException if getInputStream() + * has already been called for this request + * @exception IOException if an input/output error occurs + */ + public BufferedReader getReader() throws IOException { + + if (usingInputStream) + throw new IllegalStateException + ("usingInputStream"); + + usingReader = true; + return httpRequest.getReader(); + + } + + /** + * Return the real path of the specified virtual path. + * + * @param path Path to be translated + * + * @deprecated As of version 2.1 of the Java Servlet API, use + * ServletContext.getRealPath(). + */ + public String getRealPath(String path) { + + if (getContext() == null) + return (null); + ServletContext servletContext = getContext(); // .getServletContext(); + if (servletContext == null) + return (null); + else { + try { + return (servletContext.getRealPath(path)); + } catch (IllegalArgumentException e) { + return (null); + } + } + + } + + + /** + * Return the remote IP address making this Request. + */ + public String getRemoteAddr() { + return httpRequest.remoteAddr().toString(); + } + + + /** + * Return the remote host name making this Request. + */ + public String getRemoteHost() { + return httpRequest.remoteHost().toString(); + } + + + /** + * Returns the Internet Protocol (IP) source port of the client + * or last proxy that sent the request. + */ + public int getRemotePort(){ + return httpRequest.getRemotePort(); + } + + + /** + * Return the name of the remote user that has been authenticated + * for this Request. + */ + public String getRemoteUser() { + + if (userPrincipal != null) { + return (userPrincipal.getName()); + } else { + return (null); + } + + } + + + /** + * Return the ServletRequest for which this object + * is the facade. This method must be implemented by a subclass. + */ + public HttpServletRequest getRequest() { + return this; + } + + public HttpRequest getHttpRequest() { + return httpRequest; + } + + public void setHttpRequest(HttpRequest req) { + this.httpRequest = req; + inputStream = new ServletInputStreamImpl(req.getBodyInputStream()); + } + + /** + * Return a RequestDispatcher that wraps the resource at the specified + * path, which may be interpreted as relative to the current request path. + * + * @param path Path of the resource to be wrapped + */ + public RequestDispatcher getRequestDispatcher(String path) { + + if (getContext() == null) + return (null); + + // If the path is already context-relative, just pass it through + if (path == null) + return (null); + else if (path.startsWith("/")) + return (getContext().getRequestDispatcher(path)); + + // Convert a request-relative path to a context-relative one + String servletPath = (String) getAttribute(RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR); + if (servletPath == null) + servletPath = getServletPath(); + + // Add the path info, if there is any + String pathInfo = getPathInfo(); + String requestPath = null; + + if (pathInfo == null) { + requestPath = servletPath; + } else { + requestPath = servletPath + pathInfo; + } + + int pos = requestPath.lastIndexOf('/'); + String relative = null; + if (pos >= 0) { + relative = RequestUtil.normalize + (requestPath.substring(0, pos + 1) + path); + } else { + relative = RequestUtil.normalize(requestPath + path); + } + + return (getContext().getRequestDispatcher(relative)); + + } + + + /** + * Return the session identifier included in this request, if any. + */ + public String getRequestedSessionId() { + return (requestedSessionId); + } + + + // ---------------------------------------------------- HttpRequest Methods + + + /** + * Return the request URI for this request. + */ + public String getRequestURI() { + return httpRequest.requestURI().toString(); + } + + /** + * Reconstructs the URL the client used to make the request. + * The returned URL contains a protocol, server name, port + * number, and server path, but it does not include query + * string parameters. + *

+ * Because this method returns a StringBuffer, + * not a String, you can modify the URL easily, + * for example, to append query parameters. + *

+ * This method is useful for creating redirect messages and + * for reporting errors. + * + * @return A StringBuffer object containing the + * reconstructed URL + */ + public StringBuffer getRequestURL() { + + StringBuffer url = new StringBuffer(); + String scheme = getScheme(); + int port = getServerPort(); + if (port < 0) + port = 80; // Work around java.net.URL bug + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + if ((scheme.equals("http") && (port != 80)) + || (scheme.equals("https") && (port != 443))) { + url.append(':'); + url.append(port); + } + url.append(getRequestURI()); + + return (url); + + } + + + /** + * Return the Response with which this Request is associated. + */ + public ServletResponseImpl getResponse() { + return (this.response); + } + + + /** + * Return the scheme used to make this Request. + */ + public String getScheme() { + String scheme = httpRequest.scheme().toString(); + if (scheme == null) { + scheme = (isSecure() ? "https" : "http"); + } + return scheme; + } + + + /** + * Return the server name responding to this Request. + */ + public String getServerName() { + return httpRequest.getServerName(); + } + + + + /** + * Return the portion of the request URI used to select the servlet + * that will process this request. + */ + public String getServletPath() { + return (getMappingData().wrapperPath.toString()); + } + + /** + * Return the input stream associated with this Request. + */ + public InputStream getStream() { + return inputStream; + } + + + /** + * Return the principal that has been authenticated for this Request. + */ + public Principal getUserPrincipal() { + return userPrincipal; + } + + + /** + * Return the Wrapper within which this Request is being processed. + */ + public ServletConfigImpl getWrapper() { + return (this.wrapper); + } + + + /** + * Return true if the session identifier included in this + * request came from a cookie. + */ + public boolean isRequestedSessionIdFromCookie() { + + if (requestedSessionId != null) + return (requestedSessionCookie); + else + return (false); + + } + + + /** + * Return true if the session identifier included in this + * request came from the request URI. + * + * @deprecated As of Version 2.1 of the Java Servlet API, use + * isRequestedSessionIdFromURL() instead. + */ + public boolean isRequestedSessionIdFromUrl() { + return (isRequestedSessionIdFromURL()); + } + + + /** + * Return true if the session identifier included in this + * request came from the request URI. + */ + public boolean isRequestedSessionIdFromURL() { + + if (requestedSessionId != null) + return (requestedSessionURL); + else + return (false); + + } + + + /** + * Return true if the session identifier included in this + * request identifies a valid session. + */ + public boolean isRequestedSessionIdValid() { + + if (requestedSessionId == null) + return (false); + if (getContext() == null) + return (false); + UserSessionManager manager = getContext().getManager(); + if (manager == null) + return (false); + HttpSession session = null; + try { + session = manager.findSession(requestedSessionId); + } catch (IOException e) { + session = null; + } + if ((session != null) && manager.isValid(session)) + return (true); + else + return (false); + + } + + /** + * Was this request received on a secure connection? + */ + public boolean isSecure() { + return (secure); + } + + + /** + * Return true if the authenticated user principal + * possesses the specified role name. + * + * @param role Role name to be validated + */ + public boolean isUserInRole(String role) { + // Have we got an authenticated principal at all? + Principal userPrincipal = getPrincipal(); + if (userPrincipal == null) + return (false); + + // Identify the Realm we will use for checking role assignmenets + if (getContext() == null) + return (false); + + // Check for a role alias defined in a element + if (wrapper != null) { + String realRole = wrapper.getSecurityRoleRef(role); + if (realRole != null) { + role = realRole; + } + } + + if (role.equals(userPrincipal.getName())) { + return true; + } + + // TODO: check !!!! + // Check for a role defined directly as a + return false; + } + + /** + * Release all object references, and initialize instance variables, in + * preparation for reuse of this object. + */ + void recycle() { + + wrapper = null; + + dispatcherType = null; + requestDispatcherPath = null; + + authType = null; + usingInputStream = false; + usingReader = false; + userPrincipal = null; + subject = null; + sessionParsed = false; + locales.clear(); + localesParsed = false; + secure = false; + + attributes.clear(); + //notes.clear(); + cookies = null; + + if (session != null) { + getContext().getManager().endAccess(session); + } + setContext(null); + session = null; + requestedSessionCookie = false; + requestedSessionId = null; + requestedSessionURL = false; + + //getMappingData().recycle(); + // httpRequest.recycle(); + + response.recycle(); + } + + + + /** + * Remove the specified request attribute if it exists. + * + * @param name Name of the request attribute to remove + */ + public void removeAttribute(String name) { + Object value = null; + boolean found = false; + + // Remove the specified attribute + // Check for read only attribute + // requests are per thread so synchronization unnecessary +// if (readOnlyAttributes.containsKey(name)) { +// return; +// } + found = attributes.containsKey(name); + if (found) { + value = attributes.get(name); + attributes.remove(name); + } else { + return; + } + + // Notify interested application event listeners + List listeners = getContext().getListeners(); + if (listeners.size() == 0) + return; + ServletRequestAttributeEvent event = null; + for (int i = 0; i < listeners.size(); i++) { + if (!(listeners.get(i) instanceof ServletRequestAttributeListener)) + continue; + ServletRequestAttributeListener listener = + (ServletRequestAttributeListener) listeners.get(i); + try { + if (event == null) { + event = + new ServletRequestAttributeEvent(getContext().getServletContext(), + getRequest(), name, value); + } + listener.attributeRemoved(event); + } catch (Throwable t) { + getContext().getLogger().log(Level.WARNING, "ServletRequestAttributeListner.attributeRemoved()", t); + // Error valve will pick this execption up and display it to user + attributes.put( ServletRequestImpl.EXCEPTION_ATTR, t ); + } + } + } + + + /** + * Set the specified request attribute to the specified value. + * + * @param name Name of the request attribute to set + * @param value The associated value + */ + public void setAttribute(String name, Object value) { + + // Name cannot be null + if (name == null) + throw new IllegalArgumentException + ("setAttribute() name == null"); + + // Null value is the same as removeAttribute() + if (value == null) { + removeAttribute(name); + return; + } + + if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) { + dispatcherType = value; + return; + } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) { + requestDispatcherPath = value; + return; + } + + Object oldValue = null; + boolean replaced = false; + + // Add or replace the specified attribute + // Check for read only attribute + // requests are per thread so synchronization unnecessary +// if (readOnlyAttributes.containsKey(name)) { +// return; +// } + + oldValue = attributes.put(name, value); + if (oldValue != null) { + replaced = true; + } + + // Pass special attributes to the native layer +// if (name.startsWith("org.apache.tomcat.")) { +// reqB.setAttribute(name, value); +// } +// + // Notify interested application event listeners + List listeners = getContext().getListeners(); + if (listeners.size() == 0) + return; + ServletRequestAttributeEvent event = null; + + for (int i = 0; i < listeners.size(); i++) { + if (!(listeners.get(i) instanceof ServletRequestAttributeListener)) + continue; + ServletRequestAttributeListener listener = + (ServletRequestAttributeListener) listeners.get(i); + try { + if (event == null) { + if (replaced) + event = + new ServletRequestAttributeEvent(getContext().getServletContext(), + getRequest(), name, oldValue); + else + event = + new ServletRequestAttributeEvent(getContext().getServletContext(), + getRequest(), name, value); + } + if (replaced) { + listener.attributeReplaced(event); + } else { + listener.attributeAdded(event); + } + } catch (Throwable t) { + getContext().getLogger().log(Level.WARNING, "ServletRequestAttributeListener error", t); + // Error valve will pick this execption up and display it to user + attributes.put( ServletRequestImpl.EXCEPTION_ATTR, t ); + } + } + } + + + // --------------------------------------------- HttpServletRequest Methods + + + /** + * Set the authentication type used for this request, if any; otherwise + * set the type to null. Typical values are "BASIC", + * "DIGEST", or "SSL". + * + * @param type The authentication type used + */ + public void setAuthType(String type) { + this.authType = type; + } + + + /** + * Overrides the name of the character encoding used in the body of + * this request. This method must be called prior to reading request + * parameters or reading input using getReader(). + * + * @param enc The character encoding to be used + * + * @exception UnsupportedEncodingException if the specified encoding + * is not supported + * + * @since Servlet 2.3 + */ + public void setCharacterEncoding(String enc) + throws UnsupportedEncodingException { + + // Ensure that the specified encoding is valid + byte buffer[] = new byte[1]; + buffer[0] = (byte) 'a'; + String dummy = new String(buffer, enc); + + // Save the validated encoding + httpRequest.setCharacterEncoding(enc); + + } + + /** + * Set the Context within which this Request is being processed. This + * must be called as soon as the appropriate Context is identified, because + * it identifies the value to be returned by getContextPath(), + * and thus enables parsing of the request URI. + * + * @param context The newly associated Context + */ + public void setContext(ServletContextImpl context) { + this.context = context; + } + + + /** + * Set the context path for this Request. This will normally be called + * when the associated Context is mapping the Request to a particular + * Wrapper. + * + * @param path The context path + */ + public void setContextPath(String path) { + + if (path == null) { + getMappingData().contextPath.set(""); + } else { + getMappingData().contextPath.set(path); + } + + } + + /** + * Set the path information for this Request. This will normally be called + * when the associated Context is mapping the Request to a particular + * Wrapper. + * + * @param path The path information + */ + public void setPathInfo(String path) { + getMappingData().pathInfo.set(path); + } + + + /** + * Set a flag indicating whether or not the requested session ID for this + * request came in through a cookie. This is normally called by the + * HTTP Connector, when it parses the request headers. + * + * @param flag The new flag + */ + public void setRequestedSessionCookie(boolean flag) { + + this.requestedSessionCookie = flag; + + } + + + /** + * Set the requested session ID for this request. This is normally called + * by the HTTP Connector, when it parses the request headers. + * + * @param id The new session id + */ + public void setRequestedSessionId(String id) { + + this.requestedSessionId = id; + + } + + + /** + * Set a flag indicating whether or not the requested session ID for this + * request came in through a URL. This is normally called by the + * HTTP Connector, when it parses the request headers. + * + * @param flag The new flag + */ + public void setRequestedSessionURL(boolean flag) { + + this.requestedSessionURL = flag; + + } + + + /** + * Set the value to be returned by isSecure() + * for this Request. + * + * @param secure The new isSecure value + */ + public void setSecure(boolean secure) { + this.secure = secure; + } + + /** + * Set the servlet path for this Request. This will normally be called + * when the associated Context is mapping the Request to a particular + * Wrapper. + * + * @param path The servlet path + */ + public void setServletPath(String path) { + if (path != null) + getMappingData().wrapperPath.set(path); + } + + + /** + * Set the input stream associated with this Request. + * + * @param stream The new input stream + */ + public void setStream(InputStream stream) { + // Ignore + } + + + /** + * Set the Principal who has been authenticated for this Request. This + * value is also used to calculate the value to be returned by the + * getRemoteUser() method. + * + * @param principal The user Principal + */ + public void setUserPrincipal(Principal principal) { + + if (System.getSecurityManager() != null){ + HttpSession session = getSession(false); + if ( (subject != null) && + (!subject.getPrincipals().contains(principal)) ){ + subject.getPrincipals().add(principal); + } else if (session != null && + session.getAttribute(ServletRequestImpl.SUBJECT_ATTR) == null) { + subject = new Subject(); + subject.getPrincipals().add(principal); + } + if (session != null){ + session.setAttribute(ServletRequestImpl.SUBJECT_ATTR, subject); + } + } + + this.userPrincipal = principal; + } + + + /** + * Set the Wrapper within which this Request is being processed. This + * must be called as soon as the appropriate Wrapper is identified, and + * before the Request is ultimately passed to an application servlet. + * @param wrapper The newly associated Wrapper + */ + public void setWrapper(ServletConfigImpl wrapper) { + this.wrapper = wrapper; + } + + + public String toString() { + return httpRequest.requestURI().toString(); + } + + + /** + * Configures the given JSESSIONID cookie. + * + * @param cookie The JSESSIONID cookie to be configured + */ + protected void configureSessionCookie(Cookie cookie) { + cookie.setMaxAge(-1); + String contextPath = null; + if (//!connector.getEmptySessionPath() && + (getContext() != null)) { + contextPath = getContext().getEncodedPath(); + } + if ((contextPath != null) && (contextPath.length() > 0)) { + cookie.setPath(contextPath); + } else { + cookie.setPath("/"); + } + if (isSecure()) { + cookie.setSecure(true); + } + } + + + /** + * Return the session associated with this Request, creating one + * if necessary. + */ + public HttpSession getSession() { + return getSession(true); + } + + + public HttpSession getSession(boolean create) { + + // There cannot be a session if no context has been assigned yet + if (getContext() == null) + return (null); + + + // Return the requested session if it exists and is valid + UserSessionManager manager = null; + if (getContext() != null) + manager = getContext().getManager(); + if (manager == null) + return (null); // Sessions are not supported + + // Return the current session if it exists and is valid + if ((session != null) && !manager.isValid(session)) + session = null; + if (session != null) + return (session); + + + if (requestedSessionId != null) { + try { + session = manager.findSession(requestedSessionId); + } catch (IOException e) { + session = null; + } + if ((session != null) && !manager.isValid(session)) + session = null; + if (session != null) { + manager.access(session); + return (session); + } + } + + // Create a new session if requested and the response is not committed + if (!create) + return (null); + if ((getContext() != null) && (response != null) && + getContext().getCookies() && + getResponse().isCommitted()) { + throw new IllegalStateException + ("isCommited()"); + } + + // Attempt to reuse session id if one was submitted in a cookie + // Do not reuse the session id if it is from a URL, to prevent possible + // phishing attacks + if (// connector.getEmptySessionPath() && + isRequestedSessionIdFromCookie()) { + session = manager.createSession(getRequestedSessionId()); + } else { + session = manager.createSession(null); + } + + // Creating a new session cookie based on that session + if ((session != null) && (getContext() != null) + && getContext().getCookies()) { + Cookie cookie = new Cookie(ServletRequestImpl.SESSION_COOKIE_NAME, + session.getId()); + configureSessionCookie(cookie); + response.addCookie(cookie); + } + + if (session != null) { + manager.access(session); + return (session); + } else { + return (null); + } + + } + + + /** + * Parse request locales. + */ + protected void parseLocales() { + + localesParsed = true; + + Enumeration values = getHeaders("accept-language"); + + while (values.hasMoreElements()) { + String value = values.nextElement().toString(); + parseLocalesHeader(value); + } + + } + + /** + * Parse accept-language header value. + */ + protected void parseLocalesHeader(String value) { + + TreeMap locales = new LocaleParser().parseLocale(value); + // Process the quality values in highest->lowest order (due to + // negating the Double value when creating the key) + Iterator keys = locales.keySet().iterator(); + while (keys.hasNext()) { + Double key = (Double) keys.next(); + ArrayList list = (ArrayList) locales.get(key); + Iterator values = list.iterator(); + while (values.hasNext()) { + Locale locale = (Locale) values.next(); + addLocale(locale); + } + } + + } + + + /** + * Parse session id in URL. Done in request for performance. + * TODO: should be done in manager + */ + protected void parseSessionCookiesId() { + String sessionCookieName = getContext().getSessionCookieName(); + + // Parse session id from cookies + ServerCookie scookie = + httpRequest.getCookie(sessionCookieName); + if (scookie == null) { + return; + } + // Override anything requested in the URL + if (!isRequestedSessionIdFromCookie()) { + // Accept only the first session id cookie + //scookie.getValue().convertToAscii(); + + setRequestedSessionId + (scookie.getValue().toString()); + setRequestedSessionCookie(true); + setRequestedSessionURL(false); + } else { + if (!isRequestedSessionIdValid()) { + // Replace the session id until one is valid + //scookie.getValue().convertToAscii(); + setRequestedSessionId + (scookie.getValue().toString()); + } + } + } + + /** + * Parse session id in URL. + */ + protected void parseSessionId() { + ServletRequestImpl request = this; + BBuffer uriBC = httpRequest.getMsgBytes().url(); + int semicolon = uriBC.indexOf(match, 0, match.length(), 0); + + if (semicolon > 0) { + + // Parse session ID, and extract it from the decoded request URI + int start = uriBC.getStart(); + int end = uriBC.getEnd(); + + int sessionIdStart = semicolon + match.length(); + int semicolon2 = uriBC.indexOf(';', sessionIdStart); + if (semicolon2 >= 0) { + request.setRequestedSessionId + (new String(uriBC.array(), start + sessionIdStart, + semicolon2 - sessionIdStart)); + // Extract session ID from request URI + byte[] buf = uriBC.array(); + for (int i = 0; i < end - start - semicolon2; i++) { + buf[start + semicolon + i] + = buf[start + i + semicolon2]; + } + uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon); + } else { + request.setRequestedSessionId + (new String(uriBC.array(), start + sessionIdStart, + (end - start) - sessionIdStart)); + uriBC.setEnd(start + semicolon); + } + request.setRequestedSessionURL(true); + + } else { + request.setRequestedSessionId(null); + request.setRequestedSessionURL(false); + } + + } + + + + /** + * Test if a given name is one of the special Servlet-spec SSL attributes. + */ + static boolean isSSLAttribute(String name) { + return ServletRequestImpl.CERTIFICATES_ATTR.equals(name) || + ServletRequestImpl.CIPHER_SUITE_ATTR.equals(name) || + ServletRequestImpl.KEY_SIZE_ATTR.equals(name) || + ServletRequestImpl.SSL_SESSION_ID_ATTR.equals(name); + } + + + + public ServletContext getServletContext() { + return getContext(); + } + + + public boolean isAsyncStarted() { + return httpRequest.isAsyncStarted(); + } + + + public boolean isAsyncSupported() { + return false; + } + + + public void setAsyncTimeout(long timeout) { + httpRequest.setAsyncTimeout(timeout); + } + + + public boolean authenticate(HttpServletResponse response) + throws IOException, ServletException { + return false; + } + + public void login(String username, String password) throws ServletException { + } + + + public void logout() throws ServletException { + } + + + public long getAsyncTimeout() { + return 0; + } + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestWrapperImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestWrapperImpl.java new file mode 100644 index 000000000..a80f003a3 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestWrapperImpl.java @@ -0,0 +1,943 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpSession; + +import org.apache.tomcat.servlets.session.UserSessionManager; +import org.apache.tomcat.servlets.util.Enumerator; +import org.apache.tomcat.servlets.util.RequestUtil; + + +/** + * Wrapper around a javax.servlet.http.HttpServletRequest + * that transforms an application request object (which might be the original + * one passed to a servlet, or might be based on the 2.3 + * javax.servlet.http.HttpServletRequestWrapper class) + * back into an internal org.apache.catalina.HttpRequest. + *

+ * WARNING: Due to Java's lack of support for multiple + * inheritance, all of the logic in ApplicationRequest is + * duplicated in ApplicationHttpRequest. Make sure that you + * keep these two classes in synchronization when making changes! + * + * @author Craig R. McClanahan + * @author Remy Maucherat + */ +public class ServletRequestWrapperImpl extends HttpServletRequestWrapper { + + + // ------------------------------------------------------- Static Variables + + + /** + * The set of attribute names that are special for request dispatchers. + */ + protected static final String specials[] = + { RequestDispatcherImpl.INCLUDE_REQUEST_URI_ATTR, + RequestDispatcherImpl.INCLUDE_CONTEXT_PATH_ATTR, + RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR, + RequestDispatcherImpl.INCLUDE_PATH_INFO_ATTR, + RequestDispatcherImpl.INCLUDE_QUERY_STRING_ATTR, + RequestDispatcherImpl.FORWARD_REQUEST_URI_ATTR, + RequestDispatcherImpl.FORWARD_CONTEXT_PATH_ATTR, + RequestDispatcherImpl.FORWARD_SERVLET_PATH_ATTR, + RequestDispatcherImpl.FORWARD_PATH_INFO_ATTR, + RequestDispatcherImpl.FORWARD_QUERY_STRING_ATTR }; + + + // ----------------------------------------------------------- Constructors + + + /** + * Construct a new wrapped request around the specified servlet request. + * + * @param request The servlet request being wrapped + */ + public ServletRequestWrapperImpl(HttpServletRequest request, + ServletContextImpl context, + boolean crossContext) { + + super(request); + this.context = context; + this.crossContext = crossContext; + setRequest(request); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The context for this request. + */ + protected ServletContextImpl context = null; + + + /** + * The context path for this request. + */ + protected String contextPath = null; + + + /** + * If this request is cross context, since this changes session accesss + * behavior. + */ + protected boolean crossContext = false; + + + /** + * The current dispatcher type. + */ + protected Object dispatcherType = null; + + + /** + * Descriptive information about this implementation. + */ + protected static final String info = + "org.apache.catalina.core.ApplicationHttpRequest/1.0"; + + + /** + * The request parameters for this request. This is initialized from the + * wrapped request, but updates are allowed. + */ + protected Map parameters = null; + + + /** + * Have the parameters for this request already been parsed? + */ + private boolean parsedParams = false; + + + /** + * The path information for this request. + */ + protected String pathInfo = null; + + + /** + * The query parameters for the current request. + */ + private String queryParamString = null; + + + /** + * The query string for this request. + */ + protected String queryString = null; + + + /** + * The current request dispatcher path. + */ + protected Object requestDispatcherPath = null; + + + /** + * The request URI for this request. + */ + protected String requestURI = null; + + + /** + * The servlet path for this request. + */ + protected String servletPath = null; + + + /** + * The currently active session for this request. + */ + protected HttpSession session = null; + + + /** + * Special attributes. + */ + protected Object[] specialAttributes = new Object[specials.length]; + + + // ------------------------------------------------- ServletRequest Methods + + + /** + * Override the getAttribute() method of the wrapped request. + * + * @param name Name of the attribute to retrieve + */ + public Object getAttribute(String name) { + + if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) { + return dispatcherType; + } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) { + if ( requestDispatcherPath != null ){ + return requestDispatcherPath.toString(); + } else { + return null; + } + } + + int pos = getSpecial(name); + if (pos == -1) { + return getRequest().getAttribute(name); + } else { + if ((specialAttributes[pos] == null) + && (specialAttributes[5] == null) && (pos >= 5)) { + // If it's a forward special attribute, and null, it means this + // is an include, so we check the wrapped request since + // the request could have been forwarded before the include + return getRequest().getAttribute(name); + } else { + return specialAttributes[pos]; + } + } + + } + + + /** + * Override the getAttributeNames() method of the wrapped + * request. + */ + public Enumeration getAttributeNames() { + return (new AttributeNamesEnumerator()); + } + + + /** + * Override the removeAttribute() method of the + * wrapped request. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + + if (!removeSpecial(name)) + getRequest().removeAttribute(name); + + } + + + /** + * Override the setAttribute() method of the + * wrapped request. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set + */ + public void setAttribute(String name, Object value) { + + if (name.equals(ServletRequestImpl.DISPATCHER_TYPE_ATTR)) { + dispatcherType = value; + return; + } else if (name.equals(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR)) { + requestDispatcherPath = value; + return; + } + + if (!setSpecial(name, value)) { + getRequest().setAttribute(name, value); + } + + } + + + /** + * Return a RequestDispatcher that wraps the resource at the specified + * path, which may be interpreted as relative to the current request path. + * + * @param path Path of the resource to be wrapped + */ + public RequestDispatcher getRequestDispatcher(String path) { + + if (context == null) + return (null); + + // If the path is already context-relative, just pass it through + if (path == null) + return (null); + else if (path.startsWith("/")) + return (context.getServletContext().getRequestDispatcher(path)); + + // Convert a request-relative path to a context-relative one + String servletPath = + (String) getAttribute(RequestDispatcherImpl.INCLUDE_SERVLET_PATH_ATTR); + if (servletPath == null) + servletPath = getServletPath(); + + // Add the path info, if there is any + String pathInfo = getPathInfo(); + String requestPath = null; + + if (pathInfo == null) { + requestPath = servletPath; + } else { + requestPath = servletPath + pathInfo; + } + + int pos = requestPath.lastIndexOf('/'); + String relative = null; + if (pos >= 0) { + relative = RequestUtil.normalize + (requestPath.substring(0, pos + 1) + path); + } else { + relative = RequestUtil.normalize(requestPath + path); + } + + return (context.getServletContext().getRequestDispatcher(relative)); + + } + + + // --------------------------------------------- HttpServletRequest Methods + + + /** + * Override the getContextPath() method of the wrapped + * request. + */ + public String getContextPath() { + + return (this.contextPath); + + } + + + /** + * Override the getParameter() method of the wrapped request. + * + * @param name Name of the requested parameter + */ + public String getParameter(String name) { + + parseParameters(); + + Object value = parameters.get(name); + if (value == null) + return (null); + else if (value instanceof String[]) + return (((String[]) value)[0]); + else if (value instanceof String) + return ((String) value); + else + return (value.toString()); + + } + + + /** + * Override the getParameterMap() method of the + * wrapped request. + */ + public Map getParameterMap() { + + parseParameters(); + return (parameters); + + } + + + /** + * Override the getParameterNames() method of the + * wrapped request. + */ + public Enumeration getParameterNames() { + + parseParameters(); + return (new Enumerator(parameters.keySet())); + + } + + + /** + * Override the getParameterValues() method of the + * wrapped request. + * + * @param name Name of the requested parameter + */ + public String[] getParameterValues(String name) { + + parseParameters(); + Object value = parameters.get(name); + if (value == null) + return ((String[]) null); + else if (value instanceof String[]) + return ((String[]) value); + else if (value instanceof String) { + String values[] = new String[1]; + values[0] = (String) value; + return (values); + } else { + String values[] = new String[1]; + values[0] = value.toString(); + return (values); + } + + } + + + /** + * Override the getPathInfo() method of the wrapped request. + */ + public String getPathInfo() { + + return (this.pathInfo); + + } + + + /** + * Override the getQueryString() method of the wrapped + * request. + */ + public String getQueryString() { + + return (this.queryString); + + } + + + /** + * Override the getRequestURI() method of the wrapped + * request. + */ + public String getRequestURI() { + + return (this.requestURI); + + } + + + /** + * Override the getRequestURL() method of the wrapped + * request. + */ + public StringBuffer getRequestURL() { + + StringBuffer url = new StringBuffer(); + String scheme = getScheme(); + int port = getServerPort(); + if (port < 0) + port = 80; // Work around java.net.URL bug + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + if ((scheme.equals("http") && (port != 80)) + || (scheme.equals("https") && (port != 443))) { + url.append(':'); + url.append(port); + } + url.append(getRequestURI()); + + return (url); + + } + + + /** + * Override the getServletPath() method of the wrapped + * request. + */ + public String getServletPath() { + + return (this.servletPath); + + } + + + /** + * Return the session associated with this Request, creating one + * if necessary. + */ + public HttpSession getSession() { + return (getSession(true)); + } + + + /** + * Return the session associated with this Request, creating one + * if necessary and requested. + * + * @param create Create a new session if one does not exist + */ + public HttpSession getSession(boolean create) { + + if (crossContext) { + + // There cannot be a session if no context has been assigned yet + if (context == null) + return (null); + UserSessionManager manager = context.getManager(); + // Return the current session if it exists and is valid + if (session != null && manager.isValid(session)) { + return session; + } + + HttpSession other = super.getSession(false); + if (create && (other == null)) { + // First create a session in the first context: the problem is + // that the top level request is the only one which can + // create the cookie safely + other = super.getSession(true); + } + if (other != null) { + HttpSession localSession = null; + try { + localSession = + manager.findSession(other.getId()); + } catch (IOException e) { + // Ignore + } + if (localSession == null && create) { + localSession = + context.getManager().createSession(other.getId()); + } + if (localSession != null) { + context.getManager().access(localSession); + session = localSession; + return session; + } + } + return null; + + } else { + return super.getSession(create); + } + + } + + + /** + * Returns true if the request specifies a JSESSIONID that is valid within + * the context of this ApplicationHttpRequest, false otherwise. + * + * @return true if the request specifies a JSESSIONID that is valid within + * the context of this ApplicationHttpRequest, false otherwise. + */ + public boolean isRequestedSessionIdValid() { + + if (crossContext) { + + String requestedSessionId = getRequestedSessionId(); + if (requestedSessionId == null) + return (false); + if (context == null) + return (false); + UserSessionManager manager = context.getManager(); + if (manager == null) + return (false); + HttpSession session = null; + try { + session = manager.findSession(requestedSessionId); + } catch (IOException e) { + session = null; + } + if ((session != null) && manager.isValid(session)) { + return (true); + } else { + return (false); + } + + } else { + return super.isRequestedSessionIdValid(); + } + } + + + // -------------------------------------------------------- Package Methods + + + /** + * Recycle this request + */ + public void recycle() { + if (session != null) { + context.getManager().endAccess(session); + } + } + + + /** + * Return descriptive information about this implementation. + */ + public String getInfo() { + + return (info); + + } + + + /** + * Perform a shallow copy of the specified Map, and return the result. + * + * @param orig Origin Map to be copied + */ + Map copyMap(Map orig) { + + if (orig == null) + return (new HashMap()); + HashMap dest = new HashMap(); + Iterator keys = orig.keySet().iterator(); + while (keys.hasNext()) { + String key = (String) keys.next(); + dest.put(key, orig.get(key)); + } + return (dest); + + } + + + /** + * Set the context path for this request. + * + * @param contextPath The new context path + */ + void setContextPath(String contextPath) { + + this.contextPath = contextPath; + + } + + + /** + * Set the path information for this request. + * + * @param pathInfo The new path info + */ + void setPathInfo(String pathInfo) { + + this.pathInfo = pathInfo; + + } + + + /** + * Set the query string for this request. + * + * @param queryString The new query string + */ + void setQueryString(String queryString) { + + this.queryString = queryString; + + } + + + /** + * Set the request that we are wrapping. + * + * @param request The new wrapped request + */ + void setRequest(HttpServletRequest request) { + + super.setRequest(request); + + // Initialize the attributes for this request + dispatcherType = request.getAttribute(ServletRequestImpl.DISPATCHER_TYPE_ATTR); + requestDispatcherPath = + request.getAttribute(ServletRequestImpl.DISPATCHER_REQUEST_PATH_ATTR); + + // Initialize the path elements for this request + contextPath = request.getContextPath(); + pathInfo = request.getPathInfo(); + queryString = request.getQueryString(); + requestURI = request.getRequestURI(); + servletPath = request.getServletPath(); + + } + + + /** + * Set the request URI for this request. + * + * @param requestURI The new request URI + */ + void setRequestURI(String requestURI) { + + this.requestURI = requestURI; + + } + + + /** + * Set the servlet path for this request. + * + * @param servletPath The new servlet path + */ + void setServletPath(String servletPath) { + + this.servletPath = servletPath; + + } + + + /** + * Parses the parameters of this request. + * + * If parameters are present in both the query string and the request + * content, they are merged. + */ + void parseParameters() { + + if (parsedParams) { + return; + } + + parameters = new HashMap(); + parameters = copyMap(getRequest().getParameterMap()); + mergeParameters(); + parsedParams = true; + } + + + /** + * Save query parameters for this request. + * + * @param queryString The query string containing parameters for this + * request + */ + void setQueryParams(String queryString) { + this.queryParamString = queryString; + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Is this attribute name one of the special ones that is added only for + * included servlets? + * + * @param name Attribute name to be tested + */ + protected boolean isSpecial(String name) { + + for (int i = 0; i < specials.length; i++) { + if (specials[i].equals(name)) + return (true); + } + return (false); + + } + + + /** + * Get a special attribute. + * + * @return the special attribute pos, or -1 if it is not a special + * attribute + */ + protected int getSpecial(String name) { + for (int i = 0; i < specials.length; i++) { + if (specials[i].equals(name)) { + return (i); + } + } + return (-1); + } + + + /** + * Set a special attribute. + * + * @return true if the attribute was a special attribute, false otherwise + */ + protected boolean setSpecial(String name, Object value) { + for (int i = 0; i < specials.length; i++) { + if (specials[i].equals(name)) { + specialAttributes[i] = value; + return (true); + } + } + return (false); + } + + + /** + * Remove a special attribute. + * + * @return true if the attribute was a special attribute, false otherwise + */ + protected boolean removeSpecial(String name) { + for (int i = 0; i < specials.length; i++) { + if (specials[i].equals(name)) { + specialAttributes[i] = null; + return (true); + } + } + return (false); + } + + + /** + * Merge the two sets of parameter values into a single String array. + * + * @param values1 First set of values + * @param values2 Second set of values + */ + protected String[] mergeValues(Object values1, Object values2) { + + ArrayList results = new ArrayList(); + + if (values1 == null) + ; + else if (values1 instanceof String) + results.add(values1); + else if (values1 instanceof String[]) { + String values[] = (String[]) values1; + for (int i = 0; i < values.length; i++) + results.add(values[i]); + } else + results.add(values1.toString()); + + if (values2 == null) + ; + else if (values2 instanceof String) + results.add(values2); + else if (values2 instanceof String[]) { + String values[] = (String[]) values2; + for (int i = 0; i < values.length; i++) + results.add(values[i]); + } else + results.add(values2.toString()); + + String values[] = new String[results.size()]; + return ((String[]) results.toArray(values)); + + } + + + // ------------------------------------------------------ Private Methods + + + /** + * Merge the parameters from the saved query parameter string (if any), and + * the parameters already present on this request (if any), such that the + * parameter values from the query string show up first if there are + * duplicate parameter names. + */ + private void mergeParameters() { + + if ((queryParamString == null) || (queryParamString.length() < 1)) + return; + + HashMap queryParameters = new HashMap(); + String encoding = getCharacterEncoding(); + if (encoding == null) + encoding = "ISO-8859-1"; + try { + RequestUtil.parseParameters + (queryParameters, queryParamString, encoding); + } catch (Exception e) { + ; + } + Iterator keys = parameters.keySet().iterator(); + while (keys.hasNext()) { + String key = (String) keys.next(); + Object value = queryParameters.get(key); + if (value == null) { + queryParameters.put(key, parameters.get(key)); + continue; + } + queryParameters.put + (key, mergeValues(value, parameters.get(key))); + } + parameters = queryParameters; + + } + + + // ----------------------------------- AttributeNamesEnumerator Inner Class + + + /** + * Utility class used to expose the special attributes as being available + * as request attributes. + */ + protected class AttributeNamesEnumerator implements Enumeration { + + protected int pos = -1; + protected int last = -1; + protected Enumeration parentEnumeration = null; + protected String next = null; + + public AttributeNamesEnumerator() { + parentEnumeration = getRequest().getAttributeNames(); + for (int i = 0; i < specialAttributes.length; i++) { + if (getAttribute(specials[i]) != null) { + last = i; + } + } + } + + public boolean hasMoreElements() { + return ((pos != last) || (next != null) + || ((next = findNext()) != null)); + } + + public Object nextElement() { + if (pos != last) { + for (int i = pos + 1; i <= last; i++) { + if (getAttribute(specials[i]) != null) { + pos = i; + return (specials[i]); + } + } + } + String result = next; + if (next != null) { + next = findNext(); + } else { + throw new NoSuchElementException(); + } + return result; + } + + protected String findNext() { + String result = null; + while ((result == null) && (parentEnumeration.hasMoreElements())) { + String current = (String) parentEnumeration.nextElement(); + if (!isSpecial(current)) { + result = current; + } + } + return result; + } + + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseImpl.java new file mode 100644 index 000000000..786e38a7b --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseImpl.java @@ -0,0 +1,1297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.lite.servlet; + + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Locale; +import java.util.TimeZone; +import java.util.Vector; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.tomcat.lite.http.HttpResponse; +import org.apache.tomcat.lite.http.HttpWriter; +import org.apache.tomcat.lite.http.MultiMap; +import org.apache.tomcat.lite.http.ServerCookie; +import org.apache.tomcat.lite.http.MultiMap.Entry; +import org.apache.tomcat.lite.io.CBuffer; +import org.apache.tomcat.lite.io.FastHttpDateFormat; +import org.apache.tomcat.lite.io.IOWriter; + +/** + * Wrapper object for the Coyote response. + * + * @author Remy Maucherat + * @author Craig R. McClanahan + * @version $Revision: 793797 $ $Date: 2009-07-13 22:38:02 -0700 (Mon, 13 Jul 2009) $ + */ + +public class ServletResponseImpl + implements HttpServletResponse { + + /** + * Format for http response header date field + * From DateTool + */ + public static final String HTTP_RESPONSE_DATE_HEADER = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + // ----------------------------------------------------------- Constructors + + + // Package - only ServletRequestImpl should call it. + ServletResponseImpl() { + } + + + /** + * The date format we will use for creating date headers. + */ + protected SimpleDateFormat format = null; + + + /** + * The associated output buffer. + */ + protected HttpWriter outputBuffer; + + + /** + * The associated output stream. + */ + protected ServletOutputStreamImpl outputStream; + + + /** + * The associated writer. + */ + protected PrintWriter writer; + + + /** + * The application commit flag. + */ + protected boolean appCommitted = false; + + + /** + * The included flag. + */ + protected boolean included = false; + + + /** + * The characterEncoding flag + */ + private boolean isCharacterEncodingSet = false; + + /** + * The error flag. + */ + protected boolean error = false; + + + /** + * The set of Cookies associated with this Response. + * Only used in facade - base object uses ServerCookie + */ + protected ArrayList cookies = new ArrayList(); + + + /** + * Using output stream flag. + */ + protected boolean usingOutputStream = false; + + + /** + * Using writer flag. + */ + protected boolean usingWriter = false; + + + protected ServletRequestImpl req = null; + + + protected CBuffer tmpUrlBuffer = CBuffer.newInstance(); + + private HttpResponse resB; + + + // Cached/derived information - reflected in headers + protected static Locale DEFAULT_LOCALE = Locale.getDefault(); + + public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1"; + + protected Locale locale = DEFAULT_LOCALE; + + // XXX + protected boolean commited = false; + protected String contentType = null; + + /** + * Has the charset been explicitly set. + */ + protected boolean charsetSet = false; + protected String characterEncoding = DEFAULT_CHARACTER_ENCODING; + + + // --------------------------------------------------------- Public Methods + + /** + * Release all object references, and initialize instance variables, in + * preparation for reuse of this object. + */ + void recycle() { + + usingOutputStream = false; + usingWriter = false; + appCommitted = false; + commited = false; + included = false; + error = false; + isCharacterEncodingSet = false; + + cookies.clear(); + + outputBuffer.recycle(); + + //resB.recycle(); + } + + + // ------------------------------------------------------- Response Methods + + + /** + * Return the number of bytes actually written to the output stream. + */ +// public int getContentCount() { +// return outputBuffer.getBytesWritten() + outputBuffer.getCharsWritten(); +// } + + + /** + * Set the application commit flag. + * + * @param appCommitted The new application committed flag value + */ + public void setAppCommitted(boolean appCommitted) { + this.appCommitted = appCommitted; + } + + +// /** +// * Application commit flag accessor. +// */ +// public boolean isAppCommitted() { +// return (this.appCommitted || isCommitted() || isSuspended() +// || ((getHttpResponse().getContentLength() > 0) +// && (getContentCount() >= getHttpResponse().getContentLength()))); +// } + + + /** + * Return the "processing inside an include" flag. + */ + public boolean getIncluded() { + return included; + } + + + /** + * Set the "processing inside an include" flag. + * + * @param included true if we are currently inside a + * RequestDispatcher.include(), else false + */ + public void setIncluded(boolean included) { + this.included = included; + } + + + /** + * Return the Request with which this Response is associated. + */ + public ServletRequestImpl getRequest() { + return (this.req); + } + + /** + * Set the Request with which this Response is associated. + * + * @param request The new associated request + */ + public void setRequest(ServletRequestImpl request) { + this.req = (ServletRequestImpl) request; + } + + + /** + * Return the output stream associated with this Response. + */ + public OutputStream getStream() { + return outputStream; + } + + + /** + * Set the output stream associated with this Response. + * + * @param stream The new output stream + */ + public void setStream(OutputStream stream) { + // This method is evil + } + + + /** + * Set the suspended flag. + * + * @param suspended The new suspended flag value + */ + public void setSuspended(boolean suspended) throws IOException { + //coyoteResponse.setCommitted(true); + flushBuffer(); + outputBuffer.setSuspended(suspended); + } + + + /** + * Suspended flag accessor. + */ + public boolean isSuspended() { + return outputBuffer.isSuspended(); + } + + + /** + * Set the error flag. + */ + public void setError() { + error = true; + } + + + /** + * Error flag accessor. + */ + public boolean isError() { + return error; + } + + + /** + * Create and return a ServletOutputStream to write the content + * associated with this Response. + * + * @exception IOException if an input/output error occurs + */ + public ServletOutputStream createOutputStream() + throws IOException { + // Probably useless + return outputStream; + } + + /** + * Return the content type that was set or calculated for this response, + * or null if no content type was set. + */ + public String getContentType() { + String ret = contentType; + + if (ret != null + && characterEncoding != null + && charsetSet) { + ret = ret + ";charset=" + characterEncoding; + } + + return ret; + } + + + // ------------------------------------------------ ServletResponse Methods + + + /** + * Flush the buffer and commit this response. + * + * @exception IOException if an input/output error occurs + */ + public void flushBuffer() + throws IOException { + outputBuffer.flush(); + } + + + /** + * Return the actual buffer size used for this Response. + */ + public int getBufferSize() { + return outputBuffer.getBufferSize(); + } + + + /** + * Return the character encoding used for this Response. + */ + public String getCharacterEncoding() { + return characterEncoding; + } + + + /** + * Return the servlet output stream associated with this Response. + * + * @exception IllegalStateException if getWriter has + * already been called for this response + * @exception IOException if an input/output error occurs + */ + public ServletOutputStream getOutputStream() + throws IOException { + + if (usingWriter) + throw new IllegalStateException + ("usingWriter"); + + usingOutputStream = true; + return outputStream; + + } + + public HttpWriter getOutputBuffer() { + return outputBuffer; + } + + + /** + * Return the Locale assigned to this response. + */ + public Locale getLocale() { + return locale; + } + + + /** + * Return the writer associated with this Response. + * + * @exception IllegalStateException if getOutputStream has + * already been called for this response + * @exception IOException if an input/output error occurs + */ + public PrintWriter getWriter() + throws IOException { + + if (usingOutputStream) + throw new IllegalStateException + ("usingOutputStream"); + + /* + * If the response's character encoding has not been specified as + * described in getCharacterEncoding (i.e., the method + * just returns the default value ISO-8859-1), + * getWriter updates it to ISO-8859-1 + * (with the effect that a subsequent call to getContentType() will + * include a charset=ISO-8859-1 component which will also be + * reflected in the Content-Type response header, thereby satisfying + * the Servlet spec requirement that containers must communicate the + * character encoding used for the servlet response's writer to the + * client). + */ + setCharacterEncoding(getCharacterEncoding()); + + usingWriter = true; + // Otherwise it'll be set on first write - encoding + // should be fixed. + outputBuffer.checkConverter(); + + if (writer == null) { + writer = new ServletWriterImpl(outputBuffer); + } + return writer; + + } + + + /** + * Has the output of this response already been committed? + */ + public boolean isCommitted() { + return getHttpResponse().isCommitted(); + } + + /** + * Clear any content written to the buffer. + * + * @exception IllegalStateException if this response has already + * been committed + */ + public void reset() { + + if (included) + return; // Ignore any call from an included servlet + + if (isCommitted()) + throw new IllegalStateException("isCommitted"); + + resB.recycle(); // reset headers, status code, message + //req.getConnector().reset(this); + contentType = null; + locale = DEFAULT_LOCALE; + characterEncoding = DEFAULT_CHARACTER_ENCODING; + charsetSet = false; + + outputBuffer.reset(); + } + + + /** + * Reset the data buffer but not any status or header information. + * + * @exception IllegalStateException if the response has already + * been committed + */ + public void resetBuffer() { + + if (isCommitted()) + throw new IllegalStateException("isCommitted"); + + outputBuffer.reset(); + + } + + + /** + * Set the buffer size to be used for this Response. + * + * @param size The new buffer size + * + * @exception IllegalStateException if this method is called after + * output has been committed for this response + */ + public void setBufferSize(int size) { + + if (isCommitted() || outputBuffer.getWrittenSinceFlush() > 0 || + getHttpResponse().getBodyOutputStream().getWrittenSinceFlush() > 0) + throw new IllegalStateException + ("isCommitted || !isNew"); + + outputBuffer.setBufferSize(size); + + } + + + /** + * Set the content length (in bytes) for this Response. + * Ignored for writers if non-ISO-8859-1 encoding ( we could add more + * encodings that are constant. + */ + public void setContentLength(int length) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + // writers can use variable-length encoding. + if (usingWriter && !"ISO-8859-1".equals(getCharacterEncoding())) { + return; + } + getHttpResponse().setContentLength(length); + + } + + + /** + * Set the content type for this Response. + * + * @param type The new content type + */ + public void setContentType(String type) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + // Ignore charset if getWriter() has already been called + if (usingWriter) { + if (type != null) { + int index = type.indexOf(";"); + if (index != -1) { + type = type.substring(0, index); + } + } + } + + getHttpResponse().setContentType(type); + + // Check to see if content type contains charset + if (type != null) { + int index = type.indexOf(";"); + if (index != -1) { + int len = type.length(); + index++; + while (index < len && Character.isSpace(type.charAt(index))) { + index++; + } + if (index+7 < len + && type.charAt(index) == 'c' + && type.charAt(index+1) == 'h' + && type.charAt(index+2) == 'a' + && type.charAt(index+3) == 'r' + && type.charAt(index+4) == 's' + && type.charAt(index+5) == 'e' + && type.charAt(index+6) == 't' + && type.charAt(index+7) == '=') { + isCharacterEncodingSet = true; + } + } + } + } + + + /* + * Overrides the name of the character encoding used in the body + * of the request. This method must be called prior to reading + * request parameters or reading input using getReader(). + * + * @param charset String containing the name of the chararacter encoding. + */ + public void setCharacterEncoding(String charset) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + // Ignore any call made after the getWriter has been invoked + // The default should be used + if (usingWriter) + return; + + if (isCommitted()) + return; + if (charset == null) + return; + + characterEncoding = charset; + charsetSet=true; + isCharacterEncodingSet = true; + } + + + + /** + * Set the Locale that is appropriate for this response, including + * setting the appropriate character encoding. + * + * @param locale The new locale + */ + public void setLocale(Locale locale) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + if (locale == null) { + return; // throw an exception? + } + + // Save the locale for use by getLocale() + this.locale = locale; + + // Set the contentLanguage for header output + String contentLanguage = locale.getLanguage(); + if ((contentLanguage != null) && (contentLanguage.length() > 0)) { + String country = locale.getCountry(); + StringBuffer value = new StringBuffer(contentLanguage); + if ((country != null) && (country.length() > 0)) { + value.append('-'); + value.append(country); + } + contentLanguage = value.toString(); + } + resB.setHeader("Content-Language", contentLanguage); + + // Ignore any call made after the getWriter has been invoked. + // The default should be used + if (usingWriter) + return; + + if (isCharacterEncodingSet) { + return; + } + + Locale2Charset cm = req.getContext().getCharsetMapper(); + String charset = cm.getCharset( locale ); + if ( charset != null ){ + setCharacterEncoding(charset); + } + + } + + + // --------------------------------------------------- HttpResponse Methods + + + /** + * Return an array of all cookies set for this response, or + * a zero-length array if no cookies have been set. + */ + public Cookie[] getCookies() { + return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()])); + } + + + /** + * Return the value for the specified header, or null if this + * header has not been set. If more than one value was added for this + * name, only the first is returned; use getHeaderValues() to retrieve all + * of them. + * + * @param name Header name to look up + */ + public String getHeader(String name) { + return getHttpResponse().getHeader(name); + } + + + /** + * Return an array of all the header names set for this response, or + * a zero-length array if no headers have been set. + */ + public Collection getHeaderNames() { + return getHttpResponse().getHeaderNames(); + } + + public Collection getHeaders(String name) { + return null; + } + + /** + * Return an array of all the header values associated with the + * specified header name, or an zero-length array if there are no such + * header values. + * + * @param name Header name to look up + */ + public String[] getHeaderValues(String name) { + Entry entry = getHttpResponse().getMimeHeaders().getEntry(name); + if (entry == null) { + return new String[] {}; + } + int size = entry.values.size(); + String[] resultArray = new String[size]; + for (int i = 0; i < size; i++) { + resultArray[i] = entry.values.get(i).getValue().toString(); + } + return resultArray; + + } + + + /** + * Return the error message that was set with sendError() + * for this Response. + */ + public String getMessage() { + return getHttpResponse().getMessage(); + } + + + /** + * Return the HTTP status code associated with this Response. + */ + public int getStatus() { + return getHttpResponse().getStatus(); + } + + + /** + * Reset this response, and specify the values for the HTTP status code + * and corresponding message. + * + * @exception IllegalStateException if this response has already been + * committed + */ + public void reset(int status, String message) { + reset(); + setStatus(status, message); + } + + + // -------------------------------------------- HttpServletResponse Methods + + + /** + * Add the specified Cookie to those that will be included with + * this Response. + * + * @param cookie Cookie to be added + */ + public void addCookie(final Cookie cookie) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + cookies.add(cookie); + + final StringBuffer sb = new StringBuffer(); + ServerCookie.appendCookieValue + (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(), + cookie.getPath(), cookie.getDomain(), cookie.getComment(), + cookie.getMaxAge(), cookie.getSecure(), false); + + // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 ) + // RFC2965 is not supported by browsers and the Servlet spec + // asks for 2109. + addHeader("Set-Cookie", sb.toString()); + + } + + + /** + * Add the specified date header to the specified value. + * + * @param name Name of the header to set + * @param value Date value to be set + */ + public void addDateHeader(String name, long value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) { + return; + } + + if (format == null) { + format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, + Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + addHeader(name, FastHttpDateFormat.formatDate(value, format)); + + } + + + /** + * Add the specified header to the specified value. + * + * @param name Name of the header to set + * @param value Value to be set + */ + public void addHeader(String name, String value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + getHttpResponse().addHeader(name, value); + + } + + + /** + * Add the specified integer header to the specified value. + * + * @param name Name of the header to set + * @param value Integer value to be set + */ + public void addIntHeader(String name, int value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + addHeader(name, "" + value); + + } + + + /** + * Has the specified header been set already in this response? + * + * @param name Name of the header to check + */ + public boolean containsHeader(String name) { + // Need special handling for Content-Type and Content-Length due to + // special handling of these in coyoteResponse + char cc=name.charAt(0); + if(cc=='C' || cc=='c') { + if(name.equalsIgnoreCase("Content-Type")) { + // Will return null if this has not been set + return getContentType() != null; + } + if(name.equalsIgnoreCase("Content-Length")) { + // -1 means not known and is not sent to client + return (getHttpResponse().getContentLength() != -1); + } + } + + return getHttpResponse().containsHeader(name); + } + + + /** + * Encode the session identifier associated with this response + * into the specified redirect URL, if necessary. + * + * @param url URL to be encoded + */ + public String encodeRedirectURL(String url) { + req.getHttpRequest().toAbsolute(url, tmpUrlBuffer); + if (isEncodeable(tmpUrlBuffer.toString())) { + return (appendSessionId(url, req.getSession().getId())); + } else { + return (url); + } + + } + + + /** + * Encode the session identifier associated with this response + * into the specified redirect URL, if necessary. + * + * @param url URL to be encoded + * + * @deprecated As of Version 2.1 of the Java Servlet API, use + * encodeRedirectURL() instead. + */ + public String encodeRedirectUrl(String url) { + return (encodeRedirectURL(url)); + } + + + /** + * Encode the session identifier associated with this response + * into the specified URL, if necessary. + * + * @param url URL to be encoded + */ + public String encodeURL(String url) { + req.getHttpRequest().toAbsolute(url, tmpUrlBuffer); + String absolute = tmpUrlBuffer.toString(); + if (isEncodeable(absolute)) { + // W3c spec clearly said + if (url.equalsIgnoreCase("")){ + url = absolute; + } + return (appendSessionId(url, req.getSession().getId())); + } else { + return (url); + } + + } + + + /** + * Encode the session identifier associated with this response + * into the specified URL, if necessary. + * + * @param url URL to be encoded + * + * @deprecated As of Version 2.1 of the Java Servlet API, use + * encodeURL() instead. + */ + public String encodeUrl(String url) { + return (encodeURL(url)); + } + + + /** + * Send an error response with the specified status and a + * default message. + * + * @param status HTTP status code to send + * + * @exception IllegalStateException if this response has + * already been committed + * @exception IOException if an input/output error occurs + */ + public void sendError(int status) + throws IOException { + sendError(status, null); + } + + + /** + * Send an error response with the specified status and message. + * + * @param status HTTP status code to send + * @param message Corresponding message to send + * + * @exception IllegalStateException if this response has + * already been committed + * @exception IOException if an input/output error occurs + */ + public void sendError(int status, String message) + throws IOException { + + if (isCommitted()) + throw new IllegalStateException + ("isCommitted"); + + // Ignore any call from an included servlet + if (included) + return; + + setError(); + + getHttpResponse().setStatus(status); + getHttpResponse().setMessage(message); + + // Clear any data content that has been buffered + resetBuffer(); + + // Cause the response to be finished (from the application perspective) + String statusPage = req.getContext().findStatusPage(status); + + if (statusPage != null) { + req.getContext().handleStatusPage(req, this, status, statusPage); + } else { + // Send a default message body. + // TODO: maybe other mechanism to customize default. + defaultStatusPage(status, message); + } + setSuspended(true); + } + + /** + * Default handler for status code != 200 + */ + void defaultStatusPage(int status, String message) + throws IOException { + setContentType("text/html"); + if (status > 400 && status < 600) { + if (!getHttpResponse().isCommitted()) { + resetBuffer(); + getOutputBuffer().write("

Status: " + + status + "

Message: " + message + + "

"); + getOutputBuffer().flush(); + } + } + } + + + + /** + * Send a temporary redirect to the specified redirect location URL. + * + * @param location Location URL to redirect to + * + * @exception IllegalStateException if this response has + * already been committed + * @exception IOException if an input/output error occurs + */ + public void sendRedirect(String location) + throws IOException { + + if (isCommitted()) + throw new IllegalStateException + ("isCommitted"); + + // Ignore any call from an included servlet + if (included) + return; + + // Clear any data content that has been buffered + resetBuffer(); + + // Generate a temporary redirect to the specified location + try { + req.getHttpRequest().toAbsolute(location, tmpUrlBuffer); + setStatus(SC_FOUND); + resB.getMimeHeaders().setValue("Location").set(tmpUrlBuffer); + } catch (IllegalArgumentException e) { + setStatus(SC_NOT_FOUND); + } + + // Cause the response to be finished (from the application perspective) + setSuspended(true); + + } + + + /** + * Set the specified date header to the specified value. + * + * @param name Name of the header to set + * @param value Date value to be set + */ + public void setDateHeader(String name, long value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) { + return; + } + + if (format == null) { + format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, + Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + setHeader(name, FastHttpDateFormat.formatDate(value, format)); + + } + + + /** + * Set the specified header to the specified value. + * + * @param name Name of the header to set + * @param value Value to be set + */ + public void setHeader(String name, String value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + getHttpResponse().setHeader(name, value); + + } + + + /** + * Set the specified integer header to the specified value. + * + * @param name Name of the header to set + * @param value Integer value to be set + */ + public void setIntHeader(String name, int value) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + setHeader(name, "" + value); + + } + + + /** + * Set the HTTP status to be returned with this response. + * + * @param status The new HTTP status + */ + public void setStatus(int status) { + setStatus(status, null); + } + + + /** + * Set the HTTP status and message to be returned with this response. + * + * @param status The new HTTP status + * @param message The associated text message + * + * @deprecated As of Version 2.1 of the Java Servlet API, this method + * has been deprecated due to the ambiguous meaning of the message + * parameter. + */ + public void setStatus(int status, String message) { + + if (isCommitted()) + return; + + // Ignore any call from an included servlet + if (included) + return; + + getHttpResponse().setStatus(status); + getHttpResponse().setMessage(message); + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Return true if the specified URL should be encoded with + * a session identifier. This will be true if all of the following + * conditions are met: + *
    + *
  • The request we are responding to asked for a valid session + *
  • The requested session ID was not received via a cookie + *
  • The specified URL points back to somewhere within the web + * application that is responding to this request + *
+ * + * @param location Absolute URL to be validated + */ + protected boolean isEncodeable(final String location) { + + if (location == null) + return (false); + + // Is this an intra-document reference? + if (location.startsWith("#")) + return (false); + + // Are we in a valid session that is not using cookies? + final ServletRequestImpl hreq = req; + final HttpSession session = hreq.getSession(false); + if (session == null) + return (false); + if (hreq.isRequestedSessionIdFromCookie()) + return (false); + + // Is this a valid absolute URL? + URL url = null; + try { + url = new URL(location); + } catch (MalformedURLException e) { + return (false); + } + + // Does this URL match down to (and including) the context path? + if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) + return (false); + if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) + return (false); + int serverPort = hreq.getServerPort(); + if (serverPort == -1) { + if ("https".equals(hreq.getScheme())) + serverPort = 443; + else + serverPort = 80; + } + int urlPort = url.getPort(); + if (urlPort == -1) { + if ("https".equals(url.getProtocol())) + urlPort = 443; + else + urlPort = 80; + } + if (serverPort != urlPort) + return (false); + + String contextPath = req.getContext().getContextPath(); + if (contextPath != null) { + String file = url.getFile(); + if ((file == null) || !file.startsWith(contextPath)) + return (false); + if( file.indexOf(";jsessionid=" + session.getId()) >= 0 ) + return (false); + } + + // This URL belongs to our web application, so it is encodeable + return (true); + + } + + + + /** + * Return the specified URL with the specified session identifier + * suitably encoded. + * + * @param url URL to be encoded with the session id + * @param sessionId Session id to be included in the encoded URL + */ + protected String appendSessionId(String url, String sessionId) { + + if ((url == null) || (sessionId == null)) + return (url); + + String path = url; + String query = ""; + String anchor = ""; + int question = url.indexOf('?'); + if (question >= 0) { + path = url.substring(0, question); + query = url.substring(question); + } + int pound = path.indexOf('#'); + if (pound >= 0) { + anchor = path.substring(pound); + path = path.substring(0, pound); + } + StringBuffer sb = new StringBuffer(path); + if( sb.length() > 0 ) { // jsessionid can't be first. + sb.append(";jsessionid="); + sb.append(sessionId); + } + sb.append(anchor); + sb.append(query); + return (sb.toString()); + + } + + public HttpResponse getHttpResponse() { + return resB; + } + + + void setHttpResponse(HttpResponse resB) { + this.resB = resB; + outputBuffer = resB.getBodyWriter(); + outputStream = new ServletOutputStreamImpl(resB.getBodyOutputStream()); + } + + + +} + diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseIncludeWrapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseIncludeWrapper.java new file mode 100644 index 000000000..50a6c31c1 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseIncludeWrapper.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + + +import java.io.IOException; +import java.util.Locale; + +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + + +/** + * Wrapper around the response object received as parameter to + * RequestDispatcher.include(). + * + * @author Costin Manolache + */ +public class ServletResponseIncludeWrapper extends HttpServletResponseWrapper { + public ServletResponseIncludeWrapper(ServletResponse current) { + super((HttpServletResponse) current); + } + + // Not overriden: + /* + public boolean containsHeader(String name) + public String encodeRedirectUrl(String url) + public String encodeRedirectURL(String url) + public String encodeUrl(String url) + public String encodeURL(String url) + public void flushBuffer() throws IOException + public int getBufferSize() + public String getCharacterEncoding() + public String getContentType() + public Locale getLocale() + public ServletOutputStream getOutputStream() throws IOException + public ServletResponse getResponse() + public PrintWriter getWriter() throws IOException + public boolean isCommitted() + public void resetBuffer() + public void setCharacterEncoding(String charset) + public void setResponse(ServletResponse response) + */ + + public void reset() { + if (getResponse().isCommitted()) + getResponse().reset(); + else + throw new IllegalStateException(); + } + + public void setContentLength(int len) { + } + + public void setContentType(String type) { + } + + public void setLocale(Locale loc) { + } + + public void setBufferSize(int size) { + } + + public void addCookie(Cookie cookie) { + } + + public void addDateHeader(String name, long value) { + } + + public void addHeader(String name, String value) { + } + + public void addIntHeader(String name, int value) { + } + + public void sendError(int sc) throws IOException { + } + + public void sendError(int sc, String msg) throws IOException { + } + + public void sendRedirect(String location) throws IOException { + } + + public void setDateHeader(String name, long value) { + } + + public void setHeader(String name, String value) { + } + + public void setIntHeader(String name, int value) { + } + + public void setStatus(int sc) { + } + + public void setStatus(int sc, String msg) { + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletWriterImpl.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletWriterImpl.java new file mode 100644 index 000000000..aedf17a4e --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletWriterImpl.java @@ -0,0 +1,289 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Coyote implementation of the servlet writer. + * + * @author Remy Maucherat + */ +public class ServletWriterImpl + extends PrintWriter { + + + // -------------------------------------------------------------- Constants + + + private static final char[] LINE_SEP = { '\r', '\n' }; + + + // ----------------------------------------------------- Instance Variables + + + protected Writer ob; + protected boolean error = false; + + + // ----------------------------------------------------------- Constructors + + + public ServletWriterImpl(Writer ob) { + super(ob); + this.ob = ob; + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Prevent cloning the facade. + */ + protected Object clone() + throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + + // -------------------------------------------------------- Package Methods + + + /** + * Clear facade. + */ + void clear() { + ob = null; + } + + + /** + * Recycle. + */ + void recycle() { + error = false; + } + + + // --------------------------------------------------------- Writer Methods + + + public void flush() { + + if (error) + return; + + try { + ob.flush(); + } catch (IOException e) { + error = true; + } + + } + + + public void close() { + + // We don't close the PrintWriter - super() is not called, + // so the stream can be reused. We close ob. + try { + ob.close(); + } catch (IOException ex ) { + ; + } + error = false; + + } + + + public boolean checkError() { + flush(); + return error; + } + + + public void write(int c) { + + if (error) + return; + + try { + ob.write(c); + } catch (IOException e) { + error = true; + } + + } + + + public void write(char buf[], int off, int len) { + + if (error) + return; + + try { + ob.write(buf, off, len); + } catch (IOException e) { + error = true; + } + + } + + + public void write(char buf[]) { + write(buf, 0, buf.length); + } + + + public void write(String s, int off, int len) { + + if (error) + return; + + try { + ob.write(s, off, len); + } catch (IOException e) { + error = true; + } + + } + + + public void write(String s) { + write(s, 0, s.length()); + } + + + // ---------------------------------------------------- PrintWriter Methods + + + public void print(boolean b) { + if (b) { + write("true"); + } else { + write("false"); + } + } + + + public void print(char c) { + write(c); + } + + + public void print(int i) { + write(String.valueOf(i)); + } + + + public void print(long l) { + write(String.valueOf(l)); + } + + + public void print(float f) { + write(String.valueOf(f)); + } + + + public void print(double d) { + write(String.valueOf(d)); + } + + + public void print(char s[]) { + write(s); + } + + + public void print(String s) { + if (s == null) { + s = "null"; + } + write(s); + } + + + public void print(Object obj) { + write(String.valueOf(obj)); + } + + + public void println() { + write(LINE_SEP); + } + + + public void println(boolean b) { + print(b); + println(); + } + + + public void println(char c) { + print(c); + println(); + } + + + public void println(int i) { + print(i); + println(); + } + + + public void println(long l) { + print(l); + println(); + } + + + public void println(float f) { + print(f); + println(); + } + + + public void println(double d) { + print(d); + println(); + } + + + public void println(char c[]) { + print(c); + println(); + } + + + public void println(String s) { + print(s); + println(); + } + + + public void println(Object o) { + print(o); + println(); + } + + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/TomcatLite.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/TomcatLite.java new file mode 100644 index 000000000..ac55b4f5a --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/TomcatLite.java @@ -0,0 +1,512 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.lite.servlet; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.tomcat.integration.ObjectManager; +import org.apache.tomcat.integration.simple.SimpleObjectManager; +import org.apache.tomcat.lite.http.DefaultHttpConnector; +import org.apache.tomcat.lite.http.Dispatcher; +import org.apache.tomcat.lite.http.HttpChannel; +import org.apache.tomcat.lite.http.HttpConnector; +import org.apache.tomcat.lite.http.HttpRequest; +import org.apache.tomcat.lite.http.HttpResponse; +import org.apache.tomcat.lite.http.MappingData; +import org.apache.tomcat.lite.http.HttpChannel.HttpService; +import org.apache.tomcat.lite.io.WrappedException; +import org.apache.tomcat.lite.io.CBuffer; +import org.apache.tomcat.lite.io.MemoryIOConnector; +import org.apache.tomcat.lite.io.CBuffer; + +/** + * Helper allowing to run servlets using Tomcat lite http server. + * + * This is not a full servlet engine - just a small subset allowing + * easier transition or reuse. + * + * @author Costin Manolache + */ +public class TomcatLite implements Runnable { + + static ServletApi api = ServletApi.get(); + + private String serverDirName; + private File workDir; + + // all contexts - hostMapper knows about hostnames and how they are mapped. + // this shouldn't be needed if we want to delegate ctx management + private ArrayList contexts = new ArrayList(); + + URLClassLoader contextParentLoader; + Logger log = Logger.getLogger("TomcatLite"); + //BaseMapper hostMapper = new BaseMapper(); + + // Servlets to preload in each context, configurable from CLI or API + Map preloadServlets = new HashMap(); + Map preloadMappings = new HashMap(); + + Map ctxDefaultInitParam = new HashMap(); + + ObjectManager om; + + private HttpConnector httpConnector; + + static String SERVLETS_PACKAGE = "org.apache.tomcat.servlets"; + + // can be set to ConfigLoader to skip auto-parsing web.xml + private String deployListener = + "org.apache.tomcat.servlets.config.deploy.WarDeploy"; + + int port = 8080; + + public TomcatLite() { + } + + public TomcatLite(ObjectManager om) { + this.setObjectManager(om); + } + + // --------------- start/stop --------------- + + public static ObjectManager defaultObjectManager() { + SimpleObjectManager cfg = new SimpleObjectManager(); + return cfg; + } + /** + * Return the object manager associated with this tomcat. + * If none set, create a minimal one with the default + * values. + */ + public ObjectManager getObjectManager() { + if (om == null) { + om = defaultObjectManager(); + om.bind("TomcatLite", this); + } + return om; + } + + public void setObjectManager(ObjectManager om) { + this.om = om; + } + + public void setPort(int port) { + this.port = port; + } + + public List/**/ getWebapps() { + return contexts; + } + + public URLClassLoader getContextParentLoader() { + if (contextParentLoader == null) { + + ClassLoader parent = this.getClass().getClassLoader(); + contextParentLoader = new URLClassLoader(new URL[] {}, + parent); + + /*if (engineRepo == null) { + engineRepo = new Repository(); + engineRepo.setParentClassLoader(parent); + } + + contextParentLoader = + engineRepo.getClassLoader(); + */ + } + return contextParentLoader; + } + + public void start() throws IOException { + long t0 = System.currentTimeMillis(); + + // start all contexts + // init all contexts + Iterator i1 = contexts.iterator(); + while (i1.hasNext()) { + ServletContextImpl ctx = (ServletContextImpl) i1.next(); + try { + ctx.start(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + long t1 = System.currentTimeMillis(); + log.fine("Engine.start() " + (t1-t0)); + } + + + /** + * Add a context - used for IntrospectionUtils. + * + * ContextPath:ContextBaseDir + */ + public void setContext(String c) throws ServletException { + String[] pathDir = c.split(":", 2); + String base = pathDir[0].trim(); + if (base.length() == 0) { + addServletContext("", pathDir[1], null); + } else { + addServletContext("", pathDir[1], base); + } + } + + public void setServletContexts(List c) throws ServletException { + for (ServletContext ctx: c) { + addServletContext((ServletContextImpl) ctx); + } + } + + public void setDeployListener(String deploy) { + this.deployListener = deploy; + } + + public String getDeployListener() { + return deployListener; + } + + public void setPreload(String servletNameClass) { + String[] nv = servletNameClass.split(":"); + preloadServlets.put(nv[0], nv[1]); + } + + public void addPreload(String servletName, String servletClassName) { + preloadServlets.put(servletName, servletClassName); + } + + public void setDefaultInitParam(String nameValue) { + String[] nv = nameValue.split(":"); + ctxDefaultInitParam.put(nv[0], nv[1]); + } + + public void addDefaultInitParam(String name, String value) { + ctxDefaultInitParam.put(name, value); + } + + public void setPreloadMappings(String servletPath) { + String[] nv = servletPath.split(":"); + preloadMappings.put(nv[0], nv[1]); + } + + public void addPreloadMapping(String servletName, String path) { + preloadMappings.put(servletName, path); + } + + public void stop() { + Iterator i1 = contexts.iterator(); + while (i1.hasNext()) { + ServletContextImpl ctx = (ServletContextImpl) i1.next(); + try { + ctx.destroy(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + try { + stopConnector(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + // -------------- Context add/remove -------------- + + public static String[] DEFAULT_WELCOME = { "index.html" }; + + public void addServletContext(ServletContextImpl ctx) throws ServletException { + ctx.setTomcat(this); + + getDispatcher().addContext(ctx.getHostname(), + ctx.getContextPath(), ctx, null, null, ctxService); + + contexts.add(ctx); + + getObjectManager().bind("ServletContext:" + + ctx.getHostname() + ctx.getContextPath(), + ctx); + + } + + /** + * Add a context. + * + * web.xml will be read as part of init, and the initialization will be + * part of start or lazy. + * + * @param hostname - "" + * if default host, or string to be matched with Host header + * @param basePath = directory where the webapp is installed + * @param path - + * context path, "/" for root, "/examples", etc + * @return a servlet context + * @throws ServletException + */ + public ServletContextImpl addServletContext(String hostname, + String basePath, + String path) + throws ServletException + { + ServletContextImpl ctx = api.newContext(); + ctx.setContextPath(path); + ctx.setBasePath(basePath); + addServletContext(ctx); + return ctx; + } + + public static ServletRequestImpl getFacade(HttpRequest req) { + ServletRequestImpl sreq = (ServletRequestImpl) req.wrapperRequest; + if (sreq == null) { + sreq = api.newRequest(req); + req.wrapperRequest = sreq; + req.nativeRequest = req.getHttpChannel(); // TODO ? + + sreq.getResponse().setHttpResponse(req.getHttpChannel().getResponse()); + } + return sreq; + } + + public void removeServletContext(ServletContext sctx) + throws ServletException + { + ServletContextImpl ctx = (ServletContextImpl) sctx; + // TODO: destroy all servlets and filters + // TODO: clean up any other reference to the context or its loader + notifyRemove(ctx); + } + + public Dispatcher getDispatcher() { + return getHttpConnector().getDispatcher(); + } + + /** + * Required for ServletContext.getContext(uri); + * @throws ServletException + * @throws IOException + */ + public ServletContextImpl getContext(ServletContextImpl impl, String uri) + throws IOException, ServletException { + MappingData md = new MappingData(); + CBuffer hostMB = CBuffer.newInstance(); + CBuffer urlMB = CBuffer.newInstance(); + hostMB.set(impl.getHostname()); + urlMB.set(uri); + getDispatcher().map(hostMB, urlMB, md); + return (ServletContextImpl) md.context; + } + + HttpService ctxService = new HttpService() { + + @Override + public void service(HttpRequest httpReq, HttpResponse httpRes) + throws IOException { + HttpChannel client = httpReq.getHttpChannel(); + + ServletRequestImpl req = getFacade(client.getRequest()); + ServletResponseImpl res = req.getResponse(); + + try { + TomcatLite.this.service(req, res); + + + if (!req.isAsyncStarted()) { + // Recycle the facade objects - + // low level recycled by connector + + // Not an actual flush - only goes to next + res.getOutputBuffer().push(); + + req.recycle(); + } + } catch (IOException ex) { + throw ex; + } catch (Throwable t) { + throw new WrappedException(t); + } + } + }; + + /** + * Service a request. + * The response is not flushed, and we don't recycle at the end. + */ + public void service(ServletRequestImpl req, ServletResponseImpl res) + throws Exception, IOException { + + // TODO: move later + req.parseSessionId(); + + MappingData mapRes = req.getMappingData(); + ServletContextImpl ctx = (ServletContextImpl)mapRes.context; + try { + // context wrapper; + mapRes.service = null; + + getDispatcher().map(ctx.getContextMap(), req.getHttpRequest().decodedURI(), mapRes); + + // Possible redirect + CBuffer redirectPathMB = mapRes.redirectPath; + if (redirectPathMB.length() != 0) { + CBuffer redirectPath = CBuffer.newInstance(); + req.getHttpRequest().getUrlEncoding() + .urlEncode(redirectPathMB, + redirectPath, req.getHttpRequest().getCharEncoder()); + + String query = req.getQueryString(); + if (req.isRequestedSessionIdFromURL()) { + // This is not optimal, but as this is not very common, it + // shouldn't matter + redirectPath.append(";") + .append(ServletRequestImpl.SESSION_PARAMETER_NAME) + .append("=") + .append(req.getRequestedSessionId()); + } + if (query != null) { + // This is not optimal, but as this is not very common, it + // shouldn't matter + redirectPath.append("?").append(query); + } + res.sendRedirect(redirectPath.toString()); + return; + } + + req.setContext(ctx); + req.parseSessionCookiesId(); + + // bind class loader + Thread.currentThread().setContextClassLoader(ctx.getClassLoader()); + + ServletConfigImpl h = (ServletConfigImpl) mapRes.getServiceObject(); + if (h != null) { + req.setWrapper((ServletConfigImpl)mapRes.getServiceObject()); + h.serviceServlet(ctx, req, res, h, mapRes ); + } + } catch (Throwable t) { + log.log(Level.INFO, ctx.contextPath + ": " + req.getRequest() + + ": User exception in servlet ", t); + } finally { + if(mapRes != null ) + mapRes.recycle(); + } + } + + + // ------------ Notifications for JMX ---------------- + + void notifyAdd(Object o) { + } + + void notifyRemove(Object o) { + } + + public void setServerDir(String dir) { + this.serverDirName = dir; + } + + public File getWork() { + if (workDir == null) { + if (serverDirName == null) { + serverDirName = "./"; + } + File rootDirFile = new File(serverDirName); + workDir = new File(rootDirFile, "tomcat-work"); + if (workDir.exists()) { + workDir.mkdirs(); + } + } + return workDir; + } + + /** + * Load all context configs, loads the connector + * + * @throws ServletException + * @throws IOException + */ + public void init() throws ServletException, IOException { + if (contexts.size() == 0) { + setContext("/:./webapps/ROOT"); + } + Iterator i1 = contexts.iterator(); + while (i1.hasNext()) { + ServletContextImpl ctx = (ServletContextImpl) i1.next(); + try { + ctx.loadConfig(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + public void startConnector() throws IOException { + getHttpConnector().setPort(port); + getHttpConnector().start(); + } + + public void stopConnector() throws Exception { + getHttpConnector().stop(); + } + + public void run() { + try { + execute(); + } catch (ServletException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void execute() throws ServletException, IOException { + init(); + start(); + startConnector(); + } + + void setHttpConnector(HttpConnector httpConnector) { + this.httpConnector = httpConnector; + } + + public HttpConnector getHttpConnector() { + if (httpConnector == null) { + httpConnector = DefaultHttpConnector.get(); + } + return httpConnector; + } + + HttpConnector local; + + public HttpConnector getLocalConnector() { + if (local == null) { + local = new HttpConnector(new MemoryIOConnector()); + } + return local; + } +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java new file mode 100644 index 000000000..c44efb44c --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java @@ -0,0 +1,533 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.lite.servlet; + + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.apache.tomcat.servlets.util.RequestUtil; + +/** + * First filter after the context and servlet are mapped. It will add + * web.xml-defined filters. + * + * costin: This is another mapping - done in RequestDispatcher or initial + * mapping. + * Also: StandardHostValve - sets attribute for error pages, + * StandardWrapperValve - mapping per invocation + * + * @author Greg Murray + * @author Remy Maucherat + */ +public class WebappFilterMapper implements Filter { + + + // -------------------------------------------------------------- Constants + + + public static final int ERROR = 1; + public static final Integer ERROR_INTEGER = new Integer(ERROR); + public static final int FORWARD = 2; + public static final Integer FORWARD_INTEGER = new Integer(FORWARD); + public static final int INCLUDE = 4; + public static final Integer INCLUDE_INTEGER = new Integer(INCLUDE); + public static final int REQUEST = 8; + public static final Integer REQUEST_INTEGER = new Integer(REQUEST); + + /** + * Request dispatcher state. + */ + public static final String DISPATCHER_TYPE_ATTR = + "org.apache.catalina.core.DISPATCHER_TYPE"; + + /** + * Request dispatcher path. + */ + public static final String DISPATCHER_REQUEST_PATH_ATTR = + "org.apache.catalina.core.DISPATCHER_REQUEST_PATH"; + + + // ----------------------------------------------------------- Constructors + ServletContextImpl servletContext; + + public WebappFilterMapper() { + } + + public WebappFilterMapper(ServletContextImpl impl) { + servletContext = impl; + } + + public void setServletContext(ServletContextImpl sc) { + servletContext = sc; + } + + // --------------------------------------------------------- Public Methods + + ArrayList filterMaps = new ArrayList(); + + public void addMapping(String filterName, + String url, + String servletName, + String type[], boolean isMatchAfter) { + FilterMap map = new FilterMap(); + map.setURLPattern(url); + map.setFilterName(filterName); + map.setServletName(servletName); + if (isMatchAfter) { + filterMaps.add(map); + } else { + filterMaps.add(0, map); + } + } + + /** + * Construct and return a FilterChain implementation that will wrap the + * execution of the specified servlet instance. If we should not execute + * a filter chain at all, return null. + * + * @param request The servlet request we are processing + * @param servlet The servlet instance to be wrapped + */ + public FilterChainImpl createFilterChain(ServletRequest request, + ServletConfigImpl wrapper, + Servlet servlet) { + + // If there is no servlet to execute, return null + if (servlet == null) + return (null); + + // get the dispatcher type + int dispatcher = -1; + if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) { + Integer dispatcherInt = + (Integer) request.getAttribute(DISPATCHER_TYPE_ATTR); + dispatcher = dispatcherInt.intValue(); + } + String requestPath = null; + Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR); + + if (attribute != null){ + requestPath = attribute.toString(); + } + + HttpServletRequest hreq = null; + if (request instanceof HttpServletRequest) + hreq = (HttpServletRequest)request; + + // Create and initialize a filter chain object + FilterChainImpl filterChain = null; + if ((request instanceof ServletRequestImpl)) { + ServletRequestImpl req = (ServletRequestImpl) request; + filterChain = (FilterChainImpl) req.getFilterChain(); + filterChain.release(); + } else { + // Security: Do not recycle + filterChain = new FilterChainImpl(); + } + + filterChain.setServlet(wrapper, servlet); + + // If there are no filter mappings, we are done + if ((filterMaps.size() == 0)) + return (filterChain); + + // Acquire the information we will need to match filter mappings + String servletName = wrapper.getServletName(); + + int n = 0; + + // TODO(costin): optimize: separate in 2 lists, one for url-mapped, one for + // servlet-name. Maybe even separate list for dispatcher and + // non-dispatcher + + // TODO(costin): optimize: set the FilterConfig in the FilterMap, to + // avoid second hash lookup + + // Add the relevant path-mapped filters to this filter chain + for (int i = 0; i < filterMaps.size(); i++) { + FilterMap filterMap = (FilterMap)filterMaps.get(i); + if (!matchDispatcher(filterMap ,dispatcher)) { + continue; + } + if (!matchFiltersURL(filterMap, requestPath)) + continue; + FilterConfigImpl filterConfig = + servletContext.getFilter(filterMap.getFilterName()); + if (filterConfig == null) { + // FIXME - log configuration problem + continue; + } + filterChain.addFilter(filterConfig); + n++; + } + + // Add filters that match on servlet name second + for (int i = 0; i < filterMaps.size(); i++) { + FilterMap filterMap = (FilterMap)filterMaps.get(i); + if (!matchDispatcher(filterMap ,dispatcher)) { + continue; + } + if (!matchFiltersServlet(filterMap, servletName)) + continue; + FilterConfigImpl filterConfig = + servletContext.getFilter(filterMap.getFilterName()); + if (filterConfig == null) { + ; // FIXME - log configuration problem + continue; + } + filterChain.addFilter(filterConfig); + n++; + } + + // Return the completed filter chain + return (filterChain); + + } + + + // -------------------------------------------------------- Private Methods + + + /** + * Return true if the context-relative request path + * matches the requirements of the specified filter mapping; + * otherwise, return null. + * + * @param filterMap Filter mapping being checked + * @param requestPath Context-relative request path of this request + */ + private boolean matchFiltersURL(FilterMap filterMap, String requestPath) { + + if (requestPath == null) + return (false); + + // Match on context relative request path + String testPath = filterMap.getURLPattern(); + if (testPath == null) + return (false); + + // Case 1 - Exact Match + if (testPath.equals(requestPath)) + return (true); + + // Case 2 - Path Match ("/.../*") + if (testPath.equals("/*")) + return (true); + if (testPath.endsWith("/*")) { + if (testPath.regionMatches(0, requestPath, 0, + testPath.length() - 2)) { + if (requestPath.length() == (testPath.length() - 2)) { + return (true); + } else if ('/' == requestPath.charAt(testPath.length() - 2)) { + return (true); + } + } + return (false); + } + + // Case 3 - Extension Match + if (testPath.startsWith("*.")) { + int slash = requestPath.lastIndexOf('/'); + int period = requestPath.lastIndexOf('.'); + if ((slash >= 0) && (period > slash) + && (period != requestPath.length() - 1) + && ((requestPath.length() - period) + == (testPath.length() - 1))) { + return (testPath.regionMatches(2, requestPath, period + 1, + testPath.length() - 2)); + } + } + + // Case 4 - "Default" Match + return (false); // NOTE - Not relevant for selecting filters + + } + + + /** + * Return true if the specified servlet name matches + * the requirements of the specified filter mapping; otherwise + * return false. + * + * @param filterMap Filter mapping being checked + * @param servletName Servlet name being checked + */ + private boolean matchFiltersServlet(FilterMap filterMap, + String servletName) { + + if (servletName == null) { + return (false); + } else { + if (servletName.equals(filterMap.getServletName())) { + return (true); + } else { + return false; + } + } + + } + + + /** + * Convienience method which returns true if the dispatcher type + * matches the dispatcher types specified in the FilterMap + */ + private boolean matchDispatcher(FilterMap filterMap, int dispatcher) { + switch (dispatcher) { + case FORWARD : { + if (filterMap.getDispatcherMapping() == FilterMap.FORWARD || + filterMap.getDispatcherMapping() == FilterMap.FORWARD_ERROR || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE) { + return true; + } + break; + } + case INCLUDE : { + if (filterMap.getDispatcherMapping() == FilterMap.INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE) { + return true; + } + break; + } + case REQUEST : { + if (filterMap.getDispatcherMapping() == FilterMap.REQUEST || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_FORWARD_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE) { + return true; + } + break; + } + case ERROR : { + if (filterMap.getDispatcherMapping() == FilterMap.ERROR || + filterMap.getDispatcherMapping() == FilterMap.FORWARD_ERROR || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR || + filterMap.getDispatcherMapping() == FilterMap.INCLUDE_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_FORWARD_INCLUDE || + filterMap.getDispatcherMapping() == FilterMap.REQUEST_ERROR_INCLUDE) { + return true; + } + break; + } + } + return false; + } + + + // -------------------- Map elements ----------------------- + + public static class FilterMap implements Serializable { + + + // ------------------------------------------------------------- Properties + + + /** + * The name of this filter to be executed when this mapping matches + * a particular request. + */ + + public static final int ERROR = 1; + public static final int FORWARD = 2; + public static final int FORWARD_ERROR =3; + public static final int INCLUDE = 4; + public static final int INCLUDE_ERROR = 5; + public static final int INCLUDE_ERROR_FORWARD =6; + public static final int INCLUDE_FORWARD = 7; + public static final int REQUEST = 8; + public static final int REQUEST_ERROR = 9; + public static final int REQUEST_ERROR_FORWARD = 10; + public static final int REQUEST_ERROR_FORWARD_INCLUDE = 11; + public static final int REQUEST_ERROR_INCLUDE = 12; + public static final int REQUEST_FORWARD = 13; + public static final int REQUEST_INCLUDE = 14; + public static final int REQUEST_FORWARD_INCLUDE= 15; + + // represents nothing having been set. This will be seen + // as equal to a REQUEST + private static final int NOT_SET = -1; + + private int dispatcherMapping=NOT_SET; + + private String filterName = null; + + /** + * The URL pattern this mapping matches. + */ + private String urlPattern = null; + + /** + * The servlet name this mapping matches. + */ + private String servletName = null; + + + + public String getFilterName() { + return (this.filterName); + } + + public void setFilterName(String filterName) { + this.filterName = filterName; + } + + + public String getServletName() { + return (this.servletName); + } + + public void setServletName(String servletName) { + this.servletName = servletName; + } + + + public String getURLPattern() { + return (this.urlPattern); + } + + public void setURLPattern(String urlPattern) { + this.urlPattern = RequestUtil.URLDecode(urlPattern); + } + + /** + * + * This method will be used to set the current state of the FilterMap + * representing the state of when filters should be applied: + * + * ERROR + * FORWARD + * FORWARD_ERROR + * INCLUDE + * INCLUDE_ERROR + * INCLUDE_ERROR_FORWARD + * REQUEST + * REQUEST_ERROR + * REQUEST_ERROR_INCLUDE + * REQUEST_ERROR_FORWARD_INCLUDE + * REQUEST_INCLUDE + * REQUEST_FORWARD, + * REQUEST_FORWARD_INCLUDE + * + */ + public void setDispatcher(String dispatcherString) { + String dispatcher = dispatcherString.toUpperCase(); + + if (dispatcher.equals("FORWARD")) { + + // apply FORWARD to the global dispatcherMapping. + switch (dispatcherMapping) { + case NOT_SET : dispatcherMapping = FORWARD; break; + case ERROR : dispatcherMapping = FORWARD_ERROR; break; + case INCLUDE : dispatcherMapping = INCLUDE_FORWARD; break; + case INCLUDE_ERROR : dispatcherMapping = INCLUDE_ERROR_FORWARD; break; + case REQUEST : dispatcherMapping = REQUEST_FORWARD; break; + case REQUEST_ERROR : dispatcherMapping = REQUEST_ERROR_FORWARD; break; + case REQUEST_ERROR_INCLUDE : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break; + case REQUEST_INCLUDE : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break; + } + } else if (dispatcher.equals("INCLUDE")) { + // apply INCLUDE to the global dispatcherMapping. + switch (dispatcherMapping) { + case NOT_SET : dispatcherMapping = INCLUDE; break; + case ERROR : dispatcherMapping = INCLUDE_ERROR; break; + case FORWARD : dispatcherMapping = INCLUDE_FORWARD; break; + case FORWARD_ERROR : dispatcherMapping = INCLUDE_ERROR_FORWARD; break; + case REQUEST : dispatcherMapping = REQUEST_INCLUDE; break; + case REQUEST_ERROR : dispatcherMapping = REQUEST_ERROR_INCLUDE; break; + case REQUEST_ERROR_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break; + case REQUEST_FORWARD : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break; + } + } else if (dispatcher.equals("REQUEST")) { + // apply REQUEST to the global dispatcherMapping. + switch (dispatcherMapping) { + case NOT_SET : dispatcherMapping = REQUEST; break; + case ERROR : dispatcherMapping = REQUEST_ERROR; break; + case FORWARD : dispatcherMapping = REQUEST_FORWARD; break; + case FORWARD_ERROR : dispatcherMapping = REQUEST_ERROR_FORWARD; break; + case INCLUDE : dispatcherMapping = REQUEST_INCLUDE; break; + case INCLUDE_ERROR : dispatcherMapping = REQUEST_ERROR_INCLUDE; break; + case INCLUDE_FORWARD : dispatcherMapping = REQUEST_FORWARD_INCLUDE; break; + case INCLUDE_ERROR_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break; + } + } else if (dispatcher.equals("ERROR")) { + // apply ERROR to the global dispatcherMapping. + switch (dispatcherMapping) { + case NOT_SET : dispatcherMapping = ERROR; break; + case FORWARD : dispatcherMapping = FORWARD_ERROR; break; + case INCLUDE : dispatcherMapping = INCLUDE_ERROR; break; + case INCLUDE_FORWARD : dispatcherMapping = INCLUDE_ERROR_FORWARD; break; + case REQUEST : dispatcherMapping = REQUEST_ERROR; break; + case REQUEST_INCLUDE : dispatcherMapping = REQUEST_ERROR_INCLUDE; break; + case REQUEST_FORWARD : dispatcherMapping = REQUEST_ERROR_FORWARD; break; + case REQUEST_FORWARD_INCLUDE : dispatcherMapping = REQUEST_ERROR_FORWARD_INCLUDE; break; + } + } + } + + public int getDispatcherMapping() { + // per the SRV.6.2.5 absence of any dispatcher elements is + // equivelant to a REQUEST value + if (dispatcherMapping == NOT_SET) return REQUEST; + else return dispatcherMapping; + } + + } + + + public void init(FilterConfig filterConfig) throws ServletException { + } + + + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) + throws IOException, ServletException { + } + + + public void destroy() { + } + +}