Package winstone

Source Code of winstone.WebAppConfiguration

/*
* Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
* Distributed under the terms of either:
* - the common development and distribution license (CDDL), v1.0; or
* - the GNU Lesser General Public License, v2.1 or later
*/
package winstone;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

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.ServletRequestAttributeListener;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionListener;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* Models the web.xml file's details ... basically just a bunch of configuration
* details, plus the actual instances of mounted servlets.
*
* @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
* @version $Id: WebAppConfiguration.java,v 1.55 2007/11/13 01:42:47 rickknowles Exp $
*/
public class WebAppConfiguration implements ServletContext, Comparator {
//    private static final String ELEM_DESCRIPTION = "description";
    private static final String ELEM_DISPLAY_NAME = "display-name";
    private static final String ELEM_SERVLET = "servlet";
    private static final String ELEM_SERVLET_MAPPING = "servlet-mapping";
    private static final String ELEM_SERVLET_NAME = "servlet-name";
    private static final String ELEM_FILTER = "filter";
    private static final String ELEM_FILTER_MAPPING = "filter-mapping";
    private static final String ELEM_FILTER_NAME = "filter-name";
    private static final String ELEM_DISPATCHER = "dispatcher";
    private static final String ELEM_URL_PATTERN = "url-pattern";
    private static final String ELEM_WELCOME_FILES = "welcome-file-list";
    private static final String ELEM_WELCOME_FILE = "welcome-file";
    private static final String ELEM_SESSION_CONFIG = "session-config";
    private static final String ELEM_SESSION_TIMEOUT = "session-timeout";
    private static final String ELEM_MIME_MAPPING = "mime-mapping";
    private static final String ELEM_MIME_EXTENSION = "extension";
    private static final String ELEM_MIME_TYPE = "mime-type";
    private static final String ELEM_CONTEXT_PARAM = "context-param";
    private static final String ELEM_PARAM_NAME = "param-name";
    private static final String ELEM_PARAM_VALUE = "param-value";
    private static final String ELEM_LISTENER = "listener";
    private static final String ELEM_LISTENER_CLASS = "listener-class";
    private static final String ELEM_DISTRIBUTABLE = "distributable";
    private static final String ELEM_ERROR_PAGE = "error-page";
    private static final String ELEM_EXCEPTION_TYPE = "exception-type";
    private static final String ELEM_ERROR_CODE = "error-code";
    private static final String ELEM_ERROR_LOCATION = "location";
    private static final String ELEM_SECURITY_CONSTRAINT = "security-constraint";
    private static final String ELEM_LOGIN_CONFIG = "login-config";
    private static final String ELEM_SECURITY_ROLE = "security-role";
    private static final String ELEM_ROLE_NAME = "role-name";
    private static final String ELEM_ENV_ENTRY = "env-entry";
    private static final String ELEM_LOCALE_ENC_MAP_LIST = "locale-encoding-mapping-list";
    private static final String ELEM_LOCALE_ENC_MAPPING = "locale-encoding-mapping";
    private static final String ELEM_LOCALE = "locale";
    private static final String ELEM_ENCODING = "encoding";
    private static final String ELEM_JSP_CONFIG = "jsp-config";
    private static final String ELEM_JSP_PROPERTY_GROUP = "jsp-property-group";
   
    private static final String DISPATCHER_REQUEST = "REQUEST";
    private static final String DISPATCHER_FORWARD = "FORWARD";
    private static final String DISPATCHER_INCLUDE = "INCLUDE";
    private static final String DISPATCHER_ERROR = "ERROR";
    private static final String JSP_SERVLET_NAME = "JspServlet";
    private static final String JSP_SERVLET_MAPPING = "*.jsp";
    private static final String JSPX_SERVLET_MAPPING = "*.jspx";
    private static final String JSP_SERVLET_LOG_LEVEL = "WARNING";
    private static final String INVOKER_SERVLET_NAME = "invoker";
    private static final String INVOKER_SERVLET_CLASS = "winstone.invoker.InvokerServlet";
    private static final String DEFAULT_INVOKER_PREFIX = "/servlet/";
    private static final String DEFAULT_SERVLET_NAME = "default";
    private static final String DEFAULT_SERVLET_CLASS = "winstone.StaticResourceServlet";
    private static final String DEFAULT_REALM_CLASS = "winstone.realm.ArgumentsRealm";
    private static final String DEFAULT_JNDI_MGR_CLASS = "winstone.jndi.WebAppJNDIManager";
    private static final String RELOADING_CL_CLASS = "winstone.classLoader.ReloadingClassLoader";
    private static final String WEBAPP_CL_CLASS = "winstone.classLoader.WebappClassLoader";   
    private static final String ERROR_SERVLET_NAME = "winstoneErrorServlet";
    private static final String ERROR_SERVLET_CLASS = "winstone.ErrorServlet";
   
    private static final String WEB_INF = "WEB-INF";
    private static final String CLASSES = "classes/";
    private static final String LIB = "lib";
   
    static final String JSP_SERVLET_CLASS = "org.apache.jasper.servlet.JspServlet";
   
    private HostConfiguration ownerHostConfig;
    private Cluster cluster;
    private String webRoot;
    private String prefix;
    private String contextName;
    private ClassLoader loader;
    private String displayName;
    private Map attributes;
    private Map initParameters;
    private Map sessions;
    private Map mimeTypes;
    private Map servletInstances;
    private Map filterInstances;
    private ServletContextAttributeListener contextAttributeListeners[];
    private ServletContextListener contextListeners[];
    private ServletRequestListener requestListeners[];
    private ServletRequestAttributeListener requestAttributeListeners[];
    private HttpSessionActivationListener sessionActivationListeners[];
    private HttpSessionAttributeListener sessionAttributeListeners[];
    private HttpSessionListener sessionListeners[];
    private Throwable contextStartupError;
    private Map exactServletMatchMounts;
    private Mapping patternMatches[];
    private Mapping filterPatternsRequest[];
    private Mapping filterPatternsForward[];
    private Mapping filterPatternsInclude[];
    private Mapping filterPatternsError[];
    private AuthenticationHandler authenticationHandler;
    private AuthenticationRealm authenticationRealm;
    private String welcomeFiles[];
    private Integer sessionTimeout;
    private Class[] errorPagesByExceptionKeysSorted;
    private Map errorPagesByException;
    private Map errorPagesByCode;
    private Map localeEncodingMap;
    private String defaultServletName;
    private String errorServletName;
    private JNDIManager jndiManager;
    private AccessLogger accessLogger;
    private Map filterMatchCache;
    private boolean useSavedSessions;
   
    public static boolean booleanArg(Map args, String name, boolean defaultTrue) {
        String value = (String) args.get(name);
        if (defaultTrue)
            return (value == null) || (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
        else
            return (value != null) && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
    }

    public static String stringArg(Map args, String name, String defaultValue) {
        return (String) (args.get(name) == null ? defaultValue : args.get(name));
    }

    public static int intArg(Map args, String name, int defaultValue) {
        return Integer.parseInt(stringArg(args, name, "" + defaultValue));
    }

    public static String getTextFromNode(Node node) {
        if (node == null) {
            return null;
        }
        Node child = node.getFirstChild();
        if (child == null) {
            return "";
        }
        String textNode = child.getNodeValue();
        if (textNode == null) {
            return "";
        } else {
            return textNode.trim();
        }
    }
   
    public static boolean useSavedSessions(Map args) {
        return booleanArg(args, "useSavedSessions", false);
    }
   
    /**
     * Constructor. This parses the xml and sets up for basic routing
     */
    public WebAppConfiguration(HostConfiguration ownerHostConfig, Cluster cluster, String webRoot,
            String prefix, ObjectPool objectPool, Map startupArgs, Node elm,
            ClassLoader parentClassLoader, File parentClassPaths[], String contextName) {
        if (!prefix.equals("") && !prefix.startsWith("/")) {
            Logger.log(Logger.WARNING, Launcher.RESOURCES,
                    "WebAppConfig.AddingLeadingSlash", prefix);
            prefix = "/" + prefix;
        }
        this.ownerHostConfig = ownerHostConfig;
        this.webRoot = webRoot;
        this.prefix = prefix;
        this.contextName = contextName;

        List localLoaderClassPathFiles = new ArrayList();
        this.loader = buildWebAppClassLoader(startupArgs, parentClassLoader,
                webRoot, localLoaderClassPathFiles);
       
        // Build switch values
        boolean useJasper = booleanArg(startupArgs, "useJasper", true);
        boolean useInvoker = booleanArg(startupArgs, "useInvoker", false);
        boolean useJNDI = booleanArg(startupArgs, "useJNDI", false);
        this.useSavedSessions = useSavedSessions(startupArgs);
       
        // Check jasper is available - simple tests
        if (useJasper) {
            try {
                Class.forName("javax.servlet.jsp.JspFactory", true, parentClassLoader);
                Class.forName(JSP_SERVLET_CLASS, true, this.loader);
            } catch (Throwable err) {
                if (booleanArg(startupArgs, "useJasper", false)) {
                    Logger.log(Logger.WARNING, Launcher.RESOURCES,
                            "WebAppConfig.JasperNotFound");
                    Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                            "WebAppConfig.JasperLoadException", err);
                }
                useJasper = false;
            }
        }
        if (useInvoker) {
            try {
                Class.forName(INVOKER_SERVLET_CLASS, false, this.loader);
            } catch (Throwable err) {
                Logger.log(Logger.WARNING, Launcher.RESOURCES,
                        "WebAppConfig.InvokerNotFound");
                useInvoker = false;
            }
        }

        this.attributes = new Hashtable();
        this.initParameters = new HashMap();
        this.sessions = new Hashtable();

        this.servletInstances = new HashMap();
        this.filterInstances = new HashMap();
        this.filterMatchCache = new HashMap();

        List contextAttributeListeners = new ArrayList();
        List contextListeners = new ArrayList();
        List requestListeners = new ArrayList();
        List requestAttributeListeners = new ArrayList();
        List sessionActivationListeners = new ArrayList();
        List sessionAttributeListeners = new ArrayList();
        List sessionListeners = new ArrayList();

        this.errorPagesByException = new HashMap();
        this.errorPagesByCode = new HashMap();
        boolean distributable = false;

        this.exactServletMatchMounts = new Hashtable();
        List localFolderPatterns = new ArrayList();
        List localExtensionPatterns = new ArrayList();

        List lfpRequest = new ArrayList();
        List lfpForward = new ArrayList();
        List lfpInclude = new ArrayList();
        List lfpError = new ArrayList();

        List localWelcomeFiles = new ArrayList();
        List startupServlets = new ArrayList();

        Set rolesAllowed = new HashSet();
        List constraintNodes = new ArrayList();
        List envEntryNodes = new ArrayList();
        List localErrorPagesByExceptionList = new ArrayList();

        Node loginConfigNode = null;

        // Add the class loader as an implicit context listener if it implements the interface
        addListenerInstance(this.loader, contextAttributeListeners,
                contextListeners, requestAttributeListeners, requestListeners,
                sessionActivationListeners, sessionAttributeListeners,
                sessionListeners);
        
        // init mimeTypes set
        this.mimeTypes = new Hashtable();
        String allTypes = Launcher.RESOURCES.getString("WebAppConfig.DefaultMimeTypes");
        StringTokenizer mappingST = new StringTokenizer(allTypes, ":", false);
        for (; mappingST.hasMoreTokens();) {
            String mapping = mappingST.nextToken();
            int delimPos = mapping.indexOf('=');
            if (delimPos == -1)
                continue;
            String extension = mapping.substring(0, delimPos);
            String mimeType = mapping.substring(delimPos + 1);
            this.mimeTypes.put(extension.toLowerCase(), mimeType);
        }

        this.localeEncodingMap = new HashMap();
        String encodingMapSet = Launcher.RESOURCES.getString("WebAppConfig.EncodingMap");
        StringTokenizer st = new StringTokenizer(encodingMapSet, ";");
        for (; st.hasMoreTokens();) {
            String token = st.nextToken();
            int delimPos = token.indexOf("=");
            if (delimPos == -1)
                continue;
            this.localeEncodingMap.put(token.substring(0, delimPos), token
                    .substring(delimPos + 1));
        }
       
        // init jsp mappings set
        List jspMappings = new ArrayList();
        jspMappings.add(JSP_SERVLET_MAPPING);
        jspMappings.add(JSPX_SERVLET_MAPPING);

        // Add required context atttributes
        File tmpDir = new File(new File(new File(System.getProperty("java.io.tmpdir"),
                "winstone.tmp"), ownerHostConfig.getHostname()), contextName);
        tmpDir.mkdirs();
        this.attributes.put("javax.servlet.context.tempdir", tmpDir);

        // Parse the web.xml file
        if (elm != null) {
            NodeList children = elm.getChildNodes();
            for (int n = 0; n < children.getLength(); n++) {
                Node child = children.item(n);
                if (child.getNodeType() != Node.ELEMENT_NODE)
                    continue;
                String nodeName = child.getNodeName();

                if (nodeName.equals(ELEM_DISPLAY_NAME))
                    this.displayName = getTextFromNode(child);

                else if (nodeName.equals(ELEM_DISTRIBUTABLE))
                    distributable = true;

                else if (nodeName.equals(ELEM_SECURITY_CONSTRAINT))
                    constraintNodes.add(child);

                else if (nodeName.equals(ELEM_ENV_ENTRY))
                    envEntryNodes.add(child);

                else if (nodeName.equals(ELEM_LOGIN_CONFIG))
                    loginConfigNode = child;

                // Session config elements
                else if (nodeName.equals(ELEM_SESSION_CONFIG)) {
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node timeoutElm = child.getChildNodes().item(m);
                        if ((timeoutElm.getNodeType() == Node.ELEMENT_NODE)
                                && (timeoutElm.getNodeName().equals(ELEM_SESSION_TIMEOUT))) {
                            String timeoutStr = getTextFromNode(timeoutElm);
                            if (!timeoutStr.equals("")) {
                                this.sessionTimeout = Integer.valueOf(timeoutStr);
                            }
                        }
                    }
                }

                // Construct the security roles
                else if (child.getNodeName().equals(ELEM_SECURITY_ROLE)) {
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node roleElm = child.getChildNodes().item(m);
                        if ((roleElm.getNodeType() == Node.ELEMENT_NODE)
                                && (roleElm.getNodeName()
                                        .equals(ELEM_ROLE_NAME)))
                            rolesAllowed.add(getTextFromNode(roleElm));
                    }
                }

                // Construct the servlet instances
                else if (nodeName.equals(ELEM_SERVLET)) {
                    ServletConfiguration instance = new ServletConfiguration(
                            this, child);
                    this.servletInstances.put(instance.getServletName(),
                            instance);
                    if (instance.getLoadOnStartup() >= 0)
                        startupServlets.add(instance);
                }

                // Construct the servlet instances
                else if (nodeName.equals(ELEM_FILTER)) {
                    FilterConfiguration instance = new FilterConfiguration(
                            this, this.loader, child);
                    this.filterInstances.put(instance.getFilterName(), instance);
                }

                // Construct the servlet instances
                else if (nodeName.equals(ELEM_LISTENER)) {
                    String listenerClass = null;
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node listenerElm = child.getChildNodes().item(m);
                        if ((listenerElm.getNodeType() == Node.ELEMENT_NODE)
                                && (listenerElm.getNodeName()
                                        .equals(ELEM_LISTENER_CLASS)))
                            listenerClass = getTextFromNode(listenerElm);
                    }
                    if (listenerClass != null)
                        try {
                            Class listener = Class.forName(listenerClass, true,
                                    this.loader);
                            Object listenerInstance = listener.newInstance();
                            addListenerInstance(listenerInstance, contextAttributeListeners,
                                    contextListeners, requestAttributeListeners, requestListeners,
                                    sessionActivationListeners, sessionAttributeListeners,
                                    sessionListeners);
                            Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                                    "WebAppConfig.AddListener", listenerClass);
                        } catch (Throwable err) {
                            Logger.log(Logger.WARNING, Launcher.RESOURCES,
                                    "WebAppConfig.InvalidListener",
                                    listenerClass);
                        }
                }

                // Process the servlet mappings
                else if (nodeName.equals(ELEM_SERVLET_MAPPING)) {
                    String name = null;
                    List mappings = new ArrayList();

                    // Parse the element and extract
                    NodeList mappingChildren = child.getChildNodes();
                    for (int k = 0; k < mappingChildren.getLength(); k++) {
                        Node mapChild = mappingChildren.item(k);
                        if (mapChild.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        String mapNodeName = mapChild.getNodeName();
                        if (mapNodeName.equals(ELEM_SERVLET_NAME)) {
                            name = getTextFromNode(mapChild);
                        } else if (mapNodeName.equals(ELEM_URL_PATTERN)) {
                            mappings.add(getTextFromNode(mapChild));
                        }
                    }
                    for (Iterator i = mappings.iterator(); i.hasNext(); ) {
                        processMapping(name, (String) i.next(), this.exactServletMatchMounts,
                                localFolderPatterns, localExtensionPatterns);
                    }
                }

                // Process the filter mappings
                else if (nodeName.equals(ELEM_FILTER_MAPPING)) {
                    String filterName = null;
                    List mappings = new ArrayList();
                    boolean onRequest = false;
                    boolean onForward = false;
                    boolean onInclude = false;
                    boolean onError = false;

                    // Parse the element and extract
                    for (int k = 0; k < child.getChildNodes().getLength(); k++) {
                        Node mapChild = child.getChildNodes().item(k);
                        if (mapChild.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        String mapNodeName = mapChild.getNodeName();
                        if (mapNodeName.equals(ELEM_FILTER_NAME)) {
                            filterName = getTextFromNode(mapChild);
                        } else if (mapNodeName.equals(ELEM_SERVLET_NAME)) {
                            mappings.add("srv:" + getTextFromNode(mapChild));
                        } else if (mapNodeName.equals(ELEM_URL_PATTERN)) {
                            mappings.add("url:" + getTextFromNode(mapChild));
                        } else if (mapNodeName.equals(ELEM_DISPATCHER)) {
                            String dispatcherValue = getTextFromNode(mapChild);
                            if (dispatcherValue.equals(DISPATCHER_REQUEST))
                                onRequest = true;
                            else if (dispatcherValue.equals(DISPATCHER_FORWARD))
                                onForward = true;
                            else if (dispatcherValue.equals(DISPATCHER_INCLUDE))
                                onInclude = true;
                            else if (dispatcherValue.equals(DISPATCHER_ERROR))
                                onError = true;
                        }
                    }
                    if (!onRequest && !onInclude && !onForward && !onError) {
                        onRequest = true;
                    }
                    if (mappings.isEmpty()) {
                        throw new WinstoneException(Launcher.RESOURCES.getString(
                                "WebAppConfig.BadFilterMapping", filterName));
                    }
                       
                    for (Iterator i = mappings.iterator(); i.hasNext(); ) {
                        String item = (String) i.next();
                        Mapping mapping = null;
                        try {
                            if (item.startsWith("srv:")) {
                                mapping = Mapping.createFromLink(filterName, item.substring(4));
                            } else {
                                mapping = Mapping.createFromURL(filterName, item.substring(4));
                            }
                            if (onRequest)
                                lfpRequest.add(mapping);
                            if (onForward)
                                lfpForward.add(mapping);
                            if (onInclude)
                                lfpInclude.add(mapping);
                            if (onError)
                                lfpError.add(mapping);
                        } catch (WinstoneException err) {
                            Logger.log(Logger.WARNING, Launcher.RESOURCES, "WebAppConfig.ErrorMapURL",
                                    err.getMessage());
                        }
                    }
                }

                // Process the list of welcome files
                else if (nodeName.equals(ELEM_WELCOME_FILES)) {
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node welcomeFile = child.getChildNodes().item(m);
                        if ((welcomeFile.getNodeType() == Node.ELEMENT_NODE)
                                && welcomeFile.getNodeName().equals(ELEM_WELCOME_FILE)) {
                            String welcomeStr = getTextFromNode(welcomeFile);
                            if (!welcomeStr.equals("")) {
                                localWelcomeFiles.add(welcomeStr);
                            }
                        }
                    }
                }
               
                // Process the error pages
                else if (nodeName.equals(ELEM_ERROR_PAGE)) {
                    String code = null;
                    String exception = null;
                    String location = null;

                    // Parse the element and extract
                    for (int k = 0; k < child.getChildNodes().getLength(); k++) {
                        Node errorChild = child.getChildNodes().item(k);
                        if (errorChild.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        String errorChildName = errorChild.getNodeName();
                        if (errorChildName.equals(ELEM_ERROR_CODE))
                            code = getTextFromNode(errorChild);
                        else if (errorChildName.equals(ELEM_EXCEPTION_TYPE))
                            exception = getTextFromNode(errorChild);
                        else if (errorChildName.equals(ELEM_ERROR_LOCATION))
                            location = getTextFromNode(errorChild);
                    }
                    if ((code != null) && (location != null))
                        this.errorPagesByCode.put(code.trim(), location.trim());
                    if ((exception != null) && (location != null))
                        try {
                            Class exceptionClass = Class.forName(exception
                                    .trim(), false, this.loader);
                            localErrorPagesByExceptionList.add(exceptionClass);
                            this.errorPagesByException.put(exceptionClass,
                                    location.trim());
                        } catch (ClassNotFoundException err) {
                            Logger.log(Logger.ERROR, Launcher.RESOURCES,
                                            "WebAppConfig.ExceptionNotFound",
                                            exception);
                        }
                }

                // Process the list of welcome files
                else if (nodeName.equals(ELEM_MIME_MAPPING)) {
                    String extension = null;
                    String mimeType = null;
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node mimeTypeNode = child.getChildNodes().item(m);
                        if (mimeTypeNode.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        else if (mimeTypeNode.getNodeName().equals(
                                ELEM_MIME_EXTENSION))
                            extension = getTextFromNode(mimeTypeNode);
                        else if (mimeTypeNode.getNodeName().equals(
                                ELEM_MIME_TYPE))
                            mimeType = getTextFromNode(mimeTypeNode);
                    }
                    if ((extension != null) && (mimeType != null))
                        this.mimeTypes.put(extension.toLowerCase(), mimeType);
                    else
                        Logger.log(Logger.WARNING, Launcher.RESOURCES,
                                "WebAppConfig.InvalidMimeMapping",
                                new String[] { extension, mimeType });
                }

                // Process the list of welcome files
                else if (nodeName.equals(ELEM_CONTEXT_PARAM)) {
                    String name = null;
                    String value = null;
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node contextParamNode = child.getChildNodes().item(m);
                        if (contextParamNode.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        else if (contextParamNode.getNodeName().equals(
                                ELEM_PARAM_NAME))
                            name = getTextFromNode(contextParamNode);
                        else if (contextParamNode.getNodeName().equals(
                                ELEM_PARAM_VALUE))
                            value = getTextFromNode(contextParamNode);
                    }
                    if ((name != null) && (value != null))
                        this.initParameters.put(name, value);
                    else
                        Logger.log(Logger.WARNING, Launcher.RESOURCES,
                                "WebAppConfig.InvalidInitParam", new String[] {
                                        name, value });
                }

                // Process locale encoding mapping elements
                else if (nodeName.equals(ELEM_LOCALE_ENC_MAP_LIST)) {
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node mappingNode = child.getChildNodes().item(m);
                        if (mappingNode.getNodeType() != Node.ELEMENT_NODE)
                            continue;
                        else if (mappingNode.getNodeName().equals(ELEM_LOCALE_ENC_MAPPING)) {
                            String localeName = "";
                            String encoding = "";
                            for (int l = 0; l < mappingNode.getChildNodes().getLength(); l++) {
                                Node mappingChildNode = mappingNode.getChildNodes().item(l);
                                if (mappingChildNode.getNodeType() != Node.ELEMENT_NODE)
                                    continue;
                                else if (mappingChildNode.getNodeName().equals(ELEM_LOCALE))
                                    localeName = getTextFromNode(mappingChildNode);
                                else if (mappingChildNode.getNodeName().equals(ELEM_ENCODING))
                                    encoding = getTextFromNode(mappingChildNode);
                            }
                            if (!encoding.equals("") && !localeName.equals(""))
                                this.localeEncodingMap.put(localeName, encoding);
                        }
                    }
                }

                // Record the url mappings for jsp files if set
                else if (nodeName.equals(ELEM_JSP_CONFIG)) {
                    for (int m = 0; m < child.getChildNodes().getLength(); m++) {
                        Node propertyGroupNode = child.getChildNodes().item(m);
                        if ((propertyGroupNode.getNodeType() == Node.ELEMENT_NODE)
                                && propertyGroupNode.getNodeName().equals(ELEM_JSP_PROPERTY_GROUP)) {
                            for (int l = 0; l < propertyGroupNode.getChildNodes().getLength(); l++) {
                                Node urlPatternNode = propertyGroupNode.getChildNodes().item(l);
                                if ((urlPatternNode.getNodeType() == Node.ELEMENT_NODE)
                                        && urlPatternNode.getNodeName().equals(ELEM_URL_PATTERN)) {
                                    String jm = getTextFromNode(urlPatternNode);
                                    if (!jm.equals("")) {
                                        jspMappings.add(jm);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
       
        // If not distributable, remove the cluster reference
        if (!distributable && (cluster != null)) {
            Logger.log(Logger.INFO, Launcher.RESOURCES,
                    "WebAppConfig.ClusterOffNotDistributable", this.contextName);
        } else {
            this.cluster = cluster;
        }


        // Build the login/security role instance
        if (!constraintNodes.isEmpty() && (loginConfigNode != null)) {
            String authMethod = null;
            for (int n = 0; n < loginConfigNode.getChildNodes().getLength(); n++) {
                if (loginConfigNode.getChildNodes().item(n).getNodeName().equals("auth-method")) {
                    authMethod = getTextFromNode(loginConfigNode.getChildNodes().item(n));
                }
            }
            // Load the appropriate auth class
            if (authMethod == null) {
                authMethod = "BASIC";
            } else {
                authMethod = WinstoneResourceBundle.globalReplace(authMethod, "-", "");
            }
            String realmClassName = stringArg(startupArgs, "realmClassName",
                    DEFAULT_REALM_CLASS).trim();
            String authClassName = "winstone.auth."
                    + authMethod.substring(0, 1).toUpperCase()
                    + authMethod.substring(1).toLowerCase()
                    + "AuthenticationHandler";
            try {
                // Build the realm
                Class realmClass = Class.forName(realmClassName, true, parentClassLoader);
                Constructor realmConstr = realmClass.getConstructor(
                        new Class[] {Set.class, Map.class });
                this.authenticationRealm = (AuthenticationRealm) realmConstr.newInstance(
                        new Object[] { rolesAllowed, startupArgs });

                // Build the authentication handler
                Class authClass = Class.forName(authClassName);
                Constructor authConstr = authClass
                        .getConstructor(new Class[] { Node.class, List.class,
                                Set.class, AuthenticationRealm.class });
                this.authenticationHandler = (AuthenticationHandler) authConstr
                        .newInstance(new Object[] { loginConfigNode,
                                constraintNodes, rolesAllowed,
                                authenticationRealm });
            } catch (ClassNotFoundException err) {
                Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                        "WebAppConfig.AuthDisabled", authMethod);
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES,
                        "WebAppConfig.AuthError", new String[] { authClassName,
                                realmClassName }, err);
            }
        } else if (!stringArg(startupArgs, "realmClassName", "").trim().equals("")) {
            Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WebAppConfig.NoWebXMLSecurityDefs");
        }

        // Instantiate the JNDI manager
        String jndiMgrClassName = stringArg(startupArgs, "webappJndiClassName",
                DEFAULT_JNDI_MGR_CLASS).trim();
        if (useJNDI) {
            try {
                // Build the realm
                Class jndiMgrClass = Class.forName(jndiMgrClassName, true, parentClassLoader);
                Constructor jndiMgrConstr = jndiMgrClass.getConstructor(new Class[] {
                        Map.class, List.class, ClassLoader.class });
                this.jndiManager = (JNDIManager) jndiMgrConstr.newInstance(new Object[] {
                        null, envEntryNodes, this.loader });
                if (this.jndiManager != null)
                    this.jndiManager.setup();
            } catch (ClassNotFoundException err) {
                Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                        "WebAppConfig.JNDIDisabled");
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES,
                        "WebAppConfig.JNDIError", jndiMgrClassName, err);
            }
        }
       
        String loggerClassName = stringArg(startupArgs, "accessLoggerClassName", "").trim();
        if (!loggerClassName.equals("")) {
            try {
                // Build the realm
                Class loggerClass = Class.forName(loggerClassName, true, parentClassLoader);
                Constructor loggerConstr = loggerClass.getConstructor(new Class[] {
                        WebAppConfiguration.class, Map.class });
                this.accessLogger = (AccessLogger) loggerConstr.newInstance(new Object[] {
                        this, startupArgs});
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES,
                        "WebAppConfig.LoggerError", loggerClassName, err);
            }
        } else {
            Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WebAppConfig.LoggerDisabled");
           
        }
       
        // Add the default index.html welcomeFile if none are supplied
        if (localWelcomeFiles.isEmpty()) {
            if (useJasper) {
                localWelcomeFiles.add("index.jsp");
            }
            localWelcomeFiles.add("index.html");
        }

        // Put the name filters after the url filters, then convert to string arrays
        this.filterPatternsRequest = (Mapping[]) lfpRequest.toArray(new Mapping[0]);
        this.filterPatternsForward = (Mapping[]) lfpForward.toArray(new Mapping[0]);
        this.filterPatternsInclude = (Mapping[]) lfpInclude.toArray(new Mapping[0]);
        this.filterPatternsError = (Mapping[]) lfpError.toArray(new Mapping[0]);

        if (this.filterPatternsRequest.length > 0)
            Arrays.sort(this.filterPatternsRequest, this.filterPatternsRequest[0]);
        if (this.filterPatternsForward.length > 0)
            Arrays.sort(this.filterPatternsForward, this.filterPatternsForward[0]);
        if (this.filterPatternsInclude.length > 0)
            Arrays.sort(this.filterPatternsInclude, this.filterPatternsInclude[0]);
        if (this.filterPatternsError.length > 0)
            Arrays.sort(this.filterPatternsError, this.filterPatternsError[0]);

        this.welcomeFiles = (String[]) localWelcomeFiles.toArray(new String[0]);
        this.errorPagesByExceptionKeysSorted = (Class[]) localErrorPagesByExceptionList
                .toArray(new Class[0]);
        Arrays.sort(this.errorPagesByExceptionKeysSorted, this);

        // Put the listeners into their arrays
        this.contextAttributeListeners = (ServletContextAttributeListener[]) contextAttributeListeners
                .toArray(new ServletContextAttributeListener[0]);
        this.contextListeners = (ServletContextListener[]) contextListeners
                .toArray(new ServletContextListener[0]);
        this.requestListeners = (ServletRequestListener[]) requestListeners
                .toArray(new ServletRequestListener[0]);
        this.requestAttributeListeners = (ServletRequestAttributeListener[]) requestAttributeListeners
                .toArray(new ServletRequestAttributeListener[0]);
        this.sessionActivationListeners = (HttpSessionActivationListener[]) sessionActivationListeners
                .toArray(new HttpSessionActivationListener[0]);
        this.sessionAttributeListeners = (HttpSessionAttributeListener[]) sessionAttributeListeners
                .toArray(new HttpSessionAttributeListener[0]);
        this.sessionListeners = (HttpSessionListener[]) sessionListeners
                .toArray(new HttpSessionListener[0]);

        // If we haven't explicitly mapped the default servlet, map it here
        if (this.defaultServletName == null)
            this.defaultServletName = DEFAULT_SERVLET_NAME;
        if (this.errorServletName == null)
            this.errorServletName = ERROR_SERVLET_NAME;

        // If we don't have an instance of the default servlet, mount the inbuilt one
        if (this.servletInstances.get(this.defaultServletName) == null) {
            boolean useDirLists = booleanArg(startupArgs, "directoryListings", true);
           
            Map staticParams = new Hashtable();
            staticParams.put("webRoot", webRoot);
            staticParams.put("prefix", this.prefix);
            staticParams.put("directoryList", "" + useDirLists);
            ServletConfiguration defaultServlet = new ServletConfiguration(
                    this,  this.defaultServletName, DEFAULT_SERVLET_CLASS,
                    staticParams, 0);
            this.servletInstances.put(this.defaultServletName, defaultServlet);
            startupServlets.add(defaultServlet);
        }

        // If we don't have an instance of the default servlet, mount the inbuilt one
        if (this.servletInstances.get(this.errorServletName) == null) {
            ServletConfiguration errorServlet = new ServletConfiguration(
                    this,  this.errorServletName, ERROR_SERVLET_CLASS,
                    new HashMap(), 0);
            this.servletInstances.put(this.errorServletName, errorServlet);
            startupServlets.add(errorServlet);
        }
       
        // Initialise jasper servlet if requested
        if (useJasper) {
            setAttribute("org.apache.catalina.classloader", this.loader);
            try {
                StringBuffer cp = new StringBuffer();
                for (Iterator i = localLoaderClassPathFiles.iterator(); i.hasNext(); ) {
                    cp.append(((File) i.next()).getCanonicalPath()).append(
                            File.pathSeparatorChar);
                }
                for (int n = 0; n < parentClassPaths.length; n++) {
                    cp.append(parentClassPaths[n].getCanonicalPath()).append(
                            File.pathSeparatorChar);
                }
                setAttribute("org.apache.catalina.jsp_classpath",
                        (cp.length() > 0 ? cp.substring(0, cp.length() - 1) : ""));
            } catch (IOException err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ErrorSettingJSPPaths", err);
            }

            Map jspParams = new HashMap();
            addJspServletParams(jspParams);
            ServletConfiguration sc = new ServletConfiguration(this,
                    JSP_SERVLET_NAME, JSP_SERVLET_CLASS, jspParams, 3);
            this.servletInstances.put(JSP_SERVLET_NAME, sc);
            startupServlets.add(sc);
            for (Iterator mapIt = jspMappings.iterator(); mapIt.hasNext(); ) {
                processMapping(JSP_SERVLET_NAME, (String) mapIt.next(),
                        this.exactServletMatchMounts, localFolderPatterns,
                        localExtensionPatterns);
            }
        }

        // Initialise invoker servlet if requested
        if (useInvoker) {
            // Get generic options
            String invokerPrefix = stringArg(startupArgs, "invokerPrefix",
                    DEFAULT_INVOKER_PREFIX);
            Map invokerParams = new HashMap();
            invokerParams.put("prefix", this.prefix);
            invokerParams.put("invokerPrefix", invokerPrefix);
            ServletConfiguration sc = new ServletConfiguration(this,
                    INVOKER_SERVLET_NAME, INVOKER_SERVLET_CLASS,
                    invokerParams, 3);
            this.servletInstances.put(INVOKER_SERVLET_NAME, sc);
            processMapping(INVOKER_SERVLET_NAME, invokerPrefix + Mapping.STAR,
                    this.exactServletMatchMounts, localFolderPatterns,
                    localExtensionPatterns);
        }

        // Sort the folder patterns so the longest paths are first
        localFolderPatterns.addAll(localExtensionPatterns);
        this.patternMatches = (Mapping[]) localFolderPatterns.toArray(new Mapping[0]);
        if (this.patternMatches.length > 0)
            Arrays.sort(this.patternMatches, this.patternMatches[0]);

        // Send init notifies
        try {
            for (int n = 0; n < this.contextListeners.length; n++) {
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(this.loader);
                this.contextListeners[n].contextInitialized(new ServletContextEvent(this));
                Thread.currentThread().setContextClassLoader(cl);
            }
        } catch (Throwable err) {
            Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ContextStartupError", this.contextName, err);
            this.contextStartupError = err;
        }

        if (this.contextStartupError == null) {
            // Load sessions if enabled
            if (this.useSavedSessions) {
                WinstoneSession.loadSessions(this);
            }
           
            // Initialise all the filters
            for (Iterator i = this.filterInstances.values().iterator(); i.hasNext();) {
                FilterConfiguration config = (FilterConfiguration) i.next();
                try {
                    config.getFilter();
                } catch (ServletException err) {
                    Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.FilterStartupError",
                            config.getFilterName(), err);
                }
            }

            // Initialise load on startup servlets
            Object autoStarters[] = startupServlets.toArray();
            Arrays.sort(autoStarters);
            for (int n = 0; n < autoStarters.length; n++) {
                ((ServletConfiguration) autoStarters[n]).ensureInitialization();
            }
        }
    }

    /**
     * Build the web-app classloader. This tries to load the preferred classloader first,
     * but if it fails, falls back to a simple URLClassLoader.
     */
    private ClassLoader buildWebAppClassLoader(Map startupArgs, ClassLoader parentClassLoader,
            String webRoot, List classPathFileList) {
        List urlList = new ArrayList();
       
        try {
            // Web-inf folder
            File webInfFolder = new File(webRoot, WEB_INF);

            // Classes folder
            File classesFolder = new File(webInfFolder, CLASSES);
            if (classesFolder.exists()) {
                Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                        "WebAppConfig.WebAppClasses");
                String classesFolderURL = classesFolder.getCanonicalFile().toURL().toString();
                urlList.add(new URL(classesFolderURL.endsWith("/") ? classesFolderURL : classesFolderURL + "/"));
                classPathFileList.add(classesFolder);
            } else {
                Logger.log(Logger.WARNING, Launcher.RESOURCES,
                        "WebAppConfig.NoWebAppClasses",
                        classesFolder.toString());
            }

            // Lib folder's jar files
            File libFolder = new File(webInfFolder, LIB);
            if (libFolder.exists()) {
                File jars[] = libFolder.listFiles();
                for (int n = 0; n < jars.length; n++) {
                    String jarName = jars[n].getName().toLowerCase();
                    if (jarName.endsWith(".jar") || jarName.endsWith(".zip")) {
                        Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                                "WebAppConfig.WebAppLib", jars[n].getName());
                        urlList.add(jars[n].toURL());
                        classPathFileList.add(jars[n]);
                    }
                }
            } else {
                Logger.log(Logger.WARNING, Launcher.RESOURCES,
                        "WebAppConfig.NoWebAppLib", libFolder
                                .toString());
            }
        } catch (MalformedURLException err) {
            throw new WinstoneException(Launcher.RESOURCES
                    .getString("WebAppConfig.BadURL"), err);
        } catch (IOException err) {
            throw new WinstoneException(Launcher.RESOURCES
                    .getString("WebAppConfig.IOException"), err);
        }

        URL jarURLs[] = (URL []) urlList.toArray(new URL[urlList.size()]);
       
        String preferredClassLoader = stringArg(startupArgs, "preferredClassLoader", WEBAPP_CL_CLASS);
        if (booleanArg(startupArgs, "useServletReloading", false) &&
                stringArg(startupArgs, "preferredClassLoader", "").equals("")) {
            preferredClassLoader = RELOADING_CL_CLASS;
        }
       
        // Try to set up the preferred class loader, and if we fail, use the normal one
        ClassLoader outputCL = null;
        if (!preferredClassLoader.equals("")) {
            try {
                Class preferredCL = Class.forName(preferredClassLoader, true, parentClassLoader);
                Constructor reloadConstr = preferredCL.getConstructor(new Class[] {
                        URL[].class, ClassLoader.class});
                outputCL = (ClassLoader) reloadConstr.newInstance(new Object[] {
                        jarURLs, parentClassLoader});
            } catch (Throwable err) {
                if (!stringArg(startupArgs, "preferredClassLoader", "").equals("") ||
                        !preferredClassLoader.equals(WEBAPP_CL_CLASS)) {
                    Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.CLError", err);
                }
            }
        }

        if (outputCL == null) {
            outputCL = new URLClassLoader(jarURLs, parentClassLoader);
        }

        Logger.log(Logger.MAX, Launcher.RESOURCES, "WebAppConfig.WebInfClassLoader", outputCL.toString());
        return outputCL;
    }
   
    private void addListenerInstance(Object listenerInstance, List contextAttributeListeners,
            List contextListeners, List requestAttributeListeners, List requestListeners,
            List sessionActivationListeners, List sessionAttributeListeners,
            List sessionListeners) {
        if (listenerInstance instanceof ServletContextAttributeListener)
            contextAttributeListeners.add(listenerInstance);
        if (listenerInstance instanceof ServletContextListener)
            contextListeners.add(listenerInstance);
        if (listenerInstance instanceof ServletRequestAttributeListener)
            requestAttributeListeners.add(listenerInstance);
        if (listenerInstance instanceof ServletRequestListener)
            requestListeners.add(listenerInstance);
        if (listenerInstance instanceof HttpSessionActivationListener)
            sessionActivationListeners.add(listenerInstance);
        if (listenerInstance instanceof HttpSessionAttributeListener)
            sessionAttributeListeners.add(listenerInstance);
        if (listenerInstance instanceof HttpSessionListener)
            sessionListeners.add(listenerInstance);
    }
   
    public String getContextPath() {
        return this.prefix;
    }

    public String getWebroot() {
        return this.webRoot;
    }

    public ClassLoader getLoader() {
        return this.loader;
    }

    public AccessLogger getAccessLogger() {
        return this.accessLogger;
    }

    public Map getFilters() {
        return this.filterInstances;
    }
   
    public String getContextName() {
        return this.contextName;
    }

    public Class[] getErrorPageExceptions() {
        return this.errorPagesByExceptionKeysSorted;
    }

    public Map getErrorPagesByException() {
        return this.errorPagesByException;
    }

    public Map getErrorPagesByCode() {
        return this.errorPagesByCode;
    }

    public Map getLocaleEncodingMap() {
        return this.localeEncodingMap;
    }

    public String[] getWelcomeFiles() {
        return this.welcomeFiles;
    }

    public boolean isDistributable() {
        return (this.cluster != null);
    }

    public Map getFilterMatchCache() {
        return this.filterMatchCache;
    }
   
    public String getOwnerHostname() {
        return this.ownerHostConfig.getHostname();
    }
   
    public ServletRequestListener[] getRequestListeners() {
        return this.requestListeners;
    }

    public ServletRequestAttributeListener[] getRequestAttributeListeners() {
        return this.requestAttributeListeners;
    }

    public static void addJspServletParams(Map jspParams) {
        jspParams.put("logVerbosityLevel", JSP_SERVLET_LOG_LEVEL);
        jspParams.put("fork", "false");
    }

    public int compare(Object one, Object two) {
        if (!(one instanceof Class) || !(two instanceof Class))
            throw new IllegalArgumentException(
                    "This comparator is only for sorting classes");
        Class classOne = (Class) one;
        Class classTwo = (Class) two;
        if (classOne.isAssignableFrom(classTwo))
            return 1;
        else if (classTwo.isAssignableFrom(classOne))
            return -1;
        else
            return 0;
    }

    public String getServletURIFromRequestURI(String requestURI) {
        if (prefix.equals("")) {
            return requestURI;
        } else if (requestURI.startsWith(prefix)) {
            return requestURI.substring(prefix.length());
        } else {
            throw new WinstoneException("This shouldn't happen, " +
                    "since we aborted earlier if we didn't match");
        }
    }
   
    /**
     * Iterates through each of the servlets/filters and calls destroy on them
     */
    public void destroy() {
        synchronized (this.filterMatchCache) {
            this.filterMatchCache.clear();
        }
       
        Collection filterInstances = new ArrayList(this.filterInstances.values());
        for (Iterator i = filterInstances.iterator(); i.hasNext();) {
            try {
                ((FilterConfiguration) i.next()).destroy();
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ShutdownError", err);
            }
        }
        this.filterInstances.clear();
       
        Collection servletInstances = new ArrayList(this.servletInstances.values());
        for (Iterator i = servletInstances.iterator(); i.hasNext();) {
            try {
                ((ServletConfiguration) i.next()).destroy();
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ShutdownError", err);
            }
        }
        this.servletInstances.clear();

        // Drop all sessions
        Collection sessions = new ArrayList(this.sessions.values());
        for (Iterator i = sessions.iterator(); i.hasNext();) {
            WinstoneSession session = (WinstoneSession) i.next();
            try {
                if (this.useSavedSessions) {
                    session.saveToTemp();
                } else {
                    session.invalidate();
                }
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ShutdownError", err);
            }
        }
        this.sessions.clear();

        // Send destroy notifies - backwards
        for (int n = this.contextListeners.length - 1; n >= 0; n--) {
            try {
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(this.loader);
                this.contextListeners[n].contextDestroyed(new ServletContextEvent(this));
                this.contextListeners[n] = null;
                Thread.currentThread().setContextClassLoader(cl);
            } catch (Throwable err) {
                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ShutdownError", err);
            }
        }
        this.contextListeners = null;
       
        // Terminate class loader reloading thread if running
        if (this.loader != null) {
            // already shutdown/handled by the servlet context listeners
//            try {
//                Method methDestroy = this.loader.getClass().getMethod("destroy", new Class[0]);
//                methDestroy.invoke(this.loader, new Object[0]);
//            } catch (Throwable err) {
//                Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.ShutdownError", err);
//            }
            this.loader = null;
        }

        // Kill JNDI manager if we have one
        if (this.jndiManager != null) {
            this.jndiManager.tearDown();
            this.jndiManager = null;
        }

        // Kill JNDI manager if we have one
        if (this.accessLogger != null) {
            this.accessLogger.destroy();
            this.accessLogger = null;
        }
    }

    /**
     * Triggered by the admin thread on the reloading class loader. This will
     * cause a full shutdown and reinstantiation of the web app - not real
     * graceful, but you shouldn't have reloading turned on in high load
     * environments.
     */
    public void resetClassLoader() throws IOException {
        this.ownerHostConfig.reloadWebApp(getContextPath());
    }

    /**
     * Here we process url patterns into the exactMatch and patternMatch lists
     */
    private void processMapping(String name, String pattern, Map exactPatterns,
            List folderPatterns, List extensionPatterns) {
       
        Mapping urlPattern = null;
        try {
            urlPattern = Mapping.createFromURL(name, pattern);
        } catch (WinstoneException err) {
            Logger.log(Logger.WARNING, Launcher.RESOURCES, "WebAppConfig.ErrorMapURL",
                    err.getMessage());
            return;
        }

        // put the pattern in the correct list
        if (urlPattern.getPatternType() == Mapping.EXACT_PATTERN) {
            exactPatterns.put(urlPattern.getUrlPattern(), name);
        } else if (urlPattern.getPatternType() == Mapping.FOLDER_PATTERN) {
            folderPatterns.add(urlPattern);
        } else if (urlPattern.getPatternType() == Mapping.EXTENSION_PATTERN) {
            extensionPatterns.add(urlPattern);
        } else if (urlPattern.getPatternType() == Mapping.DEFAULT_SERVLET) {
            this.defaultServletName = name;
        } else {
            Logger.log(Logger.WARNING, Launcher.RESOURCES, "WebAppConfig.InvalidMount",
                    new String[] { name, pattern });
        }
    }

    /**
     * Execute the pattern match, and try to return a servlet that matches this
     * URL
     */
    private ServletConfiguration urlMatch(String path,
            StringBuffer servletPath, StringBuffer pathInfo) {
        Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "WebAppConfig.URLMatch", path);

        // Check exact matches first
        String exact = (String) this.exactServletMatchMounts.get(path);
        if (exact != null) {
            if (this.servletInstances.get(exact) != null) {
                servletPath.append(WinstoneRequest.decodeURLToken(path));
                // pathInfo.append(""); // a hack - empty becomes null later
                return (ServletConfiguration) this.servletInstances.get(exact);
            }
        }

        // Inexact mount check
        for (int n = 0; n < this.patternMatches.length; n++) {
            Mapping urlPattern = this.patternMatches[n];
            if (urlPattern.match(path, servletPath, pathInfo) &&
                    (this.servletInstances.get(urlPattern.getMappedTo()) != null)) {
                return (ServletConfiguration) this.servletInstances
                        .get(urlPattern.getMappedTo());
            }
        }

        // return default servlet
        // servletPath.append(""); // unneeded
        if (this.servletInstances.get(this.defaultServletName) == null)
            throw new WinstoneException(Launcher.RESOURCES.getString(
                    "WebAppConfig.MatchedNonExistServlet",
                    this.defaultServletName));
//        pathInfo.append(path);
        servletPath.append(WinstoneRequest.decodeURLToken(path));
        return (ServletConfiguration) this.servletInstances.get(this.defaultServletName);
    }

    /**
     * Constructs a session instance with the given sessionId
     *
     * @param sessionId The sessionID for the new session
     * @return A valid session object
     */
    public WinstoneSession makeNewSession(String sessionId) {
        WinstoneSession ws = new WinstoneSession(sessionId);
        ws.setWebAppConfiguration(this);
        setSessionListeners(ws);
        if ((this.sessionTimeout != null) && (this.sessionTimeout.intValue() > 0)) {
            ws.setMaxInactiveInterval(this.sessionTimeout.intValue() * 60);
        } else {
            ws.setMaxInactiveInterval(-1);
        }
        ws.setLastAccessedDate(System.currentTimeMillis());
        ws.sendCreatedNotifies();
        this.sessions.put(sessionId, ws);
        return ws;
    }

    /**
     * Retrieves the session by id. If the web app is distributable, it asks the
     * other members of the cluster if it doesn't have it itself.
     *
     * @param sessionId The id of the session we want
     * @return A valid session instance
     */
    public WinstoneSession getSessionById(String sessionId, boolean localOnly) {
        if (sessionId == null) {
            return null;
        }
        WinstoneSession session = (WinstoneSession) this.sessions.get(sessionId);
        if (session != null) {
            return session;
        }

        // If I'm distributable ... check remotely
        if ((this.cluster != null) && !localOnly) {
            session = this.cluster.askClusterForSession(sessionId, this);
            if (session != null) {
                this.sessions.put(sessionId, session);
            }
            return session;
        } else {
            return null;
        }
    }

    /**
     * Add/Remove the session from the collection
     */
    void removeSessionById(String sessionId) {
        this.sessions.remove(sessionId);
    }
    void addSession(String sessionId, WinstoneSession session) {
        this.sessions.put(sessionId, session);
    }

    public void invalidateExpiredSessions() {
        Object allSessions[] = this.sessions.values().toArray();
        int expiredCount = 0;
       
        for (int n = 0; n < allSessions.length; n++) {
            WinstoneSession session = (WinstoneSession) allSessions[n];
            if (!session.isNew() && session.isUnusedByRequests() && session.isExpired()) {
                session.invalidate();
                expiredCount++;
            }
        }
        if (expiredCount > 0) {
            Logger.log(Logger.DEBUG, Launcher.RESOURCES,
                    "WebAppConfig.InvalidatedSessions", expiredCount + "");
        }
    }
   
    public void setSessionListeners(WinstoneSession session) {
        session.setSessionActivationListeners(this.sessionActivationListeners);
        session.setSessionAttributeListeners(this.sessionAttributeListeners);
        session.setSessionListeners(this.sessionListeners);
    }

    public void removeServletConfigurationAndMappings(ServletConfiguration config) {
        this.servletInstances.remove(config.getServletName());
        // The urlMatch method will only match to non-null mappings, so we don't need
        // to remove anything here
    }
   
    /***************************************************************************
     *
     * OK ... from here to the end is the interface implementation methods for
     * the servletContext interface.
     *
     **************************************************************************/

    // Application level attributes
    public Object getAttribute(String name) {
        return this.attributes.get(name);
    }

    public Enumeration getAttributeNames() {
        return Collections.enumeration(this.attributes.keySet());
    }

    public void removeAttribute(String name) {
        Object me = this.attributes.get(name);
        this.attributes.remove(name);
        if (me != null)
            for (int n = 0; n < this.contextAttributeListeners.length; n++) {
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(getLoader());
                this.contextAttributeListeners[n].attributeRemoved(
                        new ServletContextAttributeEvent(this, name, me));
                Thread.currentThread().setContextClassLoader(cl);
            }
    }

    public void setAttribute(String name, Object object) {
        if (object == null) {
            removeAttribute(name);
        } else {
            Object me = this.attributes.get(name);
            this.attributes.put(name, object);
            if (me != null) {
                for (int n = 0; n < this.contextAttributeListeners.length; n++) {
                    ClassLoader cl = Thread.currentThread().getContextClassLoader();
                    Thread.currentThread().setContextClassLoader(getLoader());
                    this.contextAttributeListeners[n].attributeReplaced(
                            new ServletContextAttributeEvent(this, name, me));
                    Thread.currentThread().setContextClassLoader(cl);
                }
            } else {
                for (int n = 0; n < this.contextAttributeListeners.length; n++) {
                    ClassLoader cl = Thread.currentThread().getContextClassLoader();
                    Thread.currentThread().setContextClassLoader(getLoader());
                    this.contextAttributeListeners[n].attributeAdded(
                            new ServletContextAttributeEvent(this, name, object));
                    Thread.currentThread().setContextClassLoader(cl);
                }
            }
        }
    }

    // Application level init parameters
    public String getInitParameter(String name) {
        return (String) this.initParameters.get(name);
    }

    public Enumeration getInitParameterNames() {
        return Collections.enumeration(this.initParameters.keySet());
    }

    // Server info
    public String getServerInfo() {
        return Launcher.RESOURCES.getString("ServerVersion");
    }

    public int getMajorVersion() {
        return 2;
    }

    public int getMinorVersion() {
        return 5;
    }

    // Weird mostly deprecated crap to do with getting servlet instances
    public javax.servlet.ServletContext getContext(String uri) {
        return this.ownerHostConfig.getWebAppByURI(uri);
    }

    public String getServletContextName() {
        return this.displayName;
    }

    /**
     * Look up the map of mimeType extensions, and return the type that matches
     */
    public String getMimeType(String fileName) {
        int dotPos = fileName.lastIndexOf('.');
        if ((dotPos != -1) && (dotPos != fileName.length() - 1)) {
            String extension = fileName.substring(dotPos + 1).toLowerCase();
            String mimeType = (String) this.mimeTypes.get(extension);
            return mimeType;
        } else
            return null;
    }

    // Context level log statements
    public void log(String message) {
        Logger.logDirectMessage(Logger.INFO, this.contextName, message, null);
    }

    public void log(String message, Throwable throwable) {
        Logger.logDirectMessage(Logger.ERROR, this.contextName, message, throwable);
    }

    /**
     * Named dispatcher - this basically gets us a simple exact dispatcher (no
     * url matching, no request attributes and no security)
     */
    public javax.servlet.RequestDispatcher getNamedDispatcher(String name) {
        ServletConfiguration servlet = (ServletConfiguration) this.servletInstances.get(name);
        if (servlet != null) {
            RequestDispatcher rd = new RequestDispatcher(this, servlet);
            if (rd != null) {
                rd.setForNamedDispatcher(this.filterPatternsForward, this.filterPatternsInclude);
                return rd;
            }
        }
        return null;
    }

    /**
     * Gets a dispatcher, which sets the request attributes, etc on a
     * forward/include. Doesn't execute security though.
     */
    public javax.servlet.RequestDispatcher getRequestDispatcher(
            String uriInsideWebapp) {
        if (uriInsideWebapp == null) {
            return null;
        } else if (!uriInsideWebapp.startsWith("/")) {
            return null;
        }

        // Parse the url for query string, etc
        String queryString = "";
        int questionPos = uriInsideWebapp.indexOf('?');
        if (questionPos != -1) {
            if (questionPos != uriInsideWebapp.length() - 1) {
                queryString = uriInsideWebapp.substring(questionPos + 1);
            }
            uriInsideWebapp = uriInsideWebapp.substring(0, questionPos);
        }

        // Return the dispatcher
        StringBuffer servletPath = new StringBuffer();
        StringBuffer pathInfo = new StringBuffer();
        ServletConfiguration servlet = urlMatch(uriInsideWebapp, servletPath, pathInfo);
        if (servlet != null) {
            RequestDispatcher rd = new RequestDispatcher(this, servlet);
            if (rd != null) {
                rd.setForURLDispatcher(servletPath.toString(), pathInfo.toString()
                        .equals("") ? null : pathInfo.toString(), queryString,
                        uriInsideWebapp, this.filterPatternsForward,
                        this.filterPatternsInclude);
                return rd;
            }
        }
        return null;
    }

    /**
     * Creates the dispatcher that corresponds to a request level dispatch (ie
     * the initial entry point). The difference here is that we need to set up
     * the dispatcher so that on a forward, it executes the security checks and
     * the request filters, while not setting any of the request attributes for
     * a forward. Also, we can't return a null dispatcher in error case - instead
     * we have to return a dispatcher pre-init'd for showing an error page (eg 404).
     * A null dispatcher is interpreted to mean a successful 302 has occurred.
     */
    public RequestDispatcher getInitialDispatcher(String uriInsideWebapp,
            WinstoneRequest request, WinstoneResponse response)
            throws IOException {
        if (!uriInsideWebapp.equals("") && !uriInsideWebapp.startsWith("/")) {
            return this.getErrorDispatcherByCode(
                    HttpServletResponse.SC_BAD_REQUEST,
                    Launcher.RESOURCES.getString("WebAppConfig.InvalidURI", uriInsideWebapp),
                    null);
        } else if (this.contextStartupError != null) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw, true);
            this.contextStartupError.printStackTrace(pw);
            return this.getErrorDispatcherByCode(
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    Launcher.RESOURCES.getString("WebAppConfig.ErrorDuringStartup", sw.toString()),
                    this.contextStartupError);
        }

        // Parse the url for query string, etc
        String queryString = "";
        int questionPos = uriInsideWebapp.indexOf('?');
        if (questionPos != -1) {
            if (questionPos != uriInsideWebapp.length() - 1)
                queryString = uriInsideWebapp.substring(questionPos + 1);
            uriInsideWebapp = uriInsideWebapp.substring(0, questionPos);
        }

        // Return the dispatcher
        StringBuffer servletPath = new StringBuffer();
        StringBuffer pathInfo = new StringBuffer();
        ServletConfiguration servlet = urlMatch(uriInsideWebapp, servletPath, pathInfo);
        if (servlet != null) {
            // If the default servlet was returned, we should check for welcome files
            if (servlet.getServletName().equals(this.defaultServletName)) {
                // Is path a directory ?
                String directoryPath = servletPath.toString();
                if (directoryPath.endsWith("/")) {
                    directoryPath = directoryPath.substring(0, directoryPath.length() - 1);
                }
                if (directoryPath.startsWith("/")) {
                    directoryPath = directoryPath.substring(1);
                }

                File res = new File(webRoot, directoryPath);
                if (res.exists() && res.isDirectory() &&
                        (request.getMethod().equals("GET") || request.getMethod().equals("HEAD"))) {
                    // Check for the send back with slash case
                    if (!servletPath.toString().endsWith("/")) {
                        Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
                                "WebAppConfig.FoundNonSlashDirectory", servletPath.toString());
                        response.sendRedirect(this.prefix
                                + servletPath.toString()
                                + pathInfo.toString()
                                + "/"
                                + (queryString.equals("") ? "" : "?" + queryString));
                        return null;
                    }

                    // Check for welcome files
                    Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
                            "WebAppConfig.CheckWelcomeFile", servletPath.toString()
                                    + pathInfo.toString());
                    String welcomeFile = matchWelcomeFiles(servletPath.toString()
                            + pathInfo.toString(), request, queryString);
                    if (welcomeFile != null) {
                        response.sendRedirect(this.prefix + welcomeFile);
//                                + servletPath.toString()
//                                + pathInfo.toString()
//                                + welcomeFile
//                                + (queryString.equals("") ? "" : "?" + queryString));
                        return null;
                    }
                }
            }

            RequestDispatcher rd = new RequestDispatcher(this, servlet);
            rd.setForInitialDispatcher(servletPath.toString(),
                    pathInfo.toString().equals("") ? null : pathInfo.toString(), queryString,
                    uriInsideWebapp, this.filterPatternsRequest, this.authenticationHandler);
            return rd;
        }
       
        // If we are here, return a 404
        return this.getErrorDispatcherByCode(HttpServletResponse.SC_NOT_FOUND,
                Launcher.RESOURCES.getString("StaticResourceServlet.PathNotFound",
                        uriInsideWebapp), null);
    }

    /**
     * Gets a dispatcher, set up for error dispatch.
     */
    public RequestDispatcher getErrorDispatcherByClass(
            Throwable exception) {

        // Check for exception class match
        Class exceptionClasses[] = this.errorPagesByExceptionKeysSorted;
        Throwable errWrapper = new ServletException(exception);
       
        while (errWrapper instanceof ServletException) {
            errWrapper = ((ServletException) errWrapper).getRootCause();
            if (errWrapper == null) {
                break;
            }
            for (int n = 0; n < exceptionClasses.length; n++) {

                Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
                        "WinstoneResponse.TestingException",
                        new String[] {this.errorPagesByExceptionKeysSorted[n].getName(),
                            errWrapper.getClass().getName()});
                if (exceptionClasses[n].isInstance(errWrapper)) {
                    String errorURI = (String) this.errorPagesByException.get(exceptionClasses[n]);
                    if (errorURI != null) {
                        RequestDispatcher rd = buildErrorDispatcher(errorURI,
                                HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                                null, errWrapper);
                        if (rd != null) {
                            return rd;
                        }
                    } else {
                        Logger.log(Logger.WARNING, Launcher.RESOURCES,
                                "WinstoneResponse.SkippingException",
                                new String[] {exceptionClasses[n].getName(),
                                    (String) this.errorPagesByException.get(exceptionClasses[n]) });
                    }
                } else {
                    Logger.log(Logger.WARNING, Launcher.RESOURCES,
                            "WinstoneResponse.ExceptionNotMatched",
                            exceptionClasses[n].getName());
                }
            }
        }
       
        // Otherwise throw a code error
        Throwable errPassDown = exception;
        while ((errPassDown instanceof ServletException) &&
                (((ServletException) errPassDown).getRootCause() != null)) {
            errPassDown = ((ServletException) errPassDown).getRootCause();
        }
        return getErrorDispatcherByCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                null, errPassDown);
    }
   
    public RequestDispatcher getErrorDispatcherByCode(
            int statusCode, String summaryMessage, Throwable exception) {
        // Check for status code match
        String errorURI = (String) getErrorPagesByCode().get("" + statusCode);
        if (errorURI != null) {
            RequestDispatcher rd = buildErrorDispatcher(errorURI, statusCode,
                    summaryMessage, exception);
            if (rd != null) {
                return rd;
            }
        }
       
        // If no dispatcher available, return a dispatcher to the default error formatter
        ServletConfiguration errorServlet = (ServletConfiguration)
                    this.servletInstances.get(this.errorServletName);
        if (errorServlet != null) {
            RequestDispatcher rd = new RequestDispatcher(this, errorServlet);
            if (rd != null) {
                rd.setForErrorDispatcher(null, null, null, statusCode,
                        summaryMessage, exception, null, this.filterPatternsError);
                return rd;
            }
        }
       
        // Otherwise log and return null
        Logger.log(Logger.ERROR, Launcher.RESOURCES, "WebAppConfig.NoErrorServlet", "" + statusCode, exception);
        return null;
    }

    /**
     * Build a dispatcher to the error handler if it's available. If it fails, return null.
     */
    private RequestDispatcher buildErrorDispatcher(String errorURI, int statusCode,
            String summaryMessage, Throwable exception) {
        // Parse the url for query string, etc
        String queryString = "";
        int questionPos = errorURI.indexOf('?');
        if (questionPos != -1) {
            if (questionPos != errorURI.length() - 1) {
                queryString = errorURI.substring(questionPos + 1);
            }
            errorURI = errorURI.substring(0, questionPos);
        }
       
        // Get the message by recursing if none supplied
        ServletException errIterator = new ServletException(exception);
        while ((summaryMessage == null) && (errIterator != null)) {
            summaryMessage = errIterator.getMessage();
            if (errIterator.getRootCause() instanceof ServletException) {
                errIterator = (ServletException) errIterator.getRootCause();
            } else {
                if (summaryMessage == null) {
                    summaryMessage = errIterator.getRootCause().getMessage();
                }
                errIterator = null;
            }
        }

        // Return the dispatcher
        StringBuffer servletPath = new StringBuffer();
        StringBuffer pathInfo = new StringBuffer();
        ServletConfiguration servlet = urlMatch(errorURI, servletPath, pathInfo);
        if (servlet != null) {
            RequestDispatcher rd = new RequestDispatcher(this, servlet);
            if (rd != null) {
                rd.setForErrorDispatcher(servletPath.toString(),
                        pathInfo.toString().equals("") ? null : pathInfo.toString(),
                                queryString, statusCode, summaryMessage,
                                exception, errorURI, this.filterPatternsError);
                return rd;
            }
        }
        return null;
    }

    /**
     * Check if any of the welcome files under this path are available. Returns the
     * name of the file if found, null otherwise. Returns the full internal webapp uri
     */
    private String matchWelcomeFiles(String path, WinstoneRequest request, String queryString) {
        if (!path.endsWith("/")) {
            path = path + "/";
        }

        String qs = (queryString.equals("") ? "" : "?" + queryString);
        for (int n = 0; n < this.welcomeFiles.length; n++) {
            String welcomeFile = this.welcomeFiles[n];
            while (welcomeFile.startsWith("/")) {
                welcomeFile = welcomeFile.substring(1);
            }
            welcomeFile = path + welcomeFile;
           
            String exact = (String) this.exactServletMatchMounts.get(welcomeFile);
            if (exact != null) {
                return welcomeFile + qs;
            }

            // Inexact folder mount check - note folder mounts only
            for (int j = 0; j < this.patternMatches.length; j++) {
                Mapping urlPattern = this.patternMatches[j];
                if ((urlPattern.getPatternType() == Mapping.FOLDER_PATTERN)
                        && urlPattern.match(welcomeFile, null, null)) {
                    return welcomeFile + qs;
                }
            }

            try {
                if (getResource(welcomeFile) != null) {
                    return welcomeFile + qs;
                }
            } catch (MalformedURLException err) {}
        }
        return null;
    }

    // Getting resources via the classloader
    public URL getResource(String path) throws MalformedURLException {
        if (path == null) {
            return null;
        } else if (!path.startsWith("/")) {
            throw new MalformedURLException(Launcher.RESOURCES.getString(
                    "WebAppConfig.BadResourcePath", path));
        } else if (!path.equals("/") && path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        File res = new File(webRoot, path.substring(1));
        return (res != null) && res.exists() ? res.toURL() : null;
    }

    public InputStream getResourceAsStream(String path) {
        try {
            URL res = getResource(path);
            return res == null ? null : res.openStream();
        } catch (IOException err) {
            throw new WinstoneException(Launcher.RESOURCES
                    .getString("WebAppConfig.ErrorOpeningStream"), err);
        }
    }

    public String getRealPath(String path) {
        // Trim the prefix
        if (path == null)
            return null;
        else {
            try {
                File res = new File(this.webRoot, path);
                if (res.isDirectory())
                    return res.getCanonicalPath() + "/";
                else
                    return res.getCanonicalPath();
            } catch (IOException err) {
                return null;
            }
        }
    }

    public Set getResourcePaths(String path) {
        // Trim the prefix
        if (path == null)
            return null;
        else if (!path.startsWith("/"))
            throw new WinstoneException(Launcher.RESOURCES.getString(
                    "WebAppConfig.BadResourcePath", path));
        else {
            String workingPath = null;
            if (path.equals("/"))
                workingPath = "";
            else {
                boolean lastCharIsSlash = path.charAt(path.length() - 1) == '/';
                workingPath = path.substring(1, path.length()
                        - (lastCharIsSlash ? 1 : 0));
            }
            File inPath = new File(this.webRoot, workingPath.equals("") ? "."
                    : workingPath).getAbsoluteFile();
            if (!inPath.exists())
                return null;
            else if (!inPath.isDirectory())
                return null;

            // Find all the files in this folder
            File children[] = inPath.listFiles();
            Set out = new HashSet();
            for (int n = 0; n < children.length; n++) {
                // Write the entry as subpath + child element
                String entry = //this.prefix +
                "/" + (workingPath.length() != 0 ? workingPath + "/" : "")
                        + children[n].getName()
                        + (children[n].isDirectory() ? "/" : "");
                out.add(entry);
            }
            return out;
        }
    }

    /**
     * @deprecated
     */
    public javax.servlet.Servlet getServlet(String name) {
        return null;
    }

    /**
     * @deprecated
     */
    public Enumeration getServletNames() {
        return Collections.enumeration(new ArrayList());
    }

    /**
     * @deprecated
     */
    public Enumeration getServlets() {
        return Collections.enumeration(new ArrayList());
    }

    /**
     * @deprecated
     */
    public void log(Exception exception, String msg) {
        this.log(msg, exception);
    }

}
TOP

Related Classes of winstone.WebAppConfiguration

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.