Package rogatkin.web

Source Code of rogatkin.web.WebAppServlet$WebAppContextFilter

/* tjws - WebAppServlet.java
* Copyright (C) 1999-2010 Dmitriy Rogatkin.  All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
*  ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
*  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*  $Id: WebAppServlet.java,v 1.93 2011/07/21 03:37:22 dmitriy Exp $
* Created on Dec 14, 2004
*/

package rogatkin.web;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
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 javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.UnavailableException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.Part;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import Acme.Utils;
import Acme.Serve.FileServlet;
import Acme.Serve.Serve;

/**
* @author dmitriy
*
*
*/
public class WebAppServlet extends HttpServlet implements ServletContext {
  public static final String DEF_DEBUG = "tjws.webapp.debug";

  public static final String WAR_NAME_AS_CONTEXTPATH = "tjws.wardeploy.warname-as-context";

  public static final String WAR_DEPLOY_IN_ROOT = "tjws.wardeploy.as-root";

  static final String MULTIPART_ERR_MSQ = "Request isn't multipart/form-data type or processing it is not enabled in deplyment descriptor web.xml";

  protected static final String WEBAPPCLASSLOADER = "rogatkin.webapp.AppClassLoader";

  protected static final String WEBAPPINITTIMEOUT = "tjws.webapp.%s.init.timeout"; // in
                                            // seconds

  List<ServletAccessDescr> servlets;

  List<FilterAccessDescriptor> filters;

  URL[] cpUrls;

  ClassLoader ucl;

  private static AppContextDelegator appContextDelegator;

  File deployDir;

  Serve server;

  int sessionTimeout;

  int initTimeout;

  long lastDeployed;

  boolean noAnnot; // ignore @WebServlet @WebFilter and @WebListener

  ThreadPoolExecutor asyncThreads;

  // / context methods
  protected String contextName;

  protected String contextPath;
 
  protected String description;

  protected Hashtable<String, Object> attributes;

  protected Hashtable<String, String> contextParameters;

  protected List<String> welcomeFiles;

  protected List<ErrorPageDescr> errorPages;

  protected List<EventListener> listeners;

  protected List<EventListener> sessionListeners;

  protected ArrayList<ServletRequestListener> requestListeners;

  protected ArrayList<ServletRequestAttributeListener> attributeListeners;

  protected Map<String, String> mimes;

  protected SessionCookieConfig scc;

  protected Set<SessionTrackingMode> dstm, stm;

  private boolean applyCompression;

  // ** interface to decouple from J2EE features

  public static interface AppContextDelegator {
    /**
     * searches object in context
     *
     * @param name
     * @return
     */
    Object lookup(String name);

    /**
     * loking for a link with name in context
     *
     * @param name
     * @return
     */
    Object lookupLink(String name);

    /**
     * add object to be available from context with name
     *
     * @param name
     * @param obj
     */
    void add(String name, Object obj);
  }

  protected static interface Openable {
    Object getOrigin();
  }

  protected static class MappingEntry {
    String servPath;

    Pattern pathPat;

    MappingEntry(String path, String pattern) {
      servPath = path;
      pathPat = Pattern.compile(pattern);
    }

    public String toString() {
      return String.format("Mapping of %s with regexp pat %s", servPath,
          pathPat);
    }
  }

  protected class ServletAccessDescr implements ServletConfig,
      Comparable<ServletAccessDescr> {
    String className;

    String name;

    Servlet instance;

    MappingEntry[] mapping;

    Map<String, String> initParams;

    String label;

    int loadOnStart;

    boolean asyncSupported;

    String runAs;

    File multipartLocation;

    long multipartMaxFile;

    long multipartMaxRequest;

    int multipartThreshold;

    boolean multipartEnabled;

    String descr;

    long timeToReactivate; // if servlet suspended

    @Override
    public String getServletName() {
      return name;
    }

    public Enumeration getInitParameterNames() {
      return new Enumeration<String>() {
        Iterator<String> i;
        {
          i = initParams.keySet().iterator();
        }

        public boolean hasMoreElements() {
          return i.hasNext();
        }

        public String nextElement() {
          return i.next();
        }
      };
    }

    @Override
    public ServletContext getServletContext() {
      return WebAppServlet.this;
    }

    public String getInitParameter(java.lang.String name) {
      return initParams.get(name);
    }

    public void add(MappingEntry entry) {
      if (mapping == null)
        mapping = new MappingEntry[1];
      else { // can't use copyOf in 1.5
        MappingEntry[] copy = new MappingEntry[mapping.length + 1];
        System.arraycopy(mapping, 0, copy, 0, mapping.length);
        mapping = copy;
      }
      mapping[mapping.length - 1] = entry;
    }

    public int compareTo(ServletAccessDescr sad) {
      return loadOnStart - sad.loadOnStart;
    }

    int matchPath(String path) {
      if (mapping == null)
        return -1;
      for (int i = 0; i < mapping.length; i++)
        if (mapping[i].pathPat.matcher(path).matches())
          return i;
      return -1;
    }

    protected Servlet newInstance() throws ServletException {
      try {
        // System.err.printf("new instance %s %s%n", descr.className,
        // Arrays.toString(ucl.getURLs()));
        Class<Servlet> servletClass = (Class<Servlet>) ucl
            .loadClass(className);
        if (noAnnot == false) {
          WebServlet servletAnnot = servletClass
              .getAnnotation(WebServlet.class);
          if (servletAnnot != null) {
            asyncSupported = servletAnnot.asyncSupported();
          }
        }
        MultipartConfig multipartAnnot = servletClass
            .getAnnotation(MultipartConfig.class);
        if (multipartAnnot != null) {
          multipartEnabled = true;
          if (multipartAnnot.location().length() > 0)
            multipartLocation = new File(multipartAnnot.location());
          multipartThreshold = multipartAnnot.fileSizeThreshold();
          multipartMaxFile = multipartAnnot.maxFileSize();
          multipartMaxRequest = multipartAnnot.maxRequestSize();
        }
        instance = servletClass.newInstance();
        final ServletException[] exHolder = new ServletException[1];
        Thread initThread = new Thread("Init thread of " + contextName) {
          public void run() {
            try {
              instance.init(ServletAccessDescr.this);
            } catch (ServletException se) {
              exHolder[0] = se;
            }
          }
        };
        initThread.start();
        initThread.join(initTimeout * 1000);
        if (exHolder[0] == null)
          if (initThread.isAlive() == true)
            exHolder[0] = new ServletException(
                String.format(
                    "Initialization of %s in context %s exceeded allocated time (%dsecs)",
                    name, contextName, initTimeout));
        if (exHolder[0] != null) {
          instance = null;
          throw exHolder[0];
        }
        return instance;
        // TODO think about setting back context loader
      } catch (InstantiationException ie) {
        throw new ServletException("Servlet class " + className
            + " can't instantiate. ", ie);
      } catch (IllegalAccessException iae) {
        throw new ServletException("Servlet class " + className
            + " can't access. ", iae);
      } catch (ClassNotFoundException cnfe) {
        log("", cnfe);
        throw new ServletException("Servlet class " + className
            + " not found. ", cnfe);
      } catch (Error e) {
        throw new ServletException(
            "Servlet class "
                + className
                + " can't be instantiated or initialized due an error.",
            e);
      } catch (Throwable t) {
        if (t instanceof ThreadDeath)
          throw (ThreadDeath) t;
        throw new ServletException(
            "Servlet class "
                + className
                + " can't be instantiated or initialized due an exception.",
            t);
      }
    }

    public String toString() {
      return "Servlet " + name + " class " + className + " path/patern "
          + Arrays.toString(mapping) + " init" + initParams
          + " inst " + instance;
    }
  }

  protected class FilterAccessDescriptor extends ServletAccessDescr implements
      FilterConfig {
    String[] servletNames;

    Filter filterInstance;

    DispatcherType[] dispatchTypes;

    public java.lang.String getFilterName() {
      return name;
    }

    public void add(String name) {
      // note the local name shadows name as class memeber
      if (servletNames == null)
        servletNames = new String[1];
      else
        servletNames = Utils.copyOf(servletNames,
            servletNames.length + 1);
      servletNames[servletNames.length - 1] = name;
    }

    public void add(DispatcherType dispatcher) {
      if (dispatchTypes == null)
        dispatchTypes = new DispatcherType[1];
      else {
        DispatcherType[] copy = new DispatcherType[dispatchTypes.length + 1];
        System.arraycopy(dispatchTypes, 0, copy, 0,
            dispatchTypes.length);
        dispatchTypes = copy;
      }
      dispatchTypes[dispatchTypes.length - 1] = dispatcher;
    }

    int matchServlet(String servletName) {
      if (servletNames == null)
        return -1;
      for (int i = 0; i < this.servletNames.length; i++)
        if (servletNames[i].equals(servletName))
          return i;
      return -1;
    }

    boolean matchDispatcher(DispatcherType dispatcher) {
      if (dispatchTypes == null)
        if (dispatcher.equals(DispatcherType.REQUEST))
          return true;
        else
          return false;
      for (int i = 0; i < dispatchTypes.length; i++)
        if (dispatcher.equals(dispatchTypes[i]))
          return true;
      return false;
    }

    protected Filter newFilterInstance() throws ServletException {
      try {
        Class<Filter> filterClass = (Class<Filter>) ucl
            .loadClass(className);
        WebFilter annot = filterClass.getAnnotation(WebFilter.class);
        if (annot != null)
          asyncSupported = annot.asyncSupported();
        filterInstance = filterClass.newInstance();
        filterInstance.init(this);
      } catch (InstantiationException ie) {
        throw new ServletException("Filter class " + className
            + " can't instantiate. ", ie);
      } catch (IllegalAccessException iae) {
        throw new ServletException("Filter class " + className
            + " can't access. ", iae);
      } catch (ClassNotFoundException cnfe) {
        throw new ServletException("Filter class " + className
            + " not found. ", cnfe);
      }
      return filterInstance;
    }

    public String toString() {
      return String.format(
          "Filter for servlets %s and types %s based on %s",
          Arrays.toString(servletNames),
          Arrays.toString(dispatchTypes), super.toString());
    }
  }

  protected static class ErrorPageDescr {
    String errorPage;

    Class exception;

    int errorCode;

    ErrorPageDescr(String page, String exClass, String code) {
      if (page == null || page.length() == 0 || page.charAt(0) != '/')
        throw new IllegalArgumentException("Error page path '" + page
            + "' must start with '/'");
      if (page.charAt(0) == '/')
        errorPage = page;
      else
        errorPage = "/" + page;
      try {
        exception = Class.forName(exClass);
      } catch (Exception e) {

      }
      try {
        errorCode = Integer.parseInt(code);
      } catch (Exception e) {

      }
    }
  }

  protected WebAppServlet(String context) {
    this.contextPath = "/" + context;
    attributes = new Hashtable<String, Object>();
    contextParameters = new Hashtable<String, String>();
    applyCompression = System.getProperty("tjws.webapp." + context
        + ".compressresponse") != null;
    if (applyCompression == false)
      applyCompression = System
          .getProperty(FileServlet.DEF_USE_COMPRESSION) != null;
    // TODO consider
    // _DEBUG = System.getProperty(getClass().getName() + ".debug") != null;
    String threadPoolSets[] = System
        .getProperty(
            "tjws.webapp." + context + ".threadpoolsets",
            System.getProperty("tjws.webapp.*.threadpoolsets",
                "20,50,300")).split(",");
    if (threadPoolSets.length != 3)
      throw new IllegalArgumentException("Illegal thread pool settings:"
          + System.getProperty("tjws.webapp." + context
              + ".threadpoolsets"));
    asyncThreads = new ThreadPoolExecutor(
        Integer.parseInt(threadPoolSets[0]),
        Integer.parseInt(threadPoolSets[1]),
        Integer.parseInt(threadPoolSets[2]), TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
          public Thread newThread(Runnable r) {
            Thread result = new Thread(r, "Pooled-t-" + contextPath);
            result.setDaemon(true);
            // result.setContextClassLoader(ucl);
            return result;
          }

        });
    System.getProperty("tjws.webapp." + context + ".multipartssets", System
        .getProperty("tjws.webapp.*.multipartssets",
            "$temp,50m,100m,2m"));
  }

  /**
   * Initializes singleton delegator
   *
   * @param acd
   */
  public static void setAppContextDelegator(AppContextDelegator acd) {
    if (appContextDelegator == null)
      appContextDelegator = acd;
  }

  public static WebAppServlet create(File deployDir, String context,
      Serve server, String virtualHost) throws ServletException {
    // TODO add method initFromDeployDirectory and move all static calls
    // there,
    // so it should look like
    // WebAppServlet result = new WebAppServlet(context);
    // result.initFromDeployDirectory(deployDir, server);
    // TODO split also the method on sections of web.xml
    XPath xp = XPathFactory.newInstance().newXPath();
    final WebAppServlet result = new WebAppServlet(context);
    result.server = server;
    try {
      result.makeCP(deployDir); // /web-app
      Node document = (Node) xp.evaluate("/*",
          new InputSource(new FileInputStream(new File(deployDir,
              "WEB-INF/web.xml"))), XPathConstants.NODE);
      // TODO process "web-fragment.xml" as well
      final String namespaceURI = document.getNamespaceURI();
      String prefix = namespaceURI == null ? "" : "j2ee:";
      xp.setNamespaceContext(new NamespaceContext() {
        public String getNamespaceURI(String prefix) {
          // System.err.printf("Resolver called with %s%n", prefix);
          if (prefix == null)
            throw new IllegalArgumentException(
                "Namespace prefix is null.");
          if (namespaceURI == null)
            return XMLConstants.NULL_NS_URI;
          if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))
            return namespaceURI;
          if ("j2ee".equals(prefix))
            return namespaceURI;
          return XMLConstants.NULL_NS_URI;
        }

        public String getPrefix(String arg0) {
          throw new UnsupportedOperationException("getPrefix(" + arg0
              + ");");
        }

        public Iterator getPrefixes(String arg0) {
          throw new UnsupportedOperationException("getPrefixes("
              + arg0 + ");");
        }
      });
      document = (Node) xp.evaluate("//" + prefix + "web-app", document,
          XPathConstants.NODE);
      if ("yes".equals(System.getProperty(WAR_NAME_AS_CONTEXTPATH)) == false)
        result.contextName = (String) xp.evaluate(prefix
            + "display-name", document, XPathConstants.STRING);
      if (result.contextName == null || result.contextName.length() == 0)
        result.contextName = context;
      else
        result.contextPath = "/" + result.contextName;
      if (result.contextName.equals(System.getProperty(WAR_DEPLOY_IN_ROOT
          + (virtualHost == null ? "" : '.' + virtualHost)))) {
        result.log(String.format("Conext %s deployed as root ",
            result.contextName, virtualHost == null ? ""
                : virtualHost == null), null);
        result.contextPath = "";
        result.contextName = "";
      }
      Node metadataAttr = document.getAttributes().getNamedItem(
          "metadata-complete");
      if (metadataAttr != null)
        result.noAnnot = "true".equals(metadataAttr.getTextContent());
      result.description = (String) xp.evaluate(prefix
          + "description", document, XPathConstants.STRING);
      if (result.description == null || result.description.length() == 0)
        result.description = "Web application :"+result.contextName;
      // context parameters
      NodeList nodes = (NodeList) xp.evaluate(prefix + "context-param",
          document, XPathConstants.NODESET);
      int nodesLen = nodes.getLength();
      for (int p = 0; p < nodesLen; p++) {
        result.contextParameters.put(
            (String) xp.evaluate(prefix + "param-name",
                nodes.item(p), XPathConstants.STRING),
            (String) xp.evaluate(prefix + "param-value",
                nodes.item(p), XPathConstants.STRING));
      }
      // session-config <session-timeout>
      Number num = (Number) xp.evaluate(prefix + "session-config/"
          + prefix + "session-timeout", document,
          XPathConstants.NUMBER);
      if (num != null)
        result.sessionTimeout = num.intValue();
      if (result.sessionTimeout < 0)
        result.sessionTimeout = 0;
      else
        result.sessionTimeout *= 60;
      result.initTimeout = 10;
      Integer initTimeout = Integer.getInteger(String.format(
          WEBAPPINITTIMEOUT, result.contextName + "."));

      if (initTimeout == null)
        initTimeout = Integer.getInteger(String.format(
            WEBAPPINITTIMEOUT, "*"));
      if (initTimeout != null)
        result.initTimeout = initTimeout;
      // TODO decide 1) if it is right place 2) do check for Android
      Thread.currentThread().setContextClassLoader(result.ucl);
      try {
        nodes = (NodeList) xp.evaluate(prefix + "resource-env-ref",
            document, XPathConstants.NODESET);
        nodesLen = nodes.getLength();
        for (int i = 0; i < nodesLen; i++) {
          Node n = nodes.item(i);
          result.log(String.format("Processing env-ref-%s", xp
              .evaluate(prefix + "description", n,
                  XPathConstants.STRING)));
          Object link = appContextDelegator == null ? null
              : appContextDelegator.lookupLink((String) xp
                  .evaluate(prefix + "resource-env-ref-name",
                      n, XPathConstants.STRING));
          if (link == null
              || link.getClass()
                  .getName()
                  .equals(xp.evaluate(prefix
                      + "resource-env-ref-type", n,
                      XPathConstants.STRING)) == false)
            result.log(String
                .format("Web container doesn't provide an administered object %s of %s",
                    xp.evaluate(prefix
                        + "resource-env-ref-name", n,
                        XPathConstants.STRING),
                    xp.evaluate(prefix
                        + "resource-env-ref-type", n,
                        XPathConstants.STRING)));
        }
        nodes = (NodeList) xp.evaluate(prefix + "resource-ref",
            document, XPathConstants.NODESET);
        nodesLen = nodes.getLength();
        for (int i = 0; i < nodesLen; i++) {
          Node n = nodes.item(i);
          result.log(String.format("Processing resource-ref-%s", xp
              .evaluate(prefix + "description", n,
                  XPathConstants.STRING)));
          String name = (String) xp.evaluate(prefix + "res-ref-name",
              n, XPathConstants.STRING);
          String type = (String) xp.evaluate(prefix + "res-type", n,
              XPathConstants.STRING);
          String auth = (String) xp.evaluate(prefix + "res-auth", n,
              XPathConstants.STRING);
          String scope = (String) xp.evaluate(prefix
              + "res-sharing-scope", n, XPathConstants.STRING);
          Object res = appContextDelegator == null ? null
              : appContextDelegator.lookup(name);
          if (res == null)
            result.log(String.format("No resource %s is available",
                name));
          else {
            Class typeClass = null;
            if (type != null && type.length() > 0)
              try {
                typeClass = Class.forName(type);
              } catch (ClassNotFoundException cne) {
                result.log(String
                    .format("No definition of class %s found, type check is bypassed",
                        type));
                // TODO res.getClasses()
              }
            if (typeClass != null
                && typeClass.isInstance(res) == false)
              result.log(String.format(
                  "No resource %s of %s is available", name,
                  type));
            else
              result.log(String
                  .format("Confirmed availability of %s of %s authorized by %s in scope of %s",
                      name, type, auth, scope));
          }
        }
        if (appContextDelegator != null) {
          nodes = (NodeList) xp.evaluate(prefix + "env-entry",
              document, XPathConstants.NODESET);
          nodesLen = nodes.getLength();
          for (int i = 0; i < nodesLen; i++) {
            Node n = nodes.item(i);
            result.log(String.format("Processing env-entry-%s", xp
                .evaluate(prefix + "description", n,
                    XPathConstants.STRING)));
            Object value = xp.evaluate(prefix + "env-entry-value",
                n, XPathConstants.STRING);
            if (value != null) {
              String type = (String) xp.evaluate(prefix
                  + "env-entry-type", n,
                  XPathConstants.STRING);
              if (type != null
                  && ("java.lang.String".equals(type)) == false) {
                // TODO can use reflection for shortness,
                // however we should check allowed types
                if ("java.lang.Boolean".equals(type))
                  value = new Boolean((String) value);
                else if ("java.lang.Byte".equals(type))
                  value = new Byte((String) value);
                else if ("java.lang.Character".equals(type)
                    && ((String) value).length() == 1)
                  value = new Character(
                      ((String) value).charAt(0));
                else if ("java.lang.Short".equals(type))
                  value = new Short((String) value);
                else if ("java.lang.Integer".equals(type))
                  value = new Integer((String) value);
                else if ("java.lang.Long".equals(type))
                  value = new Long((String) value);
                else if ("java.lang.Float".equals(type))
                  value = new Float((String) value);
                else if ("java.lang.Double".equals(type))
                  value = new Double((String) value);
              }
              appContextDelegator.add((String) xp.evaluate(prefix
                  + "env-entry-name", n,
                  XPathConstants.STRING), value);
            }
          }
        }
      } catch (Exception e) {
        result.log(
            "A problem in obtaining context, all context related settings will be ignored",
            e);
      }
      // bypass EJB stuff
      if (nodesLen > 0)
        result.log("EJB references are not supported");
      nodes = (NodeList) xp.evaluate(prefix + "ejb-local-ref", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0)
        result.log("Local EJB references are not supported");
      // /////////////////////////////////////////////////////////////////
      // listeners listener-class
      nodes = (NodeList) xp.evaluate(prefix + "listener/" + prefix
          + "listener-class", document, XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0) {
        result.listeners = new ArrayList<EventListener>(nodesLen);
        for (int i = 0; i < nodesLen; i++)
          try {
            EventListener eventListener = (EventListener) result.ucl
                .loadClass(
                    nodes.item(i).getTextContent().trim())
                .newInstance();
            if (eventListener instanceof HttpSessionListener
                || eventListener instanceof HttpSessionAttributeListener) {
              if (result.sessionListeners == null)
                result.sessionListeners = new ArrayList<EventListener>(
                    nodesLen);
              result.sessionListeners
                  .add((EventListener) eventListener);
            }

            if (eventListener instanceof ServletRequestListener) {
              if (result.requestListeners == null)
                result.requestListeners = new ArrayList<ServletRequestListener>(
                    nodesLen);
              result.requestListeners
                  .add((ServletRequestListener) eventListener);
            }

            if (eventListener instanceof ServletRequestAttributeListener) {
              if (result.attributeListeners == null)
                result.attributeListeners = new ArrayList<ServletRequestAttributeListener>(
                    nodesLen);
              result.attributeListeners
                  .add((ServletRequestAttributeListener) eventListener);
            }
            result.listeners.add(eventListener); // because the same
                                // class can
                                // implement
                                // other
                                // listener
                                // interfaces
          } catch (Exception e) {
            result.log("Event listener "
                + nodes.item(i).getTextContent()
                + " can't be created due an exception.", e);
          } catch (Error e) {
            result.log("Event listener "
                + nodes.item(i).getTextContent()
                + " can't be created due an error.", e);
          }
      }
      // restore sessions for this context
      // serve.sessions.restore for the current context

      // notify context listeners
      if (result.listeners != null)
        for (EventListener listener : result.listeners) {
          if (listener instanceof ServletContextListener) {
            final ServletContextListener contListener = (ServletContextListener) listener;
            contListener
                .contextInitialized(new ServletContextEvent(
                    result));
          }
        }
      // process filters
      nodes = (NodeList) xp.evaluate(prefix + "filter", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      result.filters = new ArrayList<FilterAccessDescriptor>(nodesLen);
      for (int i = 0; i < nodesLen; i++) {
        Node n = nodes.item(i);
        FilterAccessDescriptor fad = result.createFilterDescriptor();
        fad.name = (String) xp.evaluate(prefix + "filter-name", n,
            XPathConstants.STRING);
        fad.className = (String) xp.evaluate(prefix + "filter-class",
            n, XPathConstants.STRING);
        if (fad.className == null)
          throw new ServletException(String.format(
              "Filter %s specified without or empty class.",
              fad.name));
        else
          fad.className = fad.className.trim();
        String value = (String) xp.evaluate(prefix + "async-supported",
            n, XPathConstants.STRING);
        if (value.length() > 0) {
          value = value.toUpperCase();
          fad.asyncSupported = "YES".equals(value)
              || "TRUE".equals(value);
        }
        fad.label = (String) xp.evaluate(prefix + "display-name", n,
            XPathConstants.STRING);
        fad.descr = (String) xp.evaluate(prefix + "description", n,
            XPathConstants.STRING);
        NodeList params = (NodeList) xp.evaluate(prefix + "init-param",
            n, XPathConstants.NODESET);
        fad.initParams = new HashMap<String, String>(params.getLength());
        for (int p = 0; p < params.getLength(); p++) {
          fad.initParams.put(
              (String) xp.evaluate(prefix + "param-name",
                  params.item(p), XPathConstants.STRING),
              (String) xp.evaluate(prefix + "param-value",
                  params.item(p), XPathConstants.STRING));
        }
        result.filters.add(fad);
      }
      // process filter's mapping
      for (FilterAccessDescriptor fad : result.filters) {
        nodes = (NodeList) xp.evaluate(prefix + "filter-mapping["
            + prefix + "filter-name=\"" + fad.name + "\"]",
            document, XPathConstants.NODESET);
        nodesLen = nodes.getLength();
        if (nodesLen == 0)
          throw new ServletException(String.format(
              "No mappings were found for the filter %s",
              fad.name));
        for (int i = 0; i < nodesLen; i++) {
          Node n = nodes.item(i);
          NodeList clarifications = (NodeList) xp.evaluate(prefix
              + "url-pattern", n, XPathConstants.NODESET);
          int claLen = clarifications.getLength();
          for (int j = 0; j < claLen; j++) {
            String mapUrl = clarifications.item(j).getTextContent();
            if (mapUrl == null || mapUrl.length() == 0)
              continue;
            fad.add(new MappingEntry(clearPath(mapUrl),
                buildREbyPathPatt(mapUrl)));
          }
          clarifications = (NodeList) xp.evaluate(prefix
              + "dispatcher", n, XPathConstants.NODESET);
          claLen = clarifications.getLength();
          for (int j = 0; j < claLen; j++) {
            String filterType = clarifications.item(j)
                .getTextContent();
            if (filterType == null || filterType.length() == 0)
              fad.add(DispatcherType.REQUEST);
            else
              fad.add(DispatcherType.valueOf(filterType));
          }
          clarifications = (NodeList) xp.evaluate(prefix
              + "servlet-name", n, XPathConstants.NODESET);
          claLen = clarifications.getLength();
          for (int j = 0; j < claLen; j++) {
            // adding servlet name
            fad.add(clarifications.item(j).getTextContent());
          }
        }
        fad.newFilterInstance();
      }
      // servlets
      nodes = (NodeList) xp.evaluate(prefix + "servlet", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      result.servlets = new ArrayList<ServletAccessDescr>(nodesLen + 1); // +jsp
      for (int i = 0; i < nodesLen; i++) {
        Node n = nodes.item(i);
        ServletAccessDescr sad = result.createDescriptor();
        sad.name = (String) xp.evaluate(prefix + "servlet-name", n,
            XPathConstants.STRING);
        sad.className = (String) xp.evaluate(prefix + "servlet-class",
            n, XPathConstants.STRING);
        if (sad.className == null || sad.className.length() == 0) {
          String jspFile = (String) xp.evaluate(prefix + "jsp-file",
              n, XPathConstants.STRING);
          if (jspFile != null) {
            result.log(String
                .format("Not supported servlet option jsp-file %s for %s, ignored.",
                    jspFile, sad.name));
            continue;
          } else
            throw new ServletException(
                String.format(
                    "Servlet %s specified without class or jsp file.",
                    sad.name));
        } else
          sad.className = sad.className.trim();
        sad.label = (String) xp.evaluate(prefix + "display-name", n,
            XPathConstants.STRING);
        sad.descr = (String) xp.evaluate(prefix + "description", n,
            XPathConstants.STRING);
        String loadOnStartVal = (String) xp.evaluate(prefix
            + "load-on-startup", n, XPathConstants.STRING);
        try {
          sad.loadOnStart = Integer.parseInt(loadOnStartVal);
        } catch (NumberFormatException nfe) {
          loadOnStartVal = loadOnStartVal.toUpperCase();
          sad.loadOnStart = "YES".equals(loadOnStartVal)
              || "TRUE".equals(loadOnStartVal) ? 0 : -1;
        }
        String value = (String) xp.evaluate(prefix + "async-supported",
            n, XPathConstants.STRING);
        if (value.length() > 0) {
          value = value.toUpperCase();
          sad.asyncSupported = "YES".equals(value)
              || "TRUE".equals(value);
        }
        sad.runAs = (String) xp.evaluate(prefix + "run-as", n,
            XPathConstants.STRING);
        value = (String) xp.evaluate(prefix + "enabled", n,
            XPathConstants.STRING);

        NodeList params = (NodeList) xp.evaluate(prefix + "init-param",
            n, XPathConstants.NODESET);
        sad.initParams = new HashMap<String, String>(params.getLength());
        for (int p = 0; p < params.getLength(); p++) {
          sad.initParams.put(
              (String) xp.evaluate(prefix + "param-name",
                  params.item(p), XPathConstants.STRING),
              (String) xp.evaluate(prefix + "param-value",
                  params.item(p), XPathConstants.STRING));
        }
        NodeList multiparts = (NodeList) xp.evaluate(prefix
            + "multipart-config", n, XPathConstants.NODESET);
        if (multiparts.getLength() == 1) {
          sad.multipartEnabled = true;
          Node multipart = (Node) multiparts.item(0);
          value = (String) xp.evaluate(prefix + "location",
              multipart, XPathConstants.STRING);
          if (value.length() > 0)
            sad.multipartLocation = new File(value);
          value = (String) xp.evaluate(prefix + "max-file-size",
              multipart, XPathConstants.STRING);
          if (value.length() > 0)
            sad.multipartMaxFile = Long.parseLong(value);
          value = (String) xp.evaluate(prefix + "max-request-size",
              multipart, XPathConstants.STRING);
          if (value.length() > 0)
            sad.multipartMaxRequest = Long.parseLong(value);
          value = (String) xp.evaluate(
              prefix + "file-size-threshold", multipart,
              XPathConstants.STRING);
          if (value.length() > 0)
            sad.multipartThreshold = Integer.parseInt(value);
        }
        NodeList securityRoles = (NodeList) xp.evaluate(prefix
            + "security-role-ref", n, XPathConstants.NODESET);
        NodeList descriptionGroups = (NodeList) xp.evaluate(prefix
            + "descriptionGroup", n, XPathConstants.NODESET);
        result.servlets.add(sad);
      }
      // assure order of initialization
      Collections.sort(result.servlets);
      // get mappings
      ServletAccessDescr wasDefault = null;
      for (ServletAccessDescr sad : result.servlets) {
        nodes = (NodeList) xp.evaluate(prefix + "servlet-mapping["
            + prefix + "servlet-name=\"" + sad.name + "\"]",
            document, XPathConstants.NODESET);
        nodesLen = nodes.getLength();
        // System.err.printf("Found %d mappings for %s%n", nodesLen,
        // sad);
        if (nodesLen == 0) {
          // no mapping at all
          String urlPat = "/" + sad.name + "/*";
          sad.add(new MappingEntry(clearPath(urlPat),
              buildREbyPathPatt(urlPat)));
        } else
          for (int i = 0; i < nodesLen; i++) {
            NodeList maps = (NodeList) xp.evaluate(prefix
                + "url-pattern", nodes.item(i),
                XPathConstants.NODESET);
            int mapsLen = maps.getLength();
            // System.err.printf("Found %d patterns for %s%n",
            // mapsLen, sad);
            if (mapsLen == 0) {
              // mapping with empty pattern
              String urlPat = "/" + sad.name + "/*";
              sad.add(new MappingEntry(clearPath(urlPat),
                  buildREbyPathPatt(urlPat)));
            } else {
              for (int j = 0; j < mapsLen; j++) {
                String urlPat = maps.item(j).getTextContent();
                if (urlPat.equals("/"))
                  if (wasDefault != null)
                    throw new ServletException(
                        "More than one default servlets defined "
                            + sad);
                  else
                    wasDefault = sad;
                sad.add(new MappingEntry(clearPath(urlPat),
                    buildREbyPathPatt(urlPat)));
              }
            }
          }
        // System.err.printf("Servlet %s, path:%s\n", sad,
        // sad.servPath);
        if (sad.loadOnStart >= 0)
          sad.newInstance();
      }
      // additional jsp mapping
      nodes = (NodeList) xp.evaluate(prefix + "jsp-config/" + prefix
          + "jsp-property-group/" + prefix + "url-pattern", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0) {
        List<String> jspPats = new ArrayList<String>(nodesLen);
        for (int i = 0; i < nodesLen; i++) {
          jspPats.add(nodes.item(i).getTextContent());
        }
        result.addJSPServlet(jspPats);
      } else
        result.addJSPServlet(null);
      if (wasDefault != null) {
        // re-add at the end
        result.servlets.remove(wasDefault);
        result.servlets.add(wasDefault);
      }
      // welcome files
      nodes = (NodeList) xp
          .evaluate(prefix + "welcome-file-list/" + prefix
              + "welcome-file", document, XPathConstants.NODESET);
      result.welcomeFiles = new ArrayList<String>(nodes.getLength() + 1);
      nodesLen = nodes.getLength();
      if (nodesLen > 0)
        for (int wfi = 0; wfi < nodesLen; wfi++)
          result.welcomeFiles.add(nodes.item(wfi).getTextContent());
      else {
        result.welcomeFiles.add("index.html");
        result.welcomeFiles.add("index.jsp");
      }
      // error pages
      nodes = (NodeList) xp.evaluate(prefix + "error-page", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0) {
        result.errorPages = new ArrayList<ErrorPageDescr>(nodesLen);
        for (int i = 0; i < nodesLen; i++) {
          Node n = nodes.item(i);
          result.errorPages.add(new WebAppServlet.ErrorPageDescr(
              (String) xp.evaluate(prefix + "location", n,
                  XPathConstants.STRING), (String) xp
                  .evaluate(prefix + "exception-type", n,
                      XPathConstants.STRING), (String) xp
                  .evaluate(prefix + "error-code", n,
                      XPathConstants.STRING)));
        }
      }
      // mime types
      nodes = (NodeList) xp.evaluate(prefix + "mime-mapping", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0) {
        result.mimes = new HashMap<String, String>(nodesLen);
        for (int i = 0; i < nodesLen; i++) {
          Node n = nodes.item(i);
          result.mimes.put(((String) xp.evaluate(
              prefix + "extension", n, XPathConstants.STRING))
              .toLowerCase(), (String) xp.evaluate(prefix
              + "mime-type", n, XPathConstants.STRING));
        }
      }
      // bypass security stuff
      nodes = (NodeList) xp.evaluate(prefix + "security-constraint",
          document, XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0)
        result.log("Security constraints are not supported");
      if (xp.evaluate(prefix + "login-config", document,
          XPathConstants.NODE) != null)
        result.log("Login config is not supported");
      nodes = (NodeList) xp.evaluate(prefix + "security-role", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
      if (nodesLen > 0)
        result.log("Security roles are not supported");
      nodes = (NodeList) xp.evaluate(prefix + "ejb-ref", document,
          XPathConstants.NODESET);
      nodesLen = nodes.getLength();
    } catch (IOException ioe) {
      throw new ServletException("A problem in reading web.xml.", ioe);
    } catch (XPathExpressionException xpe) {
      server.log("", xpe);
      throw new ServletException("A problem in parsing web.xml.", xpe);
    } // finally { // streams will be closed by InputSource
    result.lastDeployed = System.currentTimeMillis();
    return result;
  }

  static <D extends ServletAccessDescr> void addMultiple(NodeList list, D d) {
    // TODO can be solution for more compact code
  }

  static public String buildREbyPathPatt(String pathPat) {
    if (pathPat.equals("/"))
      return "/.*";
    if (pathPat.startsWith("*."))
      return pathPat.replace(".", "\\.").replace("?", ".")
          .replace("*", ".*").replace("|", "\\|"); // +"\\??.*";
    // TODO think more
    int wcp = pathPat.indexOf('*');
    // if (wcp > 0 && pathPat.charAt(wcp - 1) == '/')
    // pathPat = pathPat.substring(0, wcp - 1) + '*';
    pathPat = pathPat.replace(".", "\\.").replace("?", ".")
        .replace("*", ".*");
    if (wcp < 0)
      if (pathPat.endsWith("/") == false)
        pathPat += "/?";
    return pathPat;
  }

  static public String clearPath(String pathMask) {
    if (pathMask.equals("/"))
      return pathMask;
    if (pathMask.startsWith("*."))
      return "/";
    int wcp = pathMask.indexOf('*');
    if (wcp < 0)
      return pathMask;
    if (wcp == 1 && pathMask.charAt(0) == '/')
      return "";
    return pathMask.substring(0, wcp);
  }

  protected static Serve.ServeConnection toServeConnection(Object proxy) {
    if (proxy instanceof Serve.ServeConnection)
      return (Serve.ServeConnection) proxy;
    else if (proxy instanceof Openable) {
      Object servCon = ((Openable) proxy).getOrigin();
      if (servCon instanceof Serve.ServeConnection)
        return (Serve.ServeConnection) servCon;
    }
    return null;
  }

  public void service(ServletRequest req, ServletResponse res)
      throws ServletException, IOException {
    // new Exception("call trace").printStackTrace();
    // TODO check access rights
    Thread.currentThread().setContextClassLoader(ucl);
    if (req.isSecure())
      fillSecureAttrs(req);
    final HttpServletRequest hreq = (HttpServletRequest) req;
    if (this.requestListeners != null) {
      ServletRequestEvent e = new ServletRequestEvent(this, hreq);
      for (ServletRequestListener rlistener : requestListeners)
        rlistener.requestInitialized(e);
    }
    try {
      String path = hreq.getPathInfo();
      // TODO: wrap request to implement methods like
      // getRequestDispatcher()
      // which supports relative path, no leading / means relative to
      // currently called
      if (_DEBUG)
        System.err
            .printf("Full req:%s, ContextPath: %s, ServletPath:%s, pathInfo:%s\n",
                hreq.getRequestURI(), hreq.getContextPath(),
                hreq.getServletPath(), path);
      SimpleFilterChain sfc = new SimpleFilterChain();
      if (path != null) {
        // note a limitation, servlet name can't start with /WEB-INF
        if (path.regionMatches(true, 0, "/WEB-INF", 0,
            "/WEB-INF".length())
            || path.regionMatches(true, 0, "/META-INF", 0,
                "/META-INF".length())) {
          ((HttpServletResponse) res)
              .sendError(HttpServletResponse.SC_NOT_FOUND);
          return;
        }
        for (FilterAccessDescriptor fad : filters)
          if (fad.matchDispatcher(DispatcherType.REQUEST)
              && fad.matchPath(path) >= 0)
            sfc.add(fad);
        for (ServletAccessDescr sad : servlets) {
          if (_DEBUG)
            System.err.println("Trying matching " + path + " to "
                + Arrays.toString(sad.mapping) + " = "
                + sad.matchPath(path));
          int patIndex;
          if ((patIndex = sad.matchPath(path)) >= 0) {
            if (sad.instance == null) {
              if (sad.loadOnStart < 0)
                synchronized (sad) {
                  if (sad.instance == null)
                    sad.newInstance();
                }
              if (sad.instance == null) {
                sad.loadOnStart = Integer.MAX_VALUE; // mark
                                    // unsuccessful
                                    // instantiation
                                    // and
                                    // ban
                                    // the
                                    // servlet?
                ((HttpServletResponse) res)
                    .sendError(
                        HttpServletResponse.SC_GONE,
                        "Servlet "
                            + sad.name
                            + " hasn't been instantiated successfully or has been unloaded.");
                return;
              }
            } else {
              if (sad.timeToReactivate > 0) {
                if (sad.timeToReactivate > System
                    .currentTimeMillis()) {
                  ((HttpServletResponse) res)
                      .setIntHeader(
                          "Retry-After",
                          (int) (sad.timeToReactivate - System
                              .currentTimeMillis()) / 1000 + 1);
                  // ((HttpServletResponse)
                  // res).setDateHeader("Retry-After", new
                  // Date(sad.timeToReactivate));
                  ((HttpServletResponse) res)
                      .sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                  return;
                } else
                  sad.timeToReactivate = 0;
              }
            }
            for (FilterAccessDescriptor fad : filters)
              if (fad.matchDispatcher(DispatcherType.REQUEST)
                  && fad.matchServlet(sad.name) >= 0)
                sfc.add(fad);
            // sfc.add(fad.filterInstance);
            // System.err.println("used:"+
            // sad.servPath+", wanted:"+((WebAppServlet)
            // sad.getServletContext()).contextPath);
            sfc.setFilter(new WebAppContextFilter(
                sad.mapping[patIndex].servPath));
            // add servlet in chain
            sfc.setServlet(sad);
            sfc.reset();
            sfc.doFilter(req, res);
            return;
          }
        }
      } else {
        ((HttpServletResponse) res).sendRedirect(hreq.getRequestURI()
            + "/");
        return;
      }

      // no matching, process as file
      sfc.setFilter(new WebAppContextFilter());
      sfc.setServlet(new HttpServlet() {
        public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
          String path = ((HttpServletRequest) req)
              .getPathTranslated();
          returnFileContent(path, (HttpServletRequest) req,
              (HttpServletResponse) res);
        }
      });
      sfc.reset();
      sfc.doFilter(req, res);
    } finally {
      if (this.requestListeners != null) {
        ServletRequestEvent e = new ServletRequestEvent(this, hreq);
        for (ServletRequestListener rlistener : requestListeners)
          rlistener.requestDestroyed(e);
      }
    }
  }

  protected void fillSecureAttrs(ServletRequest req) {
    Serve.ServeConnection scon = toServeConnection(req);
    if (scon != null) {
      if (scon.getSocket() instanceof SSLSocket) {
        SSLSocket ssocket = (SSLSocket) scon.getSocket();
        SSLSession ssess = ssocket.getSession();
        String cipherSuite = ssess.getCipherSuite();
        req.setAttribute("javax.servlet.request.cipher_suite",
            cipherSuite);
        int cipherBits = 0;
        // TODO cache in session
        if (cipherSuite.indexOf("128") > 0)
          cipherBits = 128;
        else if (cipherSuite.indexOf("40") > 0)
          cipherBits = 40;
        else if (cipherSuite.indexOf("3DES") > 0)
          cipherBits = 168;
        else if (cipherSuite.indexOf("IDEA") > 0)
          cipherBits = 128;
        else if (cipherSuite.indexOf("DES") > 0)
          cipherBits = 56;
        req.setAttribute("javax.servlet.request.key_size", cipherBits);
        try {
          req.setAttribute("javax.servlet.request.X509Certificate",
              ssess.getPeerCertificateChain());
        } catch (SSLPeerUnverifiedException e) {
        }
      }
    } else
      log("Can't obtain an original request for " + req);
  }

  protected SimpleFilterChain buildFilterChain(String servletName,
      String requestPath, DispatcherType filterType) {
    SimpleFilterChain sfc = new SimpleFilterChain();
    // add path filters
    if (requestPath != null)
      for (FilterAccessDescriptor fad : filters)
        if (fad.matchDispatcher(filterType)
            && fad.matchPath(requestPath) >= 0)
          sfc.add(fad);
    /*
     * if (filterType == error) {
     * System.err.printf("JSP error %s ---> %s%n", request
     * .getAttribute("javax.servlet.jsp.jspException"), request
     * .getAttribute("javax.servlet.error.status_code"));
     * ((Throwable)request
     * .getAttribute("javax.servlet.jsp.jspException")).printStackTrace(); }
     */

    // add name filters
    if (servletName != null)
      for (FilterAccessDescriptor fad : filters)
        if (fad.matchDispatcher(DispatcherType.REQUEST)
            && fad.matchServlet(servletName) >= 0)
          sfc.add(fad);
    return sfc;
  }

  protected void returnFileContent(String path, HttpServletRequest req,
      HttpServletResponse res) throws IOException, ServletException {
    // Note : can't call or forward to file servlet since it can be not
    // installed
    File fpath = new File(path);
    if (fpath.isDirectory()) {
      File baseDir = fpath;
      for (String indexPage : welcomeFiles) {
        fpath = new File(baseDir, indexPage);
        if (fpath.exists() && fpath.isFile()) {
          if (indexPage.charAt(0) != '/')
            indexPage = "/" + indexPage;
          RequestDispatcher rd = req.getRequestDispatcher(indexPage);
          if (rd != null) {
            rd.forward(req, res);
            return;
          }
          break;
        }
      }
    }
    if (fpath.exists() == false) {
      res.sendError(res.SC_NOT_FOUND);
      return;
    }
    if (fpath.isFile() == false) {
      res.sendError(res.SC_FORBIDDEN);
      return;
    }

    String temp = getMimeType(fpath.getName());
    res.setContentType(temp);

    long lastMod = fpath.lastModified();
    res.setDateHeader("Last-modified", lastMod);
    String ifModSinceStr = req.getHeader("If-Modified-Since");
    long ifModSince = -1;
    if (ifModSinceStr != null) {
      int semi = ifModSinceStr.indexOf(';');
      if (semi != -1)
        ifModSinceStr = ifModSinceStr.substring(0, semi);
      try {
        ifModSince = DateFormat.getDateInstance().parse(ifModSinceStr)
            .getTime();
      } catch (Exception ignore) {
      }
    }
    if (ifModSince != -1 && ifModSince >= lastMod) {
      res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
      return;
    }
    // TODO add range handling
    boolean doCompress = false;
    if (applyCompression && temp != null && temp.startsWith("text")) {
      if (Utils.isGzipAccepted(req.getHeader("Accept-Encoding")) > 0) {
        res.setHeader("Content-Encoding", "gzip");
        doCompress = true;
      }
    }

    if ("HEAD".equals(req.getMethod())) {
      res.setHeader("Content-Length", Long.toString(fpath.length()));
      return;
    }
    OutputStream os = null;
    InputStream is = null;
    try {
      is = new FileInputStream(fpath);
      os = res.getOutputStream();
      if (doCompress)
        os = new GZIPOutputStream(os);
      else
        res.setHeader("Content-Length", Long.toString(fpath.length()));
      WarRoller.copyStream(is, os);
      if (doCompress)
        ((GZIPOutputStream) os).finish();
    } catch (IllegalStateException ise) {
      // assure length
      res.setHeader("Content-Length", Long.toString(fpath.length()));
      PrintWriter pw = res.getWriter();
      // TODO decide on encoding/charset used by the reader
      String charSetName = res.getCharacterEncoding();
      if (charSetName == null)
        charSetName = Utils.ISO_8859_1;
      Utils.copyStream(new InputStreamReader(is, charSetName), pw);
      // consider Writer is OK to do not close
      // consider underneath stream closing OK
    } finally {
      try {
        is.close();
      } catch (Exception x) {
      }
      try {
        if (os != null)
          os.close();
      } catch (Exception x) {
      }
    }
  }

  protected void addJSPServlet(List<String> patterns) {
    ServletAccessDescr sad = createDescriptor();
    // get class name from serve
    sad.initParams = new HashMap<String, String>(10);
    Map<String, String> arguments = (Map<String, String>) server.arguments;
    sad.className = arguments.get(Serve.ARG_JSP);
    if (sad.className == null) {
      sad.className = "gnu.jspengine.JspServlet";
      sad.initParams.put("repository",
          new File(deployDir, "~~~").getPath());
      sad.initParams.put("debug", System.getProperty(getClass().getName()
          + ".debug") != null ? "yes" : "no");
      sad.initParams.put("classloadername", WEBAPPCLASSLOADER);
    } else {
      String pnpx = sad.className + '.';
      int cnl = pnpx.length();
      String classPath = Utils.calculateClassPath(ucl);
      for (String ipn : arguments.keySet())
        if (ipn.startsWith(pnpx))
          sad.initParams
              .put(ipn.substring(cnl),
                  arguments
                      .get(ipn)
                      .replace("%context%", contextName)
                      .replace("%deploydir%",
                          deployDir.getPath())
                      .replace("%classloader%",
                          WEBAPPCLASSLOADER)
                      .replace("%classpath%", classPath));
    }
    sad.descr = "JSP support servlet";
    sad.label = "JSP";
    sad.loadOnStart = -1;
    sad.name = "jsp";
    String jspPat;
    if (patterns == null || patterns.size() == 0)
      jspPat = "/.*\\.jsp";
    else {
      jspPat = buildREbyPathPatt(patterns.get(0));
      for (int i = 1; i < patterns.size(); i++)
        jspPat += "|" + buildREbyPathPatt(patterns.get(i));
    }
    sad.add(new MappingEntry("/", jspPat));
    servlets.add(sad);
  }

  protected ServletAccessDescr createDescriptor() {
    return new ServletAccessDescr();
  }

  protected FilterAccessDescriptor createFilterDescriptor() {
    return new FilterAccessDescriptor();
  }

  protected void makeCP(File dd) throws IOException {
    deployDir = dd.getCanonicalFile();
    final List<URL> urls = new ArrayList<URL>();
    // add servlet classes
    ClassLoader cl = getClass().getClassLoader();
    while (cl != null) {
      if (cl instanceof URLClassLoader) {
        if (((URLClassLoader) cl)
            .findResource("javax/servlet/jsp/JspPage.class") != null
            || ((URLClassLoader) cl)
                .findResource("javax/servlet/http/HttpServlet.class") != null) {
          for (URL url : ((URLClassLoader) cl).getURLs())
            urls.add(url);
        }
      }
      cl = cl.getParent();
    }
    File classesFile = new File(deployDir, "WEB-INF/classes");
    if (classesFile.exists() && classesFile.isDirectory())
      try {
        urls.add(classesFile.toURL());
      } catch (java.net.MalformedURLException mfe) {

      }
    File libFile = new File(deployDir, "WEB-INF/lib");
    libFile.listFiles(new FileFilter() {
      public boolean accept(File file) {
        String name = file.getName().toLowerCase();
        if (name.endsWith(".jar") || name.endsWith(".zip"))
          try {
            urls.add(file.toURL());
          } catch (java.net.MalformedURLException mfe) {

          }
        return false;
      }
    });
    cpUrls = urls.toArray(new URL[urls.size()]);

    setAttribute(WEBAPPCLASSLOADER,
        ucl = createClassLoader(cpUrls, getClass().getClassLoader()));
    // System.err.println("CP "+urls+"\nLoader:"+ucl);
  }

  ClassLoader createClassLoader(URL[] classPath, ClassLoader parent) {
    String classLoaderClassName = System
        .getProperty(WebApp.DEF_WEBAPP_CLASSLOADER); //WEBAPPCLASSLOADER
    // ClassLoader result = null;
    if (classLoaderClassName != null) {
      try {
        // TODO consider constructor extra parameter PermissionCollection permissionCollection
        return (ClassLoader) Class
            .forName(classLoaderClassName, true, parent)
            .getConstructor(URL[].class, ClassLoader.class)
            .newInstance(classPath, parent);
      } catch (Exception e) {
        log("Creation of custom class loader " + classLoaderClassName
            + " failed", e);
      }
    }
    return new URLClassLoader(cpUrls, getClass().getClassLoader()) {
      @Override
      public URL getResource(String name) {
        URL url = super.getResource(name);
        if (url == null && name.startsWith("/")) {
          url = super.getResource(name.substring(1));
        }
        return url;
      }
    };
  }

  void dispatch(String path, ServletRequest request, ServletResponse response)
      throws ServletException, IOException {
    ((SimpleDispatcher) request.getRequestDispatcher(path)).dispatch(
        request, response, DispatcherType.ASYNC);
  }

  /*
   * protected URL toURL(File file) throws MalformedURLException {
   * System.err.println
   * ("file:/"+file.getAbsolutePath()+(file.isDirectory()?"/":"")); return new
   * URL("file:/"+file.getAbsolutePath()+(file.isDirectory()?"/":"")); }
   */

  // /////////////////////////////////////////////////////////////////////////////////
  // context methods
  @Override
  public String getContextPath() {
    return contextPath;
  }

  @Override
  public String getServletContextName() {
    return contextName;
  }
 
  @Override
  public String getServletInfo() {
    return description;
  }

  @Override
  public String getServletName() {
    return contextName;
  }
 
  @Override
  public void removeAttribute(String name) {
    Object value = attributes.remove(name);
    if (listeners != null)
      for (EventListener listener : listeners)
        if (listener instanceof ServletContextAttributeListener)
          ((ServletContextAttributeListener) listener)
              .attributeRemoved(new ServletContextAttributeEvent(
                  this, name, value));
  }

  @Override
  public void setAttribute(String name, Object object) {
    // log("Set attr:"+name+" to "+object);
    if (object == null) {
      removeAttribute(name);
      return;
    }
    Object oldObj = attributes.put(name, object);
    if (listeners != null)
      for (EventListener listener : listeners) {
        if (listener instanceof ServletContextAttributeListener)
          if (oldObj == null)
            ((ServletContextAttributeListener) listener)
                .attributeAdded(new ServletContextAttributeEvent(
                    this, name, object));
          else
            ((ServletContextAttributeListener) listener)
                .attributeReplaced(new ServletContextAttributeEvent(
                    this, name, object));
      }
  }

  @Override
  public Enumeration getAttributeNames() {
    return attributes.keys();
  }

  @Override
  public Object getAttribute(String name) {
    // log("context: "+this+" return attr:"+name+" as "+attributes.get(name));
    return attributes.get(name);
  }

  @Override
  public String getServerInfo() {
    return "TJWS/J2EE container, Copyright &copy; 2010 Dmitriy Rogatkin";
  }

  @Override
  public ServletContext getServletContext() {
    return this;
  }
 
  public String getRealPath(String path) {
    path = validatePath(path);
    if (path == null)
      return null;
    else
      return new File(deployDir, path).getPath();
  }

  public void log(String msg) {
    server.log((contextName == null ? "" : contextName) + "> " + msg);
  }

  public void log(Exception exception, String msg) {
    server.log(exception, (contextName == null ? "" : contextName) + "> "
        + msg);
  }

  public void log(String message, Throwable throwable) {
    server.log((contextName == null ? "" : contextName) + "> " + message,
        throwable);
  }

  public Enumeration getServletNames() {
    Vector<String> result = new Vector<String>();
    for (ServletAccessDescr sad : servlets)
      result.add(sad.name);
    return result.elements();
  }

  public Enumeration getServlets() {
    Vector<Servlet> result = new Vector<Servlet>();
    for (ServletAccessDescr sad : servlets)
      result.add(sad.instance);
    return result.elements();

  }

  public Servlet getServlet(String name) throws ServletException {
    for (ServletAccessDescr sad : servlets)
      if (name.equals(sad.name))
        return sad.instance;
    throw new ServletException("No servlet " + name);
  }

  public RequestDispatcher getNamedDispatcher(String name) {
    for (ServletAccessDescr sad : servlets)
      if (name.equals(sad.name)) {
        if (sad.instance == null && sad.loadOnStart < 0)
          try {
            sad.newInstance();
          } catch (ServletException se) {
          }
        if (sad.instance != null)
          return new SimpleDispatcher(name, sad.instance);
        else
          break;
      }
    return null;
  }

  public RequestDispatcher getRequestDispatcher(String path) {
    if (_DEBUG)
      System.err.printf("getRequestDispatcher(%s)%n", path);
    if (path == null || path.length() == 0 || path.charAt(0) != '/')
      return null; // path must start with / for call from context
    // look for servlets first
    String clearPath = extractQueryAnchor(path, false);
    for (ServletAccessDescr sad : servlets) {
      if (_DEBUG)
        System.err.printf(
            "For dispatcher trying match %s (%s) %s = %d%n", path,
            clearPath, Arrays.toString(sad.mapping),
            sad.matchPath(clearPath));
      int patIndex;
      if ((patIndex = sad.matchPath(clearPath)) >= 0) {
        if (sad.instance == null && sad.loadOnStart < 0)
          try {
            synchronized (sad) {
              if (sad.instance == null)
                sad.newInstance();
            }
          } catch (ServletException se) {
            log(String.format(
                "Can't instantiate a %s exception %s", sad, se),
                se.getRootCause());
          }
        if (_DEBUG)
          System.err.printf("Found processing instance %s of %s%n",
              sad.instance, sad);
        if (sad.instance != null)
          return new SimpleDispatcher(sad.instance,
              sad.mapping[patIndex].servPath, path);
        else
          return null; // servlet not working
      }
    }
    // no matching servlets, check for resources
    try {
      if (_DEBUG)
        System.err.printf("Dispatching to resource %s%n", path);
      if (getResource(path) == null)
        throw new MalformedURLException(); // check path is valid
      return new SimpleDispatcher(new HttpServlet() {
        public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
          String path;
          if (((HttpServletRequest) req).getAttribute("javax.servlet.include.request_uri") != null)
            path = req
                .getRealPath((String) req
                    .getAttribute("javax.servlet.include.path_info"));
          else
            path = ((HttpServletRequest) req).getPathTranslated();
          if (_DEBUG)
            System.err
                .printf("Dispatched file servlet for %s translated %s%n",
                    path, ((HttpServletRequest) req)
                        .getPathTranslated());
          returnFileContent(path, (HttpServletRequest) req,
              (HttpServletResponse) res);
        }
      }, path);
    } catch (MalformedURLException mfe) {
    }
    return null;
  }

  public InputStream getResourceAsStream(String path) {
    try {
      return getResource(path).openStream();
    } catch (NullPointerException npe) {
      if (_DEBUG)
        System.err.println("URL can't be created for :" + path);
    } catch (IOException ioe) {
      if (_DEBUG)
        ioe.printStackTrace();
    }
    return null;
  }

  public URL getResource(String path) throws MalformedURLException {
    if (path.charAt(0) != '/')
      throw new MalformedURLException("Path: " + path
          + " has to start with '/'");
    path = extractQueryAnchor(path, false);
    try {
      File resFile = new File(getRealPath(path)).getCanonicalFile();
      if (resFile.exists())
        return resFile.toURL();
    } catch (IOException io) {
    }
    return null;
  }

  public Set getResourcePaths(String path) {
    if (path.charAt(0) != '/')
      throw new IllegalArgumentException(
          "getResourcePaths: path parameters must begin with '/'");
    path = extractQueryAnchor(path, false);
    File dir = new File(getRealPath(path));
    if (dir.exists() == false || dir.isDirectory() == false)
      return null;
    Set<String> set = new TreeSet<String>();
    String[] els = dir.list();
    for (String el : els) {
      String fp = path + "/" + el;
      if (new File(getRealPath(fp)).isDirectory())
        fp += "/";
      set.add("/" + fp);
    }
    return set;
  }

  public String getMimeType(String file) {
    if (mimes != null && file != null) {
      int p = file.lastIndexOf('.');
      if (p > 0) {
        String result = mimes.get(file.substring(p).toLowerCase());
        if (result != null)
          return result;
      }
    }
    return server.getMimeType(file);
  }

  public int getMinorVersion() {
    return 0;
  }

  public int getMajorVersion() {
    return 3;
  }

  public ServletContext getContext(String uripath) {
    Servlet servlet = server.getServlet(uripath);
    if (servlet != null)
      return servlet.getServletConfig().getServletContext();
    return null;
  }

  public String getInitParameter(String name) {
    return contextParameters.get(name);
  }

  public Enumeration getInitParameterNames() {
    return contextParameters.keys();
  }

  // ////////////// servlet spec 3.0 ///////////////////////

  /**
   * Gets the minor version of the Servlet specification that the application
   * represented by this ServletContext is based on.
   */
  public int getEffectiveMinorVersion() {
    return 0;
  }

  /**
   * Gets the major version of the Servlet specification that the application
   * represented by this ServletContext is based on.
   *
   */
  public int getEffectiveMajorVersion() {
    return 3;
  }

  /**
   * Sets the context initialization parameter with the given name and value
   * on this ServletContext.
   *
   * @param name
   *            of parameter
   * @param value
   *            of parameter
   * @return true if parameter set
   */
  public boolean setInitParameter(java.lang.String name,
      java.lang.String value) {
    if (contextParameters != null)
      throw new IllegalStateException();
    return false;
  }

  /**
   * Adds the servlet with the given name and class name to this servlet
   * context.
   *
   */
  public ServletRegistration.Dynamic addServlet(String servletName,
      String className) {
    throw new UnsupportedOperationException();
  }

  /**
   * Registers the given servlet instance with this ServletContext under the
   * given servletName.
   *
   */
  public ServletRegistration.Dynamic addServlet(java.lang.String servletName,
      Servlet servlet) {
    throw new UnsupportedOperationException();
  }

  /**
   * Adds the servlet with the given name and class type to this servlet
   * context.
   * <p>
   * The registered servlet may be further configured via the returned
   * ServletRegistration object.
   */
  public ServletRegistration.Dynamic addServlet(java.lang.String servletName,
      java.lang.Class<? extends Servlet> servletClass) {
    throw new UnsupportedOperationException();
  }

  /**
   * Instantiates the given Servlet class.
   * <p>
   * The returned Servlet instance may be further customized before it is
   * registered with this ServletContext via a call to
   * addServlet(String,Servlet).
   */
  public <T extends Servlet> T createServlet(java.lang.Class<T> clazz)
      throws ServletException {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the ServletRegistration corresponding to the servlet with the given
   * servletName.
   *
   */
  public ServletRegistration getServletRegistration(
      java.lang.String servletName) {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets a (possibly empty) Map of the ServletRegistration objects (keyed by
   * servlet name) corresponding to all servlets registered with this
   * ServletContext.
   */
  public Map<String, ? extends ServletRegistration> getServletRegistrations() {
    throw new UnsupportedOperationException();
  }

  /**
   * Adds the filter with the given name and class name to this servlet
   * context.
   *
   */
  public FilterRegistration.Dynamic addFilter(String filterName,
      String className) {
    throw new UnsupportedOperationException();
  }

  /**
   * Registers the given filter instance with this ServletContext under the
   * given filterName.
   *
   */
  public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
    throw new UnsupportedOperationException();
  }

  /**
   * Adds the filter with the given name and class type to this servlet
   * context.
   *
   */
  public FilterRegistration.Dynamic addFilter(String filterName,
      Class<? extends Filter> filterClass) {
    throw new UnsupportedOperationException();
  }

  /**
   * Instantiates the given Filter class.
   *
   */
  public <T extends Filter> T createFilter(Class<T> clazz)
      throws ServletException {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the FilterRegistration corresponding to the filter with the given
   * filterName.
   *
   */
  public FilterRegistration getFilterRegistration(String filterName) {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets a (possibly empty) Map of the FilterRegistration objects (keyed by
   * filter name) corresponding to all filters registered with this
   * ServletContext.
   *
   */
  public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the SessionCookieConfig object through which various properties of
   * the session tracking cookies created on behalf of this ServletContext may
   * be configured
   *
   */
  public SessionCookieConfig getSessionCookieConfig() {
    return scc;
  }

  /**
   * Sets the session tracking modes that are to become effective for this
   * ServletContext
   * <p>
   * Repeated invocations of this method will return the same
   * SessionCookieConfig instance.
   */
  public void setSessionTrackingModes(
      Set<SessionTrackingMode> sessionTrackingModes) {

  }

  /**
   * Gets the session tracking modes that are supported by default for this
   * ServletContext.
   *
   */
  public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
    return dstm;
  }

  /**
   * Gets the session tracking modes that are in effect for this
   * ServletContext
   *
   */
  public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
    return stm;
  }

  /**
   * Adds the listener with the given class name to this ServletContext.
   *
   */
  public void addListener(String className) {
    throw new UnsupportedOperationException();
  }

  /**
   * Adds the given listener to this ServletContext.
   *
   */
  public <T extends EventListener> void addListener(T t) {

  }

  /**
   * Adds a listener of the given class type to this ServletContext.
   * <p>
   * The given listenerClass must implement one or more of the following
   * interfaces:
   * <p>
   * <ul>
   * <li>ServletContextAttributeListener
   * <li>ServletRequestListener
   * <li>ServletRequestAttributeListener
   * <li>HttpSessionListener
   * <li>HttpSessionAttributeListener
   * </ul>
   */
  public void addListener(Class<? extends EventListener> listenerClass) {

  }

  /**
   * Instantiates the given EventListener class.
   *
   */
  public <T extends EventListener> T createListener(Class<T> clazz)
      throws ServletException {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the <jsp-config> related configuration that was aggregated from the
   * web.xml and web-fragment.xml descriptor files of the web application
   * represented by this ServletContext.
   *
   */
  public JspConfigDescriptor getJspConfigDescriptor() {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the class loader of the web application represented by this
   * ServletContext.
   *
   */
  public ClassLoader getClassLoader() {
    // TODO figure out when SecurityException should be thrown
    return ucl;
  }

  /**
   * Declares role names that are tested using isUserInRole.
   *
   */
  public void declareRoles(String... roleNames) {
    throw new UnsupportedOperationException();
  }

  protected void setErrorAttributes(ServletRequest req, int status,
      String msg, String servletName, String requestURI, Throwable t,
      Class eclass) {
    req.setAttribute("javax.servlet.error.status_code", status);
    req.setAttribute("javax.servlet.error.exception_type ", eclass);
    req.setAttribute("javax.servlet.error.message", msg);
    req.setAttribute("javax.servlet.error.exception", t);
    req.setAttribute("javax.servlet.error.request_uri", requestURI);
    req.setAttribute("javax.servlet.error.servlet_name", servletName);
  }

  public static String validatePath(String path) {
    return Utils.canonicalizePath(path);
  }

  public void destroy() {
    Thread.currentThread().setContextClassLoader(ucl);
    if (filters != null)
      for (FilterAccessDescriptor fad : filters)
        if (fad.filterInstance != null)
          fad.filterInstance.destroy();
    for (ServletAccessDescr sad : servlets)
      if (sad.instance != null)
        sad.instance.destroy();
    if (listeners != null)
      for (int i = listeners.size() - 1; i > -1; i--) {
        EventListener listener = listeners.get(i);
        if (listener instanceof ServletContextListener)
          ((ServletContextListener) listener)
              .contextDestroyed(new ServletContextEvent(this));
      }
    Enumeration e = getAttributeNames();
    while (e.hasMoreElements())
      removeAttribute((String) e.nextElement());
    // log("Destroy");
  }

  protected class SimpleDispatcher implements RequestDispatcher {
    Servlet servlet;

    String servletPath;

    String path;

    String named;

    SimpleDispatcher(Servlet s, String p) {
      this(s, null, p);
    }

    SimpleDispatcher(String n, Servlet s) {
      this(s, null, null);
      named = n;
    }

    SimpleDispatcher(Servlet s, String sp, String p) {
      servlet = s;
      path = p;
      servletPath = sp;
      // if (servletPath.length() > 1 && servletPath.endsWith("/"))
      // servletPath = servletPath.substring(0, servletPath.length()-1);
      // ending '/' adjustment done on demand
    }

    @Override
    public String toString() {
      return String.format(
          "Dispatcher for %s path %s name %s servlet %s", servlet,
          path, named, servletPath);
    }

    // //////////////////////////////////////////////////////////////////
    // interface RequestDispatcher

    public void forward(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {
      dispatch(request, response, DispatcherType.FORWARD);
    }

    public void dispatch(ServletRequest request, ServletResponse response,
        DispatcherType dispType) throws ServletException, IOException {
      if (_DEBUG)
        System.err.printf("%s path: %s, servlet: %s%n", dispType
            .equals(DispatcherType.ASYNC) ? "ASYNC_DISPATCH"
            : "FORWARD", path, servlet);
      response.reset(); // drop all previously putting data and headers
      SimpleFilterChain sfc = buildFilterChain(
          named,
          path,
          request.getAttribute("javax.servlet.error.status_code") == null ? dispType
              : DispatcherType.ERROR);
      sfc.setServlet(servlet);
      sfc.reset();
      if (_DEBUG)
        printRequestChain(request);
      // try{Thread.sleep(1000);}catch(Exception e) {}
      sfc.doFilter(new DispatchedRequest((HttpServletRequest) request,
          dispType), response);
      // servlet.service(new DispatchedRequest((HttpServletRequest)
      // request, true), response);
    }

    void printRequestChain(ServletRequest r) {
      if (r instanceof DispatchedRequest) {
        ServletRequest sr = r;
        while (sr instanceof DispatchedRequest) {
          System.err.println("Wrapper req:" + sr);
          sr = ((DispatchedRequest) sr).getRequest();
        }
        System.err.println("Original request:" + sr);
      } else
        System.err.println("Just request:" + r);
    }

    public void include(ServletRequest request,
        final ServletResponse response) throws ServletException,
        java.io.IOException {
      Serve.ServeConnection scon = toServeConnection(response);
      if (scon != null)
        scon.setInInclude(true);
      if (_DEBUG)
        System.err
            .printf("INCLUDE path: %s, servlet: %s, servlet path %s, name:%s%n",
                path, servlet, servletPath, named);
      try {
        SimpleFilterChain sfc = buildFilterChain(named, path,
            DispatcherType.INCLUDE);
        sfc.setServlet(servlet);
        sfc.reset();
        if (_DEBUG)
          printRequestChain(request);
        sfc.doFilter(new DispatchedRequest(
            (HttpServletRequest) request, DispatcherType.INCLUDE),
            new HttpServletResponseWrapper(
                (HttpServletResponse) response) {
              // TODO review match to 2.5, some calls are allowed
              // now
              public void addDateHeader(java.lang.String name,
                  long date) {
              }

              public void setDateHeader(java.lang.String name,
                  long date) {
              }

              public void setHeader(java.lang.String name,
                  java.lang.String value) {
              }

              public void addHeader(java.lang.String name,
                  java.lang.String value) {
              }

              public void setIntHeader(java.lang.String name,
                  int value) {
              }

              public void addIntHeader(java.lang.String name,
                  int value) {
              }

              public void setStatus(int sc) {
              }

              public void setStatus(int sc, java.lang.String sm) {
              }

              public void sendRedirect(java.lang.String location)
                  throws java.io.IOException {
              }

              public void sendError(int sc)
                  throws java.io.IOException {
              }

              public void sendError(int sc, java.lang.String msg)
                  throws java.io.IOException {
              }

              public void reset() {
              }

              public void setLocale(java.util.Locale loc) {
              }

              public void resetBuffer() {
              }

              public void setContentType(java.lang.String type) {
              }

              public void setContentLength(int len) {
              }

              public void setCharacterEncoding(
                  java.lang.String charset) {
              }
            });
      } finally {
        if (scon != null)
          scon.setInInclude(false);
      }
    }

    class DispatchedRequest extends HttpServletRequestWrapper {
      DispatcherType dispType;

      boolean forward;

      DispatchedRequest(HttpServletRequest request,
          DispatcherType dispType) {
        super(request);
        this.dispType = dispType;
        forward = dispType.equals(DispatcherType.FORWARD)
            || dispType.equals(DispatcherType.ASYNC);
        // System.err.printf("Created !!!!1 %s%n", dispType);
      }

      @Override
      public String getPathInfo() {
        if (forward)
          return getPathInfo1();
        return super.getPathInfo();
      }

      public String getPathInfo1() {
        if (path == null)
          return super.getPathInfo();

        if ("/".equals(servletPath))
          return null;
        if (servletPath != null && servletPath.length() == 0
            && "/".equals(path))
          return path;
        int qp = path.indexOf('?');
        if (qp < 0)
          qp = path.indexOf('#');
        int sp = servletPath == null ? -1 : path.indexOf(servletPath);
        if (sp >= 0) {
          sp += servletPath.length()
              - (servletPath.endsWith("/") ? 1 : 0);
          if (_DEBUG)
            System.err
                .printf("FORWARD getPathinfo() path %s, servlet %s, sp %d, qp %d, res %s%n",
                    path, servletPath, sp, qp,
                    path.substring(sp));
          if (qp > sp)
            return path.substring(sp, qp);
          else
            return path.substring(sp);
        }
        if (_DEBUG)
          System.err.printf("FORWARD get pathinfo ret: %s%n", path);
        return path;
      }

      @Override
      public String getPathTranslated() {
        return getRealPath(getPathInfo());
      }

      @Override
      public String getRealPath(String path) {
        return WebAppServlet.this.getRealPath(path);
      }

      @Override
      public String getServletPath() {
        if (forward)
          return getServletPath1();
        return super.getServletPath();
      }

      public String getServletPath1() {
        if (servletPath != null)
          if (servletPath.equals("/"))
            return extractQueryAnchor(path, false);
          else
            return servletPath.endsWith("/") ? servletPath
                .substring(0, servletPath.length() - 1)
                : servletPath;

        return super.getServletPath();
      }

      public String getRequestURI1() {
        if (path == null)
          if (servletPath != null)
            return servletPath;
          else
            return null;
        return contextPath + extractQueryAnchor(path, false);
      }

      @Override
      public String getRequestURI() {
        if (forward)
          return getRequestURI1();
        return super.getRequestURI();
      }

      @Override
      public String getContextPath() {
        return contextPath;
      }

      public String getQueryString1() {
        if (path == null)
          return null;
        return extractQueryAnchor(path, true);
      }

      @Override
      public String getQueryString() {
        if (forward)
          return getQueryString1();
        return super.getQueryString();
      }

      @Override
      public Enumeration getAttributeNames() {
        List<String> attributes = new ArrayList<String>(10);
        if (named == null) {
          if (forward) {
            if (dispType.equals(DispatcherType.ASYNC)) {
              attributes.add(AsyncContext.ASYNC_CONTEXT_PATH);
              attributes.add(AsyncContext.ASYNC_PATH_INFO);
              attributes.add(AsyncContext.ASYNC_QUERY_STRING);
              attributes.add(AsyncContext.ASYNC_REQUEST_URI);
              attributes.add(AsyncContext.ASYNC_SERVLET_PATH);
            } else {
              attributes.add("javax.servlet.forward.request_uri");
              attributes
                  .add("javax.servlet.forward.context_path");
              attributes
                  .add("javax.servlet.forward.servlet_path");
              attributes.add("javax.servlet.forward.path_info");
              attributes
                  .add("javax.servlet.forward.query_string");
            }
          } else {
            attributes.add("javax.servlet.include.request_uri");
            attributes.add("javax.servlet.include.path_info");
            attributes.add("javax.servlet.include.context_path");
            attributes.add("javax.servlet.include.servlet_path");
            attributes.add("javax.servlet.include.query_string");
          }
        }
        Enumeration e = super.getAttributeNames();
        while (e.hasMoreElements())
          attributes.add((String) e.nextElement());
        return Collections.enumeration(attributes);
      }

      @Override
      public Object getAttribute(String name) {
        if (named == null) {
          // System.err.printf("-->requested attr:%s%n", name);
          if (forward) {
            if (dispType.equals(DispatcherType.ASYNC)) {
              if (AsyncContext.ASYNC_REQUEST_URI.equals(name))
                return super.getRequestURI();
              else if (AsyncContext.ASYNC_CONTEXT_PATH
                  .equals(name))
                return super.getContextPath();
              else if (AsyncContext.ASYNC_SERVLET_PATH
                  .equals(name))
                return super.getServletPath();
              else if (AsyncContext.ASYNC_PATH_INFO.equals(name))
                return super.getPathInfo();
              else if (AsyncContext.ASYNC_QUERY_STRING
                  .equals(name))
                return super.getQueryString();
            } else {
              if ("javax.servlet.forward.request_uri"
                  .equals(name))
                return super.getRequestURI();
              else if ("javax.servlet.forward.context_path"
                  .equals(name))
                return super.getContextPath();
              else if ("javax.servlet.forward.servlet_path"
                  .equals(name))
                return super.getServletPath();
              else if ("javax.servlet.forward.path_info"
                  .equals(name))
                return super.getPathInfo();
              else if ("javax.servlet.forward.query_string"
                  .equals(name))
                return super.getQueryString();
              else if ("javax.servlet.include.servlet_path"
                  .equals(name)
                  || "javax.servlet.include.request_uri"
                      .equals(name))
                return null;
            }
          } else {
            if ("javax.servlet.include.request_uri".equals(name))
              return getRequestURI1();
            else if ("javax.servlet.include.path_info".equals(name))
              return getPathInfo1();
            else if ("javax.servlet.include.context_path"
                .equals(name))
              return getContextPath();
            else if ("javax.servlet.include.query_string"
                .equals(name))
              return getQueryString1();
            else if ("javax.servlet.include.servlet_path"
                .equals(name))
              return getServletPath1();
            else if ("javax.servlet.forward.request_uri"
                .equals(name)
                || "javax.servlet.forward.servlet_path"
                    .equals(name))
              return null;
          }
        }
        // System.err.printf("!!!return attr:%s=%s%n", name,
        // super.getAttribute(name));
        return super.getAttribute(name);
      }

      @Override
      public void removeAttribute(String name) {
        if (_DEBUG && name.startsWith("javax.servlet."))
          System.err
              .printf("An attempt to remove systen  ATTR: %s in mode %s = %s%n",
                  name, forward ? "FORWARD" : "INCLUDE",
                  getAttribute(name));
        super.removeAttribute(name);
      }

      // @Override
      // public void setAttribute(String name, Object value) {
      // System.err.printf("!!!Set attr %s=%s%n", name, value);
      // super.setAttribute(name, value);
      // }

      @Override
      public RequestDispatcher getRequestDispatcher(String path) {
        if (_DEBUG)
          System.err.printf("Request %s processing from %s%n", path,
              forward ? "FORWARD" : "INCLUDE");
        if (path.charAt(0) != '/') {
          String sp = getServletPath();
          String pi = getPathInfo();
          if (pi == null) {
            int lsp = sp.lastIndexOf('/');
            if (lsp >= 0)
              path = sp.substring(0, lsp) + '/' + path;
            else
              path = '/' + path;
          } else {
            int lsp = pi.lastIndexOf('/');
            if (lsp >= 0)
              path = sp + pi.substring(0, lsp) + '/' + path;
            else
              path = sp + '/' + path;
          }
          // System.err.printf("DEBUG: sp: %sp, pi: %s, p: %s%n", sp,
          // pi, path);
        }
        return WebAppServlet.this.getRequestDispatcher(path);
      }

      @Override
      public String getParameter(String name) {
        Map<String, String[]> params = createParameters();
        String[] result = params.get(name);
        if (result != null)
          return result[0];
        return super.getParameter(name);
      }

      @Override
      public Map getParameterMap() {
        HashMap<String, String[]> result = new HashMap<String, String[]>();
        result.putAll(createParameters());
        result.putAll(super.getParameterMap());
        return result;
      }

      @Override
      public Enumeration getParameterNames() {
        Map params = getParameterMap();
        Hashtable result = new Hashtable();
        result.putAll(params);
        return result.keys();
      }

      @Override
      public String[] getParameterValues(String name) {
        Map<String, String[]> params = createParameters();
        String[] result = params.get(name);
        if (result != null)
          return result;
        return super.getParameterValues(name);
      }

      // //////////////// servlet spec 3.0 ///////////////////

      /**
       * Use the container login mechanism configured for the
       * ServletContext to authenticate the user making this request.
       *
       */
      public boolean authenticate(HttpServletResponse response)
          throws java.io.IOException, ServletException {
        return super.authenticate(response);
      }

      /**
       * Validate the provided username and password in the password
       * validation realm used by the web container login mechanism
       * configured for the ServletContext.
       *
       */
      public void login(String username, String password)
          throws ServletException {
        super.login(username, password);
      }

      /**
       * Establish null as the value returned when getUserPrincipal,
       * getRemoteUser, and getAuthType is called on the request.
       *
       */
      public void logout() throws ServletException {
        super.logout();
      }

      /**
       * Gets all the Part components of this request, provided that it is
       * of type multipart/form-data.
       */
      public Collection<Part> getParts() throws java.io.IOException,
          ServletException {
        return super.getParts();
      }

      /**
       * Gets the Part with the given name.
       *
       */
      public Part getPart(String name) throws java.io.IOException,
          ServletException {
        return super.getPart(name);
      }

      /**
       * Gets the dispatcher type of this request.
       *
       */
      public DispatcherType getDispatcherType() {
        return forward ? DispatcherType.FORWARD
            : DispatcherType.INCLUDE;
      }

      /**
       * Gets the AsyncContext that was created or reinitialized by the
       * most recent invocation of startAsync() or
       * startAsync(ServletRequest,ServletResponse) on this request.
       *
       * @return the AsyncContext that was created or reinitialized by the
       *         most recent invocation of startAsync() or
       *         startAsync(ServletRequest,ServletResponse) on this
       *         request
       * @throws IllegalStateException
       *             - if this request has not been put into asynchronous
       *             mode, i.e., if neither startAsync() nor
       *             startAsync(ServletRequest,ServletResponse) has been
       *             called
       */
      public AsyncContext getAsyncContext() {
        return super.getAsyncContext();
      }

      /**
       * Puts this request into asynchronous mode, and initializes its
       * AsyncContext with the original (unwrapped) ServletRequest and
       * ServletResponse objects.
       *
       * @return
       * @throws IllegalStateException
       */
      public AsyncContext startAsync() throws IllegalStateException {
        return super.startAsync();
      }

      /**
       * Puts this request into asynchronous mode, and initializes its
       * AsyncContext with the given request and response objects. The
       * ServletRequest and ServletResponse arguments must be the same
       * instances, or instances of ServletRequestWrapper and
       * ServletResponseWrapper that wrap them, that were passed to the
       * service method of the Servlet or the doFilter method of the
       * Filter, respectively, in whose scope this method is being called.
       *
       * @param servletRequest
       * @param servletResponse
       * @return
       * @throws IllegalStateException
       */
      public AsyncContext startAsync(ServletRequest servletRequest,
          ServletResponse servletResponse)
          throws IllegalStateException {
        return super.startAsync(servletRequest, servletResponse);
      }

      /**
       * Checks if this request has been put into asynchronous mode.
       *
       * @return
       */
      public boolean isAsyncStarted() {
        return super.isAsyncStarted();
      }

      protected Map<String, String[]> createParameters() {
        String query = getQueryString();
        if (query != null)
          return Acme.Utils.parseQueryString(query, null);
        return new Hashtable<String, String[]>();
      }

      @Override
      public String toString() {
        return String.format("Dispatching request attached to %s",
            SimpleDispatcher.this);
      }
    }
  }

  // ////////////// Filter methods /////////////////////
  protected class WebAppContextFilter implements Filter {
    String servPathHolder;

    WebAppContextFilter(String servletPath) {
      // TODO move to init()
      if (servletPath != null)
        servPathHolder = servletPath;
      else
        throw new NullPointerException("Servlet path is null");
    }

    WebAppContextFilter() {
      this("/");
    }

    public void init(FilterConfig filterConfig) throws ServletException {
      // no init for the filter
    }

    /**
     * this is mandatory filter converting TJWS base servlet requests to web
     * application context relative request
     */
    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws java.io.IOException, ServletException {
      final HttpServletRequest hreq = (HttpServletRequest) request;
      final HttpServletResponse hres = (HttpServletResponse) response;
      final HttpServletResponse[] proxiedRespHolder = new HttpServletResponse[1];
      // TODO a research if request wrapper is more efficient
      chain.doFilter(
          (HttpServletRequest) Proxy.newProxyInstance(
              javax.servlet.http.HttpServletRequest.class
                  .getClassLoader(),
              new Class[] {
                  javax.servlet.http.HttpServletRequest.class,
                  Openable.class }, new InvocationHandler() {
                AsyncContextImpl asyncCtx;

                boolean asyncEnabled;

                Multipart multiparts;

                public Object invoke(Object proxy,
                    Method method, Object[] args)
                    throws Throwable {
                  String mn = method.getName();
                  if (mn.equals("getServletPath")) {
                    if (_DEBUG)
                      System.err.println("getServletPath() "
                          + extractPath(hreq
                              .getRequestURI(),
                              contextPath,
                              servPathHolder,
                              false));
                    return extractPath(
                        hreq.getRequestURI(),
                        contextPath, servPathHolder,
                        false);
                  } else if (mn.equals("getPathInfo")) {
                    if (_DEBUG)
                      System.err.println("getPathInfo() "
                          + extractPath(hreq
                              .getRequestURI(),
                              contextPath,
                              servPathHolder,
                              true));
                    return extractPath(
                        hreq.getRequestURI(),
                        contextPath, servPathHolder,
                        true);
                  } else if (mn.equals("getRealPath")) {
                    if (_DEBUG)
                      System.err.println("Path:"
                          + args[0]);
                    return getRealPath((String) args[0]);
                  } else if (mn.equals("getPathTranslated")) {
                    return getRealPath(hreq.getPathInfo());
                  } else if (mn
                      .equals("getRequestDispatcher")) {
                    String url = (String) args[0];
                    if (url.charAt(0) != '/') {
                      String bp = extractPath(
                          hreq.getRequestURI(),
                          contextPath,
                          servPathHolder, false);
                      int lsp = bp.lastIndexOf('/');
                      if (lsp > 0)
                        bp = bp.substring(0, lsp);
                      url = bp + '/' + url;
                      if (_DEBUG)
                        System.err
                            .printf("req.getDispatcher(%s), %s %n",
                                url, bp);
                    }
                    return getRequestDispatcher(url);
                  } else if (mn.equals("getContextPath")) {
                    return contextPath;
                  } else if (mn.equals("getSession")) {
                    HttpSession session = (HttpSession) method
                        .invoke(hreq, args);
                    // System.err.println("getsession:"+session);
                    // TODO some overhead is here, context
                    // and listeners will be overloaded each
                    // time
                    // time of accessing session while it's
                    // new
                    if (session instanceof Serve.AcmeSession
                        && (session.getServletContext() == null || session
                            .isNew())) {
                      // System.err.println("set listeners & context");
                      ((Serve.AcmeSession) session)
                          .setListeners(WebAppServlet.this.sessionListeners);
                      ((Serve.AcmeSession) session)
                          .setServletContext(WebAppServlet.this);
                      if (sessionTimeout > 0)
                        session.setMaxInactiveInterval(sessionTimeout);
                    }
                    return session;
                  } else if (mn.equals("startAsync")) { // ////////////////////////
                                      // Async
                                      // ////////////////////////
                    if (asyncEnabled == false)
                      throw new IllegalStateException(
                          "Async mode not supported");
                    if (asyncCtx == null) {
                      if (args == null)
                        asyncCtx = new AsyncContextImpl(
                            (ServletRequest) proxy,
                            proxiedRespHolder[0],
                            (Serve.ServeConnection) hreq);
                      else
                        asyncCtx = new AsyncContextImpl(
                            (ServletRequest) args[0],
                            (ServletResponse) args[1],
                            (Serve.ServeConnection) hreq);
                    }
                    asyncCtx.notifyStart();
                    return asyncCtx;
                  } else if (mn.equals("getServletContext")) {
                    return WebAppServlet.this;
                  } else if (mn.equals("isAsyncSupported")) {
                    return asyncEnabled;
                  } else if (mn.equals("isAsyncStarted")) {
                    return asyncCtx != null;
                  } else if (mn.equals("getAsyncContext")) {
                    if (asyncCtx != null)
                      return asyncCtx;
                    throw new IllegalStateException(
                        "Request has not been set in async mode");
                  } else if (mn.equals("getDispatcherType")) {
                    return DispatcherType.REQUEST;
                  } else if (mn.equals("getParts")) { // ////////////////////
                                    // Multi
                                    // part
                                    // ///////////////////////////
                    if (multiparts != null)
                      return multiparts.getParts();
                    throw new ServletException(
                        MULTIPART_ERR_MSQ);
                  } else if (mn.equals("getPart")) {
                    if (multiparts != null)
                      return multiparts
                          .getPart((String) args[0]);
                    throw new ServletException(
                        MULTIPART_ERR_MSQ);
                  } else if (attributeListeners != null) {
                    if (mn.equals("setAttribute")) {
                      Object av = hreq
                          .getAttribute((String) args[0]);
                      hreq.setAttribute((String) args[0],
                          args[1]);
                      if (av == null) {
                        ServletRequestAttributeEvent e = new ServletRequestAttributeEvent(
                            WebAppServlet.this,
                            hreq, (String) args[0],
                            args[1]);
                        for (ServletRequestAttributeListener sarl : attributeListeners)
                          sarl.attributeAdded(e);
                      } else {
                        ServletRequestAttributeEvent e = new ServletRequestAttributeEvent(
                            WebAppServlet.this,
                            hreq, (String) args[0],
                            av);
                        for (ServletRequestAttributeListener sarl : attributeListeners)
                          sarl.attributeReplaced(e);
                      }
                      return null;
                    } else if (mn.equals("removeAttribute")) {
                      Object av = hreq
                          .getAttribute((String) args[0]);
                      hreq.removeAttribute((String) args[0]);
                      ServletRequestAttributeEvent e = new ServletRequestAttributeEvent(
                          WebAppServlet.this, hreq,
                          (String) args[0], av);
                      for (ServletRequestAttributeListener sarl : attributeListeners)
                        sarl.attributeRemoved(e);
                      return null;
                    } else if (mn.equals("getOrigin")) {
                      Object origin = hreq;
                      while (origin instanceof Openable)
                        origin = ((Openable) origin)
                            .getOrigin();
                      return origin;
                    }
                  }
                  try {
                    return method.invoke(hreq, args);
                  } catch (InvocationTargetException ite) {
                    throw ite.getTargetException();
                  }
                }
              }), // response);
          proxiedRespHolder[0] = (HttpServletResponse) Proxy
              .newProxyInstance(
                  javax.servlet.http.HttpServletResponse.class
                      .getClassLoader(),
                  new Class[] {
                      javax.servlet.http.HttpServletResponse.class,
                      Openable.class },
                  new InvocationHandler() {
                    public Object invoke(Object proxy,
                        Method method, Object[] args)
                        throws Throwable {
                      String mn = method.getName();
                      if (mn.equals("sendError")) {
                        if (errorPages != null)
                          for (ErrorPageDescr epd : errorPages)
                            if (epd.errorCode == ((Integer) args[0])
                                .intValue()) {
                              setErrorAttributes(
                                  hreq,
                                  (Integer) args[0],
                                  args.length > 1 ? (String) args[1]
                                      : "",
                                  getServletName(),
                                  hreq.getRequestURI(),
                                  null, null);
                              // System.err.printf("ERROR Forwarding to %s for %d%n",epd.errorPage,
                              // args[0]);
                              getRequestDispatcher(
                                  epd.errorPage)
                                  .forward(
                                      hreq,
                                      hres);
                              return null;
                            }
                      } else if (mn.equals("getOrigin")) {
                        Object origin = hres;
                        while (origin instanceof Openable)
                          origin = ((Openable) origin)
                              .getOrigin();
                        return origin;

                      } // else if
                        // (mn.equals("sendRedirect")) {
                        // System.err.printf("Redirect to:%s%n",args[0]);
                        // }
                      return method.invoke(hres, args);
                    }
                  }));
    }

    public void destroy() {
      // destroy context filter
    }
  }

  /**
   * This function extract meaningful path or query
   *
   * @param path
   *            path to extract from
   * @param query
   *            true if extract query
   * @return extraction or null
   */
  public static String extractQueryAnchor(String path, boolean query) {
    int qp = path.indexOf('?');
    if (query) {
      if (qp >= 0)
        return path.substring(qp + 1);
      return null;
    }
    int hp = path.indexOf('#');
    if (qp >= 0) {
      if (hp >= 0 && hp < qp)
        return path.substring(0, hp);
      return path.substring(0, qp);
    } else if (hp >= 0)
      return path.substring(0, hp);
    return path;
  }

  /**
   * This function extract certain path from request URI, URI is considered
   * and not decoded
   *
   * @param uri
   *            - URI
   * @param context
   *            - context name
   * @param servlet
   *            - servlet name
   * @param info
   * @return
   */
  static public String extractPath(String uri, String context,
      String servlet, boolean info) throws UnsupportedEncodingException {
    uri = Utils.decode(uri, Serve.UTF8);
    if (_DEBUG)
      System.err
          .printf("Extract path URI: %s, context: %s, servlet: %s, action: %b\n",
              uri, context, servlet, info);
    int cl = context.length();
    int sl = servlet.length();
    int sp = uri.indexOf(servlet, cl);
    if (_DEBUG)
      System.err.printf("servlet pos: %d%n", sp);
    if (sp < 0) {
      // if ("/".equals(servlet))
      // sp = cl-1;
      // else
      sp = cl;
    }
    int pp = uri.indexOf('?', sp); // + sl
    int ph = uri.indexOf('#', sp); // + sl
    if (ph >= 0 && ((pp >= 0 && ph < pp) || pp < 0))
      pp = ph;
    int ip = uri.indexOf('/', sp + sl - (servlet.endsWith("/") ? 1 : 0));
    if (_DEBUG)
      System.err.printf("servlet pos %d, info pos: %d, param pos: %d %n",
          sp, ip, pp);
    if (info == false) {
      if (servlet.equals("/") || ip < 0)
        if (pp > 0)
          return uri.substring(sp, pp);
        else
          return uri.substring(sp);
      if (pp < 0)
        return uri.substring(sp, ip);

      return uri.substring(sp, pp);
    }
    if (servlet.equals("/") || ip < 0 || (pp > 0 && ip > pp))
      return null;
    if (pp < 0)
      return uri.substring(ip);
    return uri.substring(ip, pp);
  }

  protected class SimpleFilterChain implements FilterChain {
    List<FilterAccessDescriptor> filters;

    Iterator<FilterAccessDescriptor> iterator;

    Servlet servlet;

    Filter filter;

    // FilterAccessDescriptor

    Filter nextFilter;

    ServletAccessDescr sad;

    SimpleFilterChain() {
      filters = new ArrayList<FilterAccessDescriptor>();
    }

    public void setFilter(Filter filter) {
      this.filter = filter;
    }

    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
      // TODO decide if wrap in a object returning isAsyncSupported based
      // on asyncSupported
      // for servlet or filter without descriptor asyncSupported = false
      if (nextFilter != null) {
        nextFilter = null;
        filter.doFilter(request, response, this);
      } else if (iterator.hasNext()) {
        FilterAccessDescriptor fad = iterator.next();
        try {
          fad.filterInstance.doFilter(
              applyAsyncSet(request, fad.asyncSupported),
              response, this);
        } catch (UnavailableException ue) {
          if (ue.isPermanent()) {
            synchronized (fad) {
              if (fad.filterInstance != null) {
                fad.filterInstance.destroy();
                fad.filterInstance = null;
              }
            }
          } else {
            fad.timeToReactivate = System.currentTimeMillis()
                + ue.getUnavailableSeconds() * 1000l;
          }
          doFilter(request, response);
          // iterator.remove();
        }
      } else
        // TODO figure out error handler needed for filters, it should
        // also handle UnavailableException
        // call sevlet
        try {
          // new Exception("==RUN AS SERVLET==").printStackTrace();
          servlet.service(
              applyAsyncSet(applyMultipart(request),
                  sad == null ? false : sad.asyncSupported),
              response);
        } catch (IOException ioe) {
          if (handleError(ioe, request, response) == false)
            throw ioe;
        } catch (UnavailableException ue) {
          // log("Servlet " + servlet + " asked to be unavailable",
          // ue);
          if (sad != null) {
            if (ue.isPermanent()) {
              synchronized (sad) {
                if (sad.instance != null) {
                  sad.instance.destroy();
                  sad.instance = null;
                }
              }
            } else {
              sad.timeToReactivate = System.currentTimeMillis()
                  + ue.getUnavailableSeconds() * 1000l;
            }
          }
          ((HttpServletResponse) response).sendError(
              HttpServletResponse.SC_SERVICE_UNAVAILABLE,
              ue.getMessage());
          // allowing custom handling?
          // eating an exception to avoid removing entire webapp
          // servlet throw ue;
        } catch (ServletException se) {
          if (handleError(se, request, response) == false)
            throw se;
        } catch (Throwable re) {
          if (re instanceof ThreadDeath)
            throw (ThreadDeath) re;
          if (handleError(re, request, response) == false)
            throw new RuntimeException(re);
        }
    }

    protected ServletRequest applyAsyncSet(ServletRequest request,
        boolean asyncEnabled) {
      // TODO decide if add wrapper
      if (asyncEnabled) {
        if (Proxy.isProxyClass(request.getClass())) {
          InvocationHandler handler = Proxy
              .getInvocationHandler(request);
          try {
            handler.getClass().getDeclaredField("asyncEnabled")
                .set(handler, Boolean.TRUE);
          } catch (Exception e) {
            log("", e);
          }
        }
      }
      return request;
    }

    protected ServletRequest applyMultipart(ServletRequest request)
        throws IOException {
      if (sad == null || sad.multipartEnabled == false)
        return request;
      if (Proxy.isProxyClass(request.getClass()) == false)
        throw new IOException(
            "Request wasn't proxied for adding multipart data");

      String contentType = request.getContentType();
      if (contentType != null) {
        contentType = contentType.toLowerCase();
        int pi = contentType.indexOf("multipart/form-data");
        if (pi < 0)
          return request;
        pi = contentType.indexOf("boundary=", pi
            + "multipart/form-data".length());
        if (pi <= 0) // invalid multipart request no boundary
          throw new IOException("Boundary attribute is missed in "
              + contentType);
        int ei = contentType.indexOf(';', pi + "boundary=".length());
        contentType = request.getContentType(); // since to lower case
                            // was applied
        String boundary = ei < 0 ? contentType.substring(pi
            + "boundary=".length()) : contentType.substring(pi
            + "boundary=".length(), ei);
        InvocationHandler handler = Proxy.getInvocationHandler(request);
        try {
          handler.getClass()
              .getDeclaredField("multiparts")
              .set(handler, new Multipart(request, boundary, sad));
        } catch (Exception e) {
          log("", e);
        }
      }
      return request;
    }

    protected boolean handleError(Throwable t, ServletRequest request,
        ServletResponse response) throws java.io.IOException,
        ServletException {
      if (errorPages != null) {
        Class eclass = t.getClass();
        for (ErrorPageDescr epd : errorPages) {
          if (epd.exception != null && eclass.equals(epd.exception)) {
            log("forward to " + epd.errorPage, t);
            ((HttpServletResponse) response)
                .sendRedirect(epd.errorPage);
            setErrorAttributes(request, -1, t.getMessage(),
                getServletName(),
                ((HttpServletRequest) request).getRequestURI(),
                t, t.getClass());
            getRequestDispatcher(epd.errorPage).forward(request,
                response);
            return true;
          }
        }
        Class[] peclasses = eclass.getClasses();
        for (Class peclass : peclasses)
          for (ErrorPageDescr epd : errorPages) {
            if (epd.exception != null
                && peclass.equals(epd.exception)) {
              log("forward to " + epd.errorPage, t);
              ((HttpServletResponse) response)
                  .sendRedirect(epd.errorPage);
              setErrorAttributes(request, -1, t.getMessage(),
                  getServletName(),
                  ((HttpServletRequest) request)
                      .getRequestURI(), t, t.getClass());
              getRequestDispatcher(epd.errorPage).forward(
                  request, response);
              return true;
            }
          }

      }
      return false;
    }

    protected void reset() {
      iterator = filters.iterator();
      nextFilter = filter;
    }

    protected void add(FilterAccessDescriptor fad) {
      if (fad.timeToReactivate > 0
          && fad.timeToReactivate > System.currentTimeMillis())
        return;
      if (filters.contains(fad) == false)
        filters.add(fad);
    }

    protected void setServlet(Servlet servlet) {
      this.servlet = servlet;
      sad = null;
    }

    protected void setServlet(ServletAccessDescr sad) {
      this.servlet = sad.instance;
      this.sad = sad;
    }
  }

  private final static boolean _DEBUG = false;

  private final static boolean __DEBUG = "yes".equals(System
      .getProperty(DEF_DEBUG)) || _DEBUG;
}
TOP

Related Classes of rogatkin.web.WebAppServlet$WebAppContextFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.