Based on the code in tomcat.lite, but using the new http connector instead of coyote.
authorcostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 26 Nov 2009 06:45:13 +0000 (06:45 +0000)
committercostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 26 Nov 2009 06:45:13 +0000 (06:45 +0000)
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

20 files changed:
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterChainImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/FilterConfigImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/JspLoader.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/Locale2Charset.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/RequestDispatcherImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi25.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletApi30.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletConfigImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletContextImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletInputStreamImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletOutputStreamImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletReaderImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletRequestWrapperImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletResponseIncludeWrapper.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/ServletWriterImpl.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/TomcatLite.java [new file with mode: 0644]
modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/WebappFilterMapper.java [new file with mode: 0644]

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 (file)
index 0000000..5f9034d
--- /dev/null
@@ -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<FilterConfigImpl> filters =  new ArrayList<FilterConfigImpl>();
+
+    /**
+     * 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 <code>service()</code> 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 (file)
index 0000000..13c08b2
--- /dev/null
@@ -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<String, String> initParams;
+
+    private Class<? extends Filter> filterClass;
+
+    private boolean initDone = false;
+
+    public void setData(String filterName, String filterClass,
+                        Map<String, String> 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<? extends Filter> filterClass2) {
+        this.filterClass = filterClass2;
+    }
+    
+
+    public String getInitParameter(String name) {
+        if (initParams == null) return null;
+        return initParams.get(name);
+    }
+
+    /**
+     * Return an <code>Enumeration</code> 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<? extends Filter>) 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<String> setInitParameters(Map<String, String> 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 (file)
index 0000000..63a301d
--- /dev/null
@@ -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 (file)
index 0000000..649967b
--- /dev/null
@@ -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 (file)
index 0000000..91d37b7
--- /dev/null
@@ -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
+     * <code>&lt;jsp-file&gt;</code> 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<MappingData> localMappingData = 
+        new ThreadLocal<MappingData>();
+
+    /*
+      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.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>: 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 (file)
index 0000000..2290bb6
--- /dev/null
@@ -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 (file)
index 0000000..1e2fe53
--- /dev/null
@@ -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 (file)
index 0000000..684b85c
--- /dev/null
@@ -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<? extends Filter> 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<? extends Servlet> servletClass) {
+                return null;
+            }
+
+
+            @Override
+            public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+                return null;
+            }
+
+
+            @Override
+            public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+                return null;
+            }
+
+            @Override
+            public FilterRegistration getFilterRegistration(String filterName) {
+                return null;
+            }
+
+            @Override
+            public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+                return null;
+            }
+
+            @Override
+            public JspConfigDescriptor getJspConfigDescriptor() {
+                return null;
+            }
+
+            @Override
+            public ServletRegistration getServletRegistration(String servletName) {
+                return null;
+            }
+
+            @Override
+            public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+                return null;
+            }
+
+            @Override
+            public SessionCookieConfig getSessionCookieConfig() {
+                return null;
+            }
+
+            @Override
+            public void setSessionTrackingModes(
+                    EnumSet<SessionTrackingMode> 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<Part> 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<DispatcherType> 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<String> dispatchers = new ArrayList<String>();
+            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<DispatcherType> 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<String> dispatchers = new ArrayList<String>();
+            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<String> setInitParameters(Map<String, String> 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<String> getServletNameMappings() {
+            return null;
+        }
+
+        @Override
+        public Collection<String> getUrlPatternMappings() {
+            return null;
+        }
+
+        @Override
+        public String getClassName() {
+            return null;
+        }
+
+        @Override
+        public String getInitParameter(String name) {
+            return null;
+        }
+
+        @Override
+        public Map<String, String> 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<String> setInitParameters(Map<String, String> initParameters)
+                throws IllegalArgumentException, IllegalStateException {
+            return setInitParameters(initParameters);
+        }
+
+        @Override
+        public void addMappingForServletNames(
+                EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+                String... servletNames) {
+        }
+
+        @Override
+        public void addMappingForUrlPatterns(
+                EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+                String... urlPatterns) {
+        }
+
+        @Override
+        public Collection<String> getServletNameMappings() {
+            return null;
+        }
+
+        @Override
+        public Collection<String> getUrlPatternMappings() {
+            return null;
+        }
+
+        @Override
+        public String getClassName() {
+            return null;
+        }
+
+        @Override
+        public String getInitParameter(String name) {
+            return null;
+        }
+
+        @Override
+        public Map<String, String> 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 (file)
index 0000000..7a0d5aa
--- /dev/null
@@ -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 <b>Wrapper</b> 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<String, String> initParams = new HashMap<String, String>();
+    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 <code>SingleThreadModel</code>.
+     */
+    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 && i<methods.length; i++) {
+            Method m = methods[i];
+           
+            if (m.getName().equals("doGet")) {
+                allow.add("GET");
+                allow.add("HEAD");
+            } else if (m.getName().equals("doPost")) {
+                allow.add("POST");
+            } else if (m.getName().equals("doPut")) {
+                allow.add("PUT");
+            } else if (m.getName().equals("doDelete")) {
+                allow.add("DELETE");
+            }
+        }
+
+        String[] methodNames = new String[allow.size()];
+        return (String[]) allow.toArray(methodNames);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     *  MUST be called before service()
+     *  This method should be called to get the servlet. After 
+     *  service(), dealocate should be called. This deals with STM and
+     *  update use counters.
+     *  
+     *  Normally called from RequestDispatcher and TomcatLite.
+     */
+    public Servlet allocate() throws ServletException {
+        // If we are currently unloading this servlet, throw an exception
+        if (unloading) 
+            throw new ServletException
+              ("allocate() while unloading " + getServletName());
+
+        Servlet servlet = null;
+            // never loaded.
+        synchronized (this) {
+            if (instance == null && !singleThreadModel) {
+                try {
+                    servlet = loadServlet();
+                } catch (ServletException e) {
+                    throw e;
+                } catch (Throwable e) {
+                    throw new ServletException("loadServlet()", e);
+                }
+
+                if (servlet != null && !singleThreadModel) {
+                    setServlet(servlet);
+                }
+            }
+        }
+    
+
+        // If not SingleThreadedModel, return the same instance every time
+        if (instance != null) {
+            countAllocated++;
+            return (instance);
+        }
+        
+        // Simpler policy for ST: unbound number of servlets ( can grow to
+        // one per thread )
+        
+        synchronized (instancePool) {
+            if (instancePool.isEmpty()) {
+                try {
+                    if (servlet != null) {
+                        // this is the first invocation
+                        countAllocated++;
+                        return servlet;
+                    }
+                    countAllocated++;
+                    Servlet newServlet = loadServlet();
+                    log.fine("New STM servet " + newServlet + " " + 
+                            countAllocated);
+                    return newServlet;
+                } catch (ServletException e) {
+                    throw e;
+                } catch (Throwable e) {
+                    throw new ServletException("allocate " + getServletName(),
+                            e);
+                }
+            }
+            log.fine("Get from pool " + instancePool.size() +  " " +
+                    countAllocated);
+            Servlet s = (Servlet) instancePool.pop();
+            countAllocated++;
+            log.fine("After get " + instancePool.size() + " " + s  + 
+                    " " + countAllocated);
+            return s;
+        }
+    }
+
+
+    /**
+     * MUST be called after service().
+     */
+    public void deallocate(Servlet servlet) {
+        // If not SingleThreadModel, no action is required
+        if (!singleThreadModel) {
+            countAllocated--;
+            return;
+        }
+
+        // Unlock and free this instance
+        synchronized (instancePool) {
+            countAllocated--;
+            if (instancePool.contains(servlet)) {
+                System.err.println("Aleady in pool " + servlet + " " 
+                        + instancePool.size()+ " " + countAllocated);
+                return;
+            }
+            System.err.println("return  pool " + servlet +  " " + 
+                    instancePool.size() + " " + countAllocated);
+            instancePool.push(servlet);
+        }
+    }
+
+    public Servlet newInstance() throws ServletException, IOException {
+        String actualClass = servletClassName;
+
+        if (instance != null) {
+            return instance;
+        }
+        if (actualClass == null) {
+            // No explicit name. Try to use the framework
+            if (jspFile != null) {
+                BaseJspLoader mapper = new JspLoader();
+                return mapper.loadProxy(jspFile, ctx, this);
+            }
+            if (actualClass == null) {
+                // Object manager can manage servlets.
+                Servlet res = (Servlet) ctx.getObjectManager().get( servletName +
+                        "-servlet");
+                if (res != null) {
+                    servletClass = res.getClass();
+                    actualClass = servletClass.getName();
+                    return res;
+                }
+            }
+
+            //ctx.getObjectManager().getObject(c);
+            //ctx.getObjectManager().getObject(servletName);
+        }
+            
+        
+        if (servletClass == null) {
+            // set classClass
+            loadClass(actualClass);
+        }
+
+        
+        // jsp-file case. Load the JspProxyServlet instead, with the 
+        // right params. Note the JspProxyServlet is _not_ jasper, 
+        // nor 'jsp' servlet - it is just a proxy with no special 
+        // params. It calls the jsp servlet and jasper to generate the
+        // real class.
+        
+        // this is quite different from catalina, where an ugly kludge was
+        // used to use the same jsp servlet in 2 roles
+        
+        // the jsp proxy is replaced by the web.xml processor
+        
+        if (servletClass == null) {
+            unavailable(null);
+            throw new UnavailableException("ClassNotFound: " + actualClass);
+        }
+        
+        // Instantiate and initialize an instance of the servlet class itself
+        try {
+            return (Servlet) servletClass.newInstance();
+        } catch (ClassCastException e) {
+            unavailable(null);
+            throw new UnavailableException("ClassCast: (Servlet)" + 
+                    actualClass);
+        } catch (Throwable e) {
+            unavailable(null);
+
+            // Added extra log statement for Bugzilla 36630:
+            // http://issues.apache.org/bugzilla/show_bug.cgi?id=36630
+            if(log.isLoggable(Level.FINE)) {
+                log.log(Level.FINE, "newInstance() error: servlet-name: " + 
+                        getServletName() +
+                        " servlet-class: " + actualClass, e);
+            }
+
+            // Restore the context ClassLoader
+            throw new ServletException("newInstance() error " + getServletName() + 
+                    " " + actualClass, e);
+        }
+    }
+
+    /**
+     * Load and initialize an instance of this servlet, if there is not already
+     * at least one initialized instance.  This can be used, for example, to
+     * load servlets that are marked in the deployment descriptor to be loaded
+     * at server startup time.
+     * @throws IOException 
+     */
+    public synchronized Servlet loadServlet() throws ServletException, IOException {
+        // Nothing to do if we already have an instance or an instance pool
+        if (!singleThreadModel && (instance != null))
+            return instance;
+        
+        long t1=System.currentTimeMillis();
+
+        Servlet servlet = newInstance();
+        
+        classLoadTime=(int) (System.currentTimeMillis() -t1);
+        
+        // Call the initialization method of this servlet
+        try {
+            servlet.init(this);
+        } catch (UnavailableException f) {
+            unavailable(f);
+            throw f;
+        } catch (ServletException f) {
+            throw f;
+        } catch (Throwable f) {
+            getServletContext().log("StandardWrapper.Throwable", f );
+            throw new ServletException("Servlet.init()", f);
+        }
+
+        // Register our newly initialized instance
+        singleThreadModel = servlet instanceof SingleThreadModel;
+        if (singleThreadModel) {
+            if (instancePool == null)
+                instancePool = new Stack();
+        }
+        loadTime=System.currentTimeMillis() -t1;
+        
+        return servlet;
+    }
+
+
+    private void loadClass(String actualClass) throws ServletException {
+        // Complain if no servlet class has been specified
+        if (actualClass == null) {
+            unavailable(null);
+            throw new ServletException("servlet-class missing " +  
+                    getServletName());
+        }
+        
+        ClassLoader classLoader = ctx.getClassLoader(); 
+        if (classLoader == null ) 
+            classLoader = this.getClass().getClassLoader();
+        
+        // Load the specified servlet class from the appropriate class loader
+        try {
+            servletClass = classLoader.loadClass(actualClass);
+        } catch (ClassNotFoundException e) {
+            servletClass = null;
+        }
+    }
+
+    /**
+     * Return a String representation of this component.
+     */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        if (ctx != null) {
+            sb.append(ctx.toString());
+            sb.append(".");
+        }
+        sb.append("Servlet[");
+        sb.append(getServletName()).append(" ");
+        sb.append(servletClassName);
+        if (jspFile != null) {
+            sb.append(" jsp=").append(jspFile);
+        }
+        sb.append("]");
+        return (sb.toString());
+    }
+
+
+    /**
+     * Process an UnavailableException, marking this servlet as unavailable
+     * for the specified amount of time.
+     *
+     * @param unavailable The exception that occurred, or <code>null</code>
+     *  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
+     * <code>destroy()</code> 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 <code>null</code>.
+     *
+     * @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<String> 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<String> failed = new HashSet<String>();
+        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<String> setInitParameters(Map<String, String> initParameters)
+            throws IllegalArgumentException, IllegalStateException {
+        return ServletContextImpl.setInitParameters(ctx, initParams, 
+                initParameters);
+    }
+
+    public void setServletClass(Class<? extends Servlet> 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 (file)
index 0000000..78e96cc
--- /dev/null
@@ -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<String, Object> attributes = new HashMap<String, Object>();
+
+    protected transient ArrayList<EventListener> lifecycleListeners = 
+        new ArrayList<EventListener>();
+
+    protected UserSessionManager manager;
+    
+    HashMap<String, FilterConfigImpl> filters = new HashMap<String, FilterConfigImpl>();
+
+    HashMap<String, ServletConfigImpl> servlets = new HashMap<String, ServletConfigImpl>();
+
+    /** 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<EventListener> getListeners() {
+        return lifecycleListeners;
+    }
+    
+    public <T extends EventListener> void  addListener(T listener) {
+      lifecycleListeners.add(listener);
+    }
+
+    public void removeListener(EventListener listener) {
+      lifecycleListeners.remove(listener);
+    }
+
+    public void addListener(Class<? extends EventListener> listenerClass) {
+    }
+
+    public void addListener(String className) {
+    }
+
+    public <T extends EventListener> T createListener(Class<T> 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 <code>null</code>.
+     *
+     * @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 <code>ServletContext</code> 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 <code>RequestDispatcher</code> 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
+     * <code>null</code> 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 <code>null</code> 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 <code>null</code>.
+     *
+     * @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 <code>RequestDispatcher</code> 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 <code>RequestDispatcher</code> 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/*<RequestDispatcherImpl>*/ 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 <code>InputStream</code>.  The
+     * path must be specified according to the rules described under
+     * <code>getResource</code>.  If no such resource can be identified,
+     * return <code>null</code>.
+     *
+     * @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
+     *  <code>log(String, Throwable)</code> 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/*<Integer, List<ServletConfigImpl>>*/ onStartup = 
+            new TreeMap/*<Integer, List<ServletConfigImpl>>*/();
+        while (fI.hasNext()) {
+            ServletConfigImpl fc = (ServletConfigImpl)fI.next();
+            if (fc.getLoadOnStartup() > 0 ) {
+                Integer i = new Integer(fc.getLoadOnStartup());
+                List/*<ServletConfigImpl>*/ old = (List)onStartup.get(i);
+                if (old == null) {
+                    old = new ArrayList/*<ServletConfigImpl>*/();
+                    onStartup.put(i, old);
+                }
+                old.add(fc);
+            }
+        }
+        Iterator keys = onStartup.keySet().iterator();
+        while (keys.hasNext()) {
+            Integer key = (Integer)keys.next();
+            List/*<ServletConfigImpl>*/ 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 extends Filter> T createFilter(Class<T> 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 extends Servlet> T createServlet(Class<T> 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<String, String> params = contextConfig.contextParam;
+       return setInitParameter(this, params, name, value);
+   }
+   
+   static Set<String> setInitParameters(ServletContextImpl ctx, 
+           Map<String, String> params,
+           Map<String, String> initParameters)
+           throws IllegalArgumentException, IllegalStateException {
+       if (ctx.startDone) {
+           throw new IllegalStateException();
+       }
+       Set<String> result = new HashSet<String>();
+       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<String, String> 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 (file)
index 0000000..8f06314
--- /dev/null
@@ -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 (file)
index 0000000..e657174
--- /dev/null
@@ -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 (file)
index 0000000..109a51c
--- /dev/null
@@ -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 (file)
index 0000000..1a1f740
--- /dev/null
@@ -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
+     * <code>&lt;jsp-file&gt;</code> 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
+     * <code>null</code>.
+     *
+     * @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 <code>Enumeration</code> 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 <code>null</code> 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<ServerCookie> 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 <code>null</code>
+     *
+     * @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
+     * <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getReader()</code> 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 <code>Accept-Language</code> 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 <code>Accept-Language</code>
+     * 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 <code>null</code>.  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 <code>Map</code> 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 <code>Map</code> containing parameter names as keys
+     *  and parameter values as map values.
+     */
+    public Map<String, String[]> 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 <code>null</code>.
+     *
+     * @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 <code>BufferedReader</code> around the
+     * servlet input stream returned by <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getInputStream()</code>
+     *  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
+     *  <code>ServletContext.getRealPath()</code>.
+     */
+    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 <code>ServletRequest</code> 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.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>,
+     * not a <code>String</code>, you can modify the URL easily,
+     * for example, to append query parameters.
+     * <p>
+     * This method is useful for creating redirect messages and
+     * for reporting errors.
+     *
+     * @return A <code>StringBuffer</code> 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 <code>true</code> if the session identifier included in this
+     * request came from a cookie.
+     */
+    public boolean isRequestedSessionIdFromCookie() {
+
+        if (requestedSessionId != null)
+            return (requestedSessionCookie);
+        else
+            return (false);
+
+    }
+
+
+    /**
+     * Return <code>true</code> 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
+     *  <code>isRequestedSessionIdFromURL()</code> instead.
+     */
+    public boolean isRequestedSessionIdFromUrl() {
+        return (isRequestedSessionIdFromURL());
+    }
+
+
+    /**
+     * Return <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <security-role-ref> 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 <security-role>
+        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 <code>null</code>.  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 <code>getReader()</code>.
+     *
+     * @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 <code>getContextPath()</code>,
+     * 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 <code>isSecure()</code>
+     * 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
+     * <code>getRemoteUser()</code> 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 (file)
index 0000000..a80f003
--- /dev/null
@@ -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 <code>javax.servlet.http.HttpServletRequest</code>
+ * that transforms an application request object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.http.HttpServletRequestWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.HttpRequest</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationRequest</code> is
+ * duplicated in <code>ApplicationHttpRequest</code>.  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 <code>getAttribute()</code> 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 <code>getAttributeNames()</code> method of the wrapped
+     * request.
+     */
+    public Enumeration getAttributeNames() {
+        return (new AttributeNamesEnumerator());
+    }
+
+
+    /**
+     * Override the <code>removeAttribute()</code> 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 <code>setAttribute()</code> 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 <code>getContextPath()</code> method of the wrapped
+     * request.
+     */
+    public String getContextPath() {
+
+        return (this.contextPath);
+
+    }
+
+
+    /**
+     * Override the <code>getParameter()</code> 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 <code>getParameterMap()</code> method of the
+     * wrapped request.
+     */
+    public Map getParameterMap() {
+
+       parseParameters();
+        return (parameters);
+
+    }
+
+
+    /**
+     * Override the <code>getParameterNames()</code> method of the
+     * wrapped request.
+     */
+    public Enumeration getParameterNames() {
+
+       parseParameters();
+        return (new Enumerator(parameters.keySet()));
+
+    }
+
+
+    /**
+     * Override the <code>getParameterValues()</code> 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 <code>getPathInfo()</code> method of the wrapped request.
+     */
+    public String getPathInfo() {
+
+        return (this.pathInfo);
+
+    }
+
+
+    /**
+     * Override the <code>getQueryString()</code> method of the wrapped
+     * request.
+     */
+    public String getQueryString() {
+
+        return (this.queryString);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURI()</code> method of the wrapped
+     * request.
+     */
+    public String getRequestURI() {
+
+        return (this.requestURI);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURL()</code> 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 <code>getServletPath()</code> 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 (file)
index 0000000..786e38a
--- /dev/null
@@ -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<Cookie> cookies = new ArrayList<Cookie>();
+
+
+    /**
+     * 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 <code>true</code> if we are currently inside a
+     *  RequestDispatcher.include(), else <code>false</code>
+     */
+    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 <code>null</code> 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 <code>getWriter</code> 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 <code>getOutputStream</code> 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 <code>getCharacterEncoding</code> (i.e., the method
+         * just returns the default value <code>ISO-8859-1</code>),
+         * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
+         * (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 <code>null</code> 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<String> getHeaderNames() {
+        return getHttpResponse().getHeaderNames();
+    }
+
+    public Collection<String> 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 <code>sendError()</code>
+     * 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
+     *  <code>encodeRedirectURL()</code> 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
+     *  <code>encodeURL()</code> 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("<html><body><h1>Status: " + 
+                        status + "</h1><h1>Message: " + message + 
+                        "</h1></body></html>");
+                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 <code>true</code> if the specified URL should be encoded with
+     * a session identifier.  This will be true if all of the following
+     * conditions are met:
+     * <ul>
+     * <li>The request we are responding to asked for a valid session
+     * <li>The requested session ID was not received via a cookie
+     * <li>The specified URL points back to somewhere within the web
+     *     application that is responding to this request
+     * </ul>
+     *
+     * @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 (file)
index 0000000..50a6c31
--- /dev/null
@@ -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 (file)
index 0000000..aedf17a
--- /dev/null
@@ -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 (file)
index 0000000..ac55b4f
--- /dev/null
@@ -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<ServletContextImpl> 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<String,String> preloadServlets = new HashMap();
+    Map<String,String> preloadMappings = new HashMap();
+    
+    Map<String,String> 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/*<ServletContextImpl>*/ 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<ServletContext> 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 (file)
index 0000000..c44efb4
--- /dev/null
@@ -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<FilterMap> 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 <code>null</code>.
+     *
+     * @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 <code>true</code> if the context-relative request path
+     * matches the requirements of the specified filter mapping;
+     * otherwise, return <code>null</code>.
+     *
+     * @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 <code>true</code> if the specified servlet name matches
+     * the requirements of the specified filter mapping; otherwise
+     * return <code>false</code>.
+     *
+     * @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() {
+    }
+
+}