// ========================================================================
// $Id: HttpContext.java,v 1.136 2006/02/21 09:47:43 gregwilkins Exp $
// Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package org.mortbay.http;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.mortbay.log.LogFactory;
import org.mortbay.http.ResourceCache.ResourceMetaData;
import org.mortbay.http.handler.ErrorPageHandler;
import org.mortbay.util.Container;
import org.mortbay.util.EventProvider;
import org.mortbay.util.IO;
import org.mortbay.util.LazyList;
import org.mortbay.util.LifeCycle;
import org.mortbay.util.LogSupport;
import org.mortbay.util.MultiException;
import org.mortbay.util.Resource;
import org.mortbay.util.URI;


/* ------------------------------------------------------------ */
/** Context for a collection of HttpHandlers.
 * HTTP Context provides an ordered container for HttpHandlers
 * that share the same path prefix, filebase, resourcebase and/or
 * classpath.
 * <p>
 * A HttpContext is analagous to a ServletContext in the
 * Servlet API, except that it may contain other types of handler
 * other than servlets.
 * <p>
 * A ClassLoader is created for the context and it uses
 * Thread.currentThread().getContextClassLoader(); as it's parent loader.
 * The class loader is initialized during start(), when a derived
 * context calls initClassLoader() or on the first call to loadClass()
 * <p>
 *
 * <B>Note. that order is important when configuring a HttpContext.
 * For example, if resource serving is enabled before servlets, then resources
 * take priority.</B>
 *
 * @see HttpServer
 * @see HttpHandler
 * @see org.mortbay.jetty.servlet.ServletHttpContext
 * @version $Id: HttpContext.java,v 1.136 2006/02/21 09:47:43 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class HttpContext extends Container
                         implements LifeCycle,
                                    HttpHandler,
                                    EventProvider,
                                    Serializable
{
    private static Log log = LogFactory.getLog(HttpContext.class);

    /* ------------------------------------------------------------ */
    /** File class path attribute.
     * If this name is set as a context init parameter, then the attribute
     * name given will be used to set the file classpath for the context as a
     * context attribute.
     */
    public final static String __fileClassPathAttr=
        "org.mortbay.http.HttpContext.FileClassPathAttribute";

    public final static String __ErrorHandler=
        "org.mortbay.http.ErrorHandler";


    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    // These attributes are serialized by WebApplicationContext, which needs
    // to be updated if you add to these
    private String _contextPath;
    private List _vhosts=new ArrayList(2);
    private List _hosts=new ArrayList(2);
    private List _handlers=new ArrayList(3);
    private Map _attributes = new HashMap(3);
    private boolean _redirectNullPath=true;
    private boolean _statsOn=false;
    private PermissionCollection _permissions;
    private boolean _classLoaderJava2Compliant=true;
    private ResourceCache _resources;
    private String[] _systemClasses=new String [] {"java.","javax.servlet.","javax.xml.","org.mortbay.","org.xml.","org.w3c.","org.apache.commons.logging."};
    private String[] _serverClasses = new String[] {"-org.mortbay.http.PathMap","-org.mortbay.jetty.servlet.Invoker","-org.mortbay.jetty.servlet.JSR154Filter","-org.mortbay.jetty.servlet.Default","org.mortbay.jetty.Server","org.mortbay.http.","org.mortbay.start.","org.mortbay.stop."};
  
    /* ------------------------------------------------------------ */
    private String _contextName;
    private String _classPath;
    private Map _initParams = new HashMap(11);
    private UserRealm _userRealm;
    private String _realmName;
    private PathMap _constraintMap=new PathMap();
    private Authenticator _authenticator;
    private RequestLog _requestLog;


    private String[] _welcomes=
    {
        "welcome.html",
        "index.html",
        "index.htm",
        "index.jsp"
    };


    /* ------------------------------------------------------------ */
    private transient boolean _gracefulStop;
    private transient ClassLoader _parent;
    private transient ClassLoader _loader;
    private transient HttpServer _httpServer;
    private transient File _tmpDir;
    private transient HttpHandler[] _handlersArray;
    private transient String[] _vhostsArray;


    /* ------------------------------------------------------------ */
    transient Object _statsLock=new Object[0];
    transient long _statsStartedAt;
    transient int _requests;
    transient int _requestsActive;
    transient int _requestsActiveMax;
    transient int _responses1xx; // Informal
    transient int _responses2xx; // Success
    transient int _responses3xx; // Redirection
    transient int _responses4xx; // Client Error
    transient int _responses5xx; // Server Error


    /* ------------------------------------------------------------ */
    /** Constructor.
     */
    public HttpContext()
    {
        setAttribute(__ErrorHandler,  new ErrorPageHandler()); 
        _resources=new ResourceCache();
        addComponent(_resources);
    }

    /* ------------------------------------------------------------ */
    /** Constructor.
     * @param httpServer
     * @param contextPathSpec
     */
    public HttpContext(HttpServer httpServer,String contextPathSpec)
    {
        this();
        setHttpServer(httpServer);
        setContextPath(contextPathSpec);
    }

    /* ------------------------------------------------------------ */
    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        in.defaultReadObject();
        _statsLock=new Object[0];
        getHandlers();
        for (int i=0;i<_handlersArray.length;i++)
            _handlersArray[i].initialize(this);
    }

    /* ------------------------------------------------------------ */
    /** Get the ThreadLocal HttpConnection.
     * Get the HttpConnection for current thread, if any.  This method is
     * not static in order to control access.
     * @return HttpConnection for this thread.
     */
    public HttpConnection getHttpConnection()
    {
        return HttpConnection.getHttpConnection();
    }

    /* ------------------------------------------------------------ */
    void setHttpServer(HttpServer httpServer)
    {
        _httpServer=httpServer;
        _contextName=null;
      
    }

    /* ------------------------------------------------------------ */
    public HttpServer getHttpServer()
    {
        return _httpServer;
    }

    /* ------------------------------------------------------------ */
    public void setStopGracefully(boolean graceful)
    {
	_gracefulStop=graceful;
    }

    /* ------------------------------------------------------------ */
    public boolean getStopGracefully()
    {
	return _gracefulStop;
    }


    /* ------------------------------------------------------------ */
    public static String canonicalContextPathSpec(String contextPathSpec)
    {
        // check context path
        if (contextPathSpec==null ||
            contextPathSpec.indexOf(',')>=0 ||
            contextPathSpec.startsWith("*"))
            throw new IllegalArgumentException ("Illegal context spec:"+contextPathSpec);

        if(!contextPathSpec.startsWith("/"))
	    contextPathSpec='/'+contextPathSpec;

        if (contextPathSpec.length()>1)
        {
            if (contextPathSpec.endsWith("/"))
                contextPathSpec+="*";
            else if (!contextPathSpec.endsWith("/*"))
                contextPathSpec+="/*";
        }

        return contextPathSpec;
    }

    /* ------------------------------------------------------------ */
    public void setContextPath(String contextPathSpec)
    {
        if (_httpServer!=null)
            _httpServer.removeMappings(this);

        contextPathSpec=canonicalContextPathSpec(contextPathSpec);

        if (contextPathSpec.length()>1)
            _contextPath=contextPathSpec.substring(0,contextPathSpec.length()-2);
        else
            _contextPath="/";

        _contextName=null;

        if (_httpServer!=null)
            _httpServer.addMappings(this);
    }


    /* ------------------------------------------------------------ */
    /**
     * @return The context prefix
     */
    public String getContextPath()
    {
        return _contextPath;
    }


    /* ------------------------------------------------------------ */
    /** Add a virtual host alias to this context.
     * @see #setVirtualHosts
     * @param hostname A hostname. A null host name means any hostname is
     * acceptable. Host names may String representation of IP addresses.
     */
    public void addVirtualHost(String hostname)
    {
        // Note that null hosts are also added.
        if (!_vhosts.contains(hostname))
        {
            _vhosts.add(hostname);
            _contextName=null;

            if (_httpServer!=null)
            {
                if (_vhosts.size()==1)
                    _httpServer.removeMapping(null,this);
                _httpServer.addMapping(hostname,this);
            }
            _vhostsArray=null;
        }
    }

    /* ------------------------------------------------------------ */
    /** remove a virtual host alias to this context.
     * @see #setVirtualHosts
     * @param hostname A hostname. A null host name means any hostname is
     * acceptable. Host names may String representation of IP addresses.
     */
    public void removeVirtualHost(String hostname)
    {
        // Note that null hosts are also added.
        if (_vhosts.remove(hostname))
        {
            _contextName=null;
            if (_httpServer!=null)
            {
                _httpServer.removeMapping(hostname,this);
                if (_vhosts.size()==0)
                    _httpServer.addMapping(null,this);
            }
            _vhostsArray=null;
        }
    }

    /* ------------------------------------------------------------ */
    /** Set the virtual hosts for the context.
     * Only requests that have a matching host header or fully qualified
     * URL will be passed to that context with a virtual host name.
     * A context with no virtual host names or a null virtual host name is
     * available to all requests that are not served by a context with a
     * matching virtual host name.
     * @param hosts Array of virtual hosts that this context responds to. A
     * null host name or null/empty array means any hostname is acceptable.
     * Host names may String representation of IP addresses.
     */
    public void setVirtualHosts(String[] hosts)
    {
        List old = new ArrayList(_vhosts);

        if (hosts!=null)
	{
	    for (int i=0;i<hosts.length;i++)
	    {
		boolean existing=old.remove(hosts[i]);
		if (!existing)
		    addVirtualHost(hosts[i]);
	    }
	}

        for (int i=0;i<old.size();i++)
            removeVirtualHost((String)old.get(i));
    }

    /* ------------------------------------------------------------ */
    /** Get the virtual hosts for the context.
     * Only requests that have a matching host header or fully qualified
     * URL will be passed to that context with a virtual host name.
     * A context with no virtual host names or a null virtual host name is
     * available to all requests that are not served by a context with a
     * matching virtual host name.
     * @return Array of virtual hosts that this context responds to. A
     * null host name or empty array means any hostname is acceptable.
     * Host names may be String representation of IP addresses.
     */
    public String[] getVirtualHosts()
    {
        if (_vhostsArray!=null)
            return _vhostsArray;
        if (_vhosts==null)
            _vhostsArray=new String[0];
        else
        {
            _vhostsArray=new String[_vhosts.size()];
            _vhostsArray=(String[])_vhosts.toArray(_vhostsArray);
        }
        return _vhostsArray;
    }


    /* ------------------------------------------------------------ */
    /** Set the hosts for the context.
     * Set the real hosts that this context will accept requests for.
     * If not null or empty, then only requests from HttpListeners for hosts
     * in this array are accepted by this context. 
     * Unlike virutal hosts, this value is not used by HttpServer for
     * matching a request to a context.
     */
    public void setHosts(String[] hosts)
        throws UnknownHostException
    {
        if (hosts==null || hosts.length==0)
            _hosts=null;
        else
        {
            _hosts=new ArrayList();
            for (int i=0;i<hosts.length;i++)
                if (hosts[i]!=null)
                    _hosts.add(InetAddress.getByName(hosts[i]));
        }
        
    }

    /* ------------------------------------------------------------ */
    /** Get the hosts for the context.
     */
    public String[] getHosts()
    {
        if (_hosts==null || _hosts.size()==0)
            return null;
        String[] hosts=new String[_hosts.size()];
        for (int i=0;i<hosts.length;i++)
        {
            InetAddress a = (InetAddress)_hosts.get(i);
            if (a!=null)
                hosts[i]=a.getHostName();
        }
        return hosts;
    }


    /* ------------------------------------------------------------ */
    /** Set system classes.
     * System classes cannot be overriden by context classloaders.
     * @param classes array of classname Strings.  Names ending with '.' are treated as package names. Names starting with '-' are treated as
     * negative matches and must be listed before any enclosing packages.
     */
    public void setSystemClasses(String[] classes)
    {
        _systemClasses=classes;
    }

    /* ------------------------------------------------------------ */
    /** Get system classes.
     * System classes cannot be overriden by context classloaders.
     * @return array of classname Strings.  Names ending with '.' are treated as package names. Names starting with '-' are treated as
     * negative matches and must be listed before any enclosing packages. Null if not set.
     */
    public String[] getSystemClasses()
    {
        return _systemClasses;
    }
    

    /* ------------------------------------------------------------ */
    /** Set system classes.
     * Servers classes cannot be seen by context classloaders.
     * @param classes array of classname Strings.  Names ending with '.' are treated as package names. Names starting with '-' are treated as
     * negative matches and must be listed before any enclosing packages.
     */
    public void setServerClasses(String[] classes)
    {
        _serverClasses=classes;
    }

    /* ------------------------------------------------------------ */
    /** Get system classes.
     * System classes cannot be seen by context classloaders.
     * @return array of classname Strings.  Names ending with '.' are treated as package names. Names starting with '-' are treated as
     * negative matches and must be listed before any enclosing packages. Null if not set.
     */
    public String[] getServerClasses()
    {
        return _serverClasses;
    }


    /* ------------------------------------------------------------ */
    public void setHandlers(HttpHandler[] handlers)
    {
        List old = new ArrayList(_handlers);

	if (handlers!=null)
	{
	    for (int i=0;i<handlers.length;i++)
	    {
		boolean existing=old.remove(handlers[i]);
		if (!existing)
		    addHandler(handlers[i]);
	    }
	}

        for (int i=0;i<old.size();i++)
            removeHandler((HttpHandler)old.get(i));
    }

    /* ------------------------------------------------------------ */
    /** Get all handlers.
     * @return List of all HttpHandlers
     */
    public HttpHandler[] getHandlers()
    {
        if (_handlersArray!=null)
            return _handlersArray;
        if (_handlers==null)
            _handlersArray=new HttpHandler[0];
        else
        {
            _handlersArray=new HttpHandler[_handlers.size()];
            _handlersArray=(HttpHandler[])_handlers.toArray(_handlersArray);
        }
        return _handlersArray;
    }


    /* ------------------------------------------------------------ */
    /** Add a handler.
     * @param i The position in the handler list
     * @param handler The handler.
     */
    public synchronized void addHandler(int i,HttpHandler handler)
    {
        _handlers.add(i,handler);
        _handlersArray=null;

        HttpContext context = handler.getHttpContext();
        if (context==null)
            handler.initialize(this);
        else if (context!=this)
            throw new IllegalArgumentException("Handler in another HttpContext");
        addComponent(handler);
    }

    /* ------------------------------------------------------------ */
    /** Add a HttpHandler to the context.
     * @param handler
     */
    public synchronized void addHandler(HttpHandler handler)
    {
        addHandler(_handlers.size(),handler);
    }

    /* ------------------------------------------------------------ */
    /** Get handler index.
     * @param handler instance
     * @return Index of handler in context or -1 if not found.
     */
    public int getHandlerIndex(HttpHandler handler)
    {
        for (int h=0;h<_handlers.size();h++)
        {
            if ( handler == _handlers.get(h))
                return h;
        }
        return -1;
    }

    /* ------------------------------------------------------------ */
    /** Get a handler by class.
     * @param handlerClass
     * @return The first handler that is an instance of the handlerClass
     */
    public synchronized HttpHandler getHandler(Class handlerClass)
    {
        for (int h=0;h<_handlers.size();h++)
        {
            HttpHandler handler = (HttpHandler)_handlers.get(h);
            if (handlerClass.isInstance(handler))
                return handler;
        }
        return null;
    }

    /* ------------------------------------------------------------ */
    /** Remove a handler.
     * The handler must be stopped before being removed.
     * @param i index of handler
     */
    public synchronized HttpHandler removeHandler(int i)
    {
        HttpHandler handler = _handlersArray[i];
        if (handler.isStarted())
            try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
        _handlers.remove(i);
        _handlersArray=null;
        removeComponent(handler);
        return handler;
    }

    /* ------------------------------------------------------------ */
    /** Remove a handler.
     * The handler must be stopped before being removed.
     */
    public synchronized void removeHandler(HttpHandler handler)
    {
        if (handler.isStarted())
            try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
        _handlers.remove(handler);
        removeComponent(handler);
        _handlersArray=null;
    }


    /* ------------------------------------------------------------ */
    /** Set context init parameter.
     * Init Parameters differ from attributes as they can only
     * have string values, servlets cannot set them and they do
     * not have a package scoped name space.
     * @param param param name
     * @param value param value or null
     */
    public void setInitParameter(String param, String value)
    {
        _initParams.put(param,value);
    }

    /* ------------------------------------------------------------ */
    /** Get context init parameter.
     * @param param param name
     * @return param value or null
     */
    public String getInitParameter(String param)
    {
        return (String)_initParams.get(param);
    }

    /* ------------------------------------------------------------ */
    /** Get context init parameter.
     * @return Enumeration of names
     */
    public Enumeration getInitParameterNames()
    {
        return Collections.enumeration(_initParams.keySet());
    }

    /* ------------------------------------------------------------ */
    /** Set a context attribute.
     * @param name attribute name
     * @param value attribute value
     */
    public synchronized void setAttribute(String name, Object value)
    {
        _attributes.put(name,value);
    }

    /* ------------------------------------------------------------ */
    /**
     * @param name attribute name
     * @return attribute value or null
     */
    public Object getAttribute(String name)
    {
        return _attributes.get(name);
    }

    /* ------------------------------------------------------------ */
    /**
     */
    public Map getAttributes()
    {
        return _attributes;
    }

    /* ------------------------------------------------------------ */
    /**
     */
    public void setAttributes(Map attributes)
    {
        _attributes=attributes;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return enumaration of names.
     */
    public Enumeration getAttributeNames()
    {
        return Collections.enumeration(_attributes.keySet());
    }

    /* ------------------------------------------------------------ */
    /**
     * @param name attribute name
     */
    public synchronized void removeAttribute(String name)
    {
        _attributes.remove(name);
    }


    /* ------------------------------------------------------------ */
    public void flushCache()
    {
        _resources.flushCache();
    }

    /* ------------------------------------------------------------ */
    public String[] getWelcomeFiles()
    {
        return _welcomes;
    }

    /* ------------------------------------------------------------ */
    public void setWelcomeFiles(String[] welcomes)
    {
        if (welcomes==null)
            _welcomes=new String[0];
        else
            _welcomes=welcomes;
    }

    /* ------------------------------------------------------------ */
    public void addWelcomeFile(String welcomeFile)
    {
        if (welcomeFile.startsWith("/") ||
            welcomeFile.startsWith(java.io.File.separator) ||
            welcomeFile.endsWith("/") ||
            welcomeFile.endsWith(java.io.File.separator))
            log.warn("Invalid welcome file: "+welcomeFile);
        List list = new ArrayList(Arrays.asList(_welcomes));
        list.add(welcomeFile);
        _welcomes=(String[])list.toArray(_welcomes);
    }

    /* ------------------------------------------------------------ */
    public void removeWelcomeFile(String welcomeFile)
    {
        List list = new ArrayList(Arrays.asList(_welcomes));
        list.remove(welcomeFile);
        _welcomes=(String[])list.toArray(_welcomes);
    }

    /* ------------------------------------------------------------ */
    public String getWelcomeFile(Resource resource)
        throws IOException
    {
        if (!resource.isDirectory())
            return null;

        for (int i=0;i<_welcomes.length;i++)
        {
            Resource welcome=resource.addPath(_welcomes[i]);
            if (welcome.exists())
                return _welcomes[i];
        }

        return null;
    }


    /* ------------------------------------------------------------ */
    /** Get the context classpath.
     * This method only returns the paths that have been set for this
     * context and does not include any paths from a parent or the
     * system classloader.
     * Note that this may not be a legal javac classpath.
     * @return a comma or ';' separated list of class
     * resources. These may be jar files, directories or URLs to jars
     * or directories.
     * @see #getFileClassPath()
     */
    public String getClassPath()
    {
        return _classPath;
    }

    /* ------------------------------------------------------------ */
    /** Get the file classpath of the context.
     * This method makes a best effort to return a complete file
     * classpath for the context.
     * It is obtained by walking the classloader hierarchy and looking for
     * URLClassLoaders.  The system property java.class.path is also checked for
     * file elements not already found in the loader hierarchy.
     * @return Path of files and directories for loading classes.
     * @exception IllegalStateException HttpContext.initClassLoader
     * has not been called.
     */
    public String getFileClassPath()
        throws IllegalStateException
    {
	
        ClassLoader loader = getClassLoader();
        if (loader==null)
            throw new IllegalStateException("Context classloader not initialized");
            
        LinkedList paths =new LinkedList();
        LinkedList loaders=new LinkedList();
        
        // Walk the loader hierarchy
       	while (loader !=null)
       	{
            loaders.add(0,loader);
            loader = loader.getParent();
       	}
       	
       	// Try to handle java2compliant modes
       	loader=getClassLoader();
       	if (loader instanceof ContextLoader && !((ContextLoader)loader).isJava2Compliant())
       	{
            loaders.remove(loader);
            loaders.add(0,loader);
       	}
        
        for (int i=0;i<loaders.size();i++)
        {
            loader=(ClassLoader)loaders.get(i);
            
            if (log.isDebugEnabled()) log.debug("extract paths from "+loader);
            if (loader instanceof URLClassLoader)
            {
                URL[] urls = ((URLClassLoader)loader).getURLs();
                for (int j=0;urls!=null && j<urls.length;j++)
                {
                    try
                    {
                        Resource path = Resource.newResource(urls[j]);
                        if (log.isTraceEnabled()) log.trace("path "+path);
                        File file = path.getFile();
                        if (file!=null)
                            paths.add(file.getAbsolutePath());
                    }
                    catch(Exception e)
                    {
                        LogSupport.ignore(log,e);
                    }
                }	
            }	
       	}
       	
        // Add the system classpath elements from property.
        String jcp=System.getProperty("java.class.path");
        if (jcp!=null)
        {
            StringTokenizer tok=new StringTokenizer(jcp,File.pathSeparator);
            while (tok.hasMoreTokens())
            {
                String path=tok.nextToken();
                if (!paths.contains(path))
                {
                    if(log.isTraceEnabled())log.trace("PATH="+path);
                    paths.add(path);
                }
                else
                    if(log.isTraceEnabled())log.trace("done="+path);			
            }
        }
        
        StringBuffer buf = new StringBuffer();
        Iterator iter = paths.iterator();
        while(iter.hasNext())
        {
            if (buf.length()>0)
                buf.append(File.pathSeparator);
            buf.append(iter.next().toString());
        }
        
        if (log.isDebugEnabled()) log.debug("fileClassPath="+buf);
        return buf.toString();
    }

    /* ------------------------------------------------------------ */
    /** Sets the class path for the context.
     * A class path is only required for a context if it uses classes
     * that are not in the system class path.
     * @param classPath a comma or ';' separated list of class
     * resources. These may be jar files, directories or URLs to jars
     * or directories.
     */
    public void setClassPath(String classPath)
    {
        _classPath=classPath;
        if (isStarted())
            log.warn("classpath set while started");
    }

    /* ------------------------------------------------------------ */
    /** Add the class path element  to the context.
     * A class path is only required for a context if it uses classes
     * that are not in the system class path.
     * @param classPath a comma or ';' separated list of class
     * resources. These may be jar files, directories or URLs to jars
     * or directories.
     */
    public void addClassPath(String classPath)
    {
        if (_classPath==null || _classPath.length()==0)
            _classPath=classPath;
        else
            _classPath+=","+classPath;

        if (isStarted())
            log.warn("classpath set while started");
    }

    /* ------------------------------------------------------------ */
    /** Add elements to the class path for the context from the jar and zip files found
     *  in the specified resource.
     * @param lib the resource that contains the jar and/or zip files.
     * @param append true if the classpath entries are to be appended to any
     * existing classpath, or false if they replace the existing classpath.
     * @see #setClassPath(String)
     */
    public void addClassPaths(Resource lib)
    {
        if (isStarted())
            log.warn("classpaths set while started");

        if (lib.exists() && lib.isDirectory())
        {
            String[] files=lib.list();
            for (int f=0;files!=null && f<files.length;f++)
            {
                try {
                    Resource fn=lib.addPath(files[f]);
                    String fnlc=fn.getName().toLowerCase();
                    if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip"))
                    {
                        addClassPath(fn.toString());
                    }
                }
                catch (Exception ex)
                {
                    log.warn(LogSupport.EXCEPTION,ex);
                }
            }
        }
    }

    /* ------------------------------------------------------------ */
    /** Get Java2 compliant classloading.
     * @return If true, the class loader will conform to the java 2
     * specification and delegate all loads to the parent classloader. If
     * false, the context classloader only delegate loads for system classes
     * or classes that it can't find itself.
     */
    public boolean isClassLoaderJava2Compliant()
    {
        return _classLoaderJava2Compliant;
    }

    /* ------------------------------------------------------------ */
    /** Set Java2 compliant classloading.
     * @param compliant If true, the class loader will conform to the java 2
     * specification and delegate all loads to the parent classloader. If
     * false, the context classloader only delegate loads for system classes
     * or classes that it can't find itself.
     */
    public void setClassLoaderJava2Compliant(boolean compliant)
    {
        _classLoaderJava2Compliant = compliant;
        if (_loader!=null && (_loader instanceof ContextLoader))
            ((ContextLoader)_loader).setJava2Compliant(compliant);
    }

    /* ------------------------------------------------------------ */
    /** Set temporary directory for context.
     * The javax.servlet.context.tempdir attribute is also set.
     * @param dir Writable temporary directory.
     */
    public void setTempDirectory(File dir)
    {
        if (isStarted())
            throw new IllegalStateException("Started");

        if (dir!=null)
        {
            try{dir=new File(dir.getCanonicalPath());}
            catch (IOException e){log.warn(LogSupport.EXCEPTION,e);}
        }

        if (dir!=null && !dir.exists())
        {
            dir.mkdir();
            dir.deleteOnExit();
        }

        if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
            throw new IllegalArgumentException("Bad temp directory: "+dir);

        _tmpDir=dir;
        setAttribute("javax.servlet.context.tempdir",_tmpDir);
    }

    /* ------------------------------------------------------------ */
    /** Get Context temporary directory.
     * A tempory directory is generated if it has not been set.  The
     * "javax.servlet.context.tempdir" attribute is consulted and if
     * not set, the host, port and context are used to generate a
     * directory within the JVMs temporary directory.
     * @return Temporary directory as a File.
     */
    public File getTempDirectory()
    {
        if (_tmpDir!=null)
            return _tmpDir;

        // Initialize temporary directory
        //
        // I'm afraid that this is very much black magic.
        // but if you can think of better....
        Object t = getAttribute("javax.servlet.context.tempdir");

        if (t!=null && (t instanceof File))
        {
            _tmpDir=(File)t;
            if (_tmpDir.isDirectory() && _tmpDir.canWrite())
                return _tmpDir;
        }

        if (t!=null && (t instanceof String))
        {
            try
            {
                _tmpDir=new File((String)t);

                if (_tmpDir.isDirectory() && _tmpDir.canWrite())
                {
                    if(log.isDebugEnabled())log.debug("Converted to File "+_tmpDir+" for "+this);
                    setAttribute("javax.servlet.context.tempdir",_tmpDir);
                    return _tmpDir;
                }
            }
            catch(Exception e)
            {
                log.warn(LogSupport.EXCEPTION,e);
            }
        }

        // No tempdir so look for a WEB-INF/work directory to use as tempDir base
        File work=null;
        try
        {
            work=new File(System.getProperty("jetty.home"),"work");
            if (!work.exists() || !work.canWrite() || !work.isDirectory())
                work=null;
        }
        catch(Exception e)
        {
            LogSupport.ignore(log,e);
        }

        // No tempdir set so make one!
        try
        {
            HttpListener httpListener=_httpServer.getListeners()[0];

            String vhost = null;
            for (int h=0;vhost==null && _vhosts!=null && h<_vhosts.size();h++)
                vhost=(String)_vhosts.get(h);
            String host=httpListener.getHost();
            String temp="Jetty_"+
                (host==null?"":host)+
                "_"+
                httpListener.getPort()+
                "_"+
                (vhost==null?"":vhost)+
                getContextPath();

            temp=temp.replace('/','_');
            temp=temp.replace('.','_');
            temp=temp.replace('\\','_');

            
            if (work!=null)
                _tmpDir=new File(work,temp);
            else
            {
                _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
                
                if (_tmpDir.exists())
                {
                    if(log.isDebugEnabled())log.debug("Delete existing temp dir "+_tmpDir+" for "+this);
                    if (!IO.delete(_tmpDir))
                    {
                        if(log.isDebugEnabled())log.debug("Failed to delete temp dir "+_tmpDir);
                    }
                
                    if (_tmpDir.exists())
                    {
                        String old=_tmpDir.toString();
                        _tmpDir=File.createTempFile(temp+"_","");
                        if (_tmpDir.exists())
                            _tmpDir.delete();
                        log.warn("Can't reuse "+old+", using "+_tmpDir);
                    }
                }
            }

            if (!_tmpDir.exists())
                _tmpDir.mkdir();
            if (work==null)
                _tmpDir.deleteOnExit();
            if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this);
        }
        catch(Exception e)
        {
            _tmpDir=null;
            LogSupport.ignore(log,e);
        }

        if (_tmpDir==null)
        {
            try{
                // that didn't work, so try something simpler (ish)
                _tmpDir=File.createTempFile("JettyContext","");
                if (_tmpDir.exists())
                    _tmpDir.delete();
                _tmpDir.mkdir();
                _tmpDir.deleteOnExit();
                if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this);
            }
            catch(IOException e)
            {
                log.fatal(e); System.exit(1);
            }
        }

        setAttribute("javax.servlet.context.tempdir",_tmpDir);
        return _tmpDir;
    }



    /* ------------------------------------------------------------ */
    /** Set ClassLoader.
     * @param loader The loader to be used by this context.
     */
    public synchronized void setClassLoader(ClassLoader loader)
    {
        if (isStarted())
            throw new IllegalStateException("Started");
        _loader=loader;
    }


    /* ------------------------------------------------------------ */
    /** Get the classloader.
     * If no classloader has been set and the context has been loaded
     * normally, then null is returned.
     * If no classloader has been set and the context was loaded from
     * a classloader, that loader is returned.
     * If a classloader has been set and no classpath has been set then
     * the set classloader is returned.
     * If a classloader and a classpath has been set, then a new
     * URLClassloader initialized on the classpath with the set loader as a
     * partent is return.
     * @return Classloader or null.
     */
    public synchronized ClassLoader getClassLoader()
    {
        return _loader;
    }

    /* ------------------------------------------------------------ */
    /** Set Parent ClassLoader.
     * By default the parent loader is the thread context classloader
     * of the thread that calls initClassLoader.  If setClassLoader is
     * called, then the parent is ignored.
     * @param loader The class loader to use for the parent loader of
     * the context classloader.
     */
    public synchronized void setParentClassLoader(ClassLoader loader)
    {
        if (isStarted())
            throw new IllegalStateException("Started");
        _parent=loader;
    }

    /* ------------------------------------------------------------ */
    public ClassLoader getParentClassLoader()
    {
        return _parent;
    }

    /* ------------------------------------------------------------ */
    /** Initialize the context classloader.
     * Initialize the context classloader with the current parameters.
     * Any attempts to change the classpath after this call will
     * result in a IllegalStateException
     * @param forceContextLoader If true, a ContextLoader is always if
     * no loader has been set.
     */
    protected void initClassLoader(boolean forceContextLoader)
        throws MalformedURLException, IOException
    {
        ClassLoader parent=_parent;
        if (_loader==null)
        {
            // If no parent, then try this threads classes loader as parent
            if (parent==null)
                parent=Thread.currentThread().getContextClassLoader();

            // If no parent, then try this classes loader as parent
            if (parent==null)
                parent=this.getClass().getClassLoader();

            if(log.isDebugEnabled())log.debug("Init classloader from "+_classPath+
                       ", "+parent+" for "+this);

            if (forceContextLoader || _classPath!=null || _permissions!=null)
            {
                ContextLoader loader=new ContextLoader(this,_classPath,parent,_permissions);
                loader.setJava2Compliant(_classLoaderJava2Compliant);
                _loader=loader;
            }
            else
                _loader=parent;
        }
    }

    /* ------------------------------------------------------------ */
    public synchronized Class loadClass(String className)
        throws ClassNotFoundException
    {
        if (_loader==null)
        {
            try{initClassLoader(false);}
            catch(Exception e)
            {
                log.warn(LogSupport.EXCEPTION,e);
                return null;
            }
        }

        if (className==null)
            return null;

        if (_loader == null) 
            return Class.forName(className); 
        return _loader.loadClass(className);
    }

    /* ------------------------------------------------------------ */
    /** Set the realm name.
     * @param realmName The name to use to retrieve the actual realm
     * from the HttpServer
     */
    public void setRealmName(String realmName)
    {
        _realmName=realmName;
    }

    /* ------------------------------------------------------------ */
    public String getRealmName()
    {
        return _realmName;
    }

    /* ------------------------------------------------------------ */
    /** Set the  realm.
     */
    public void setRealm(UserRealm realm)
    {
        _userRealm=realm;
    }

    /* ------------------------------------------------------------ */
    public UserRealm getRealm()
    {
        return _userRealm;
    }

    /* ------------------------------------------------------------ */
    public Authenticator getAuthenticator()
    {
        return _authenticator;
    }

    /* ------------------------------------------------------------ */
    public void setAuthenticator(Authenticator authenticator)
    {
        _authenticator=authenticator;
    }

    /* ------------------------------------------------------------ */
    public void addSecurityConstraint(String pathSpec, SecurityConstraint sc)
    {
        Object scs = _constraintMap.get(pathSpec);
        scs = LazyList.add(scs,sc);
        _constraintMap.put(pathSpec,scs);
        
        if(log.isDebugEnabled())log.debug("added "+sc+" at "+pathSpec);
    }

    /* ------------------------------------------------------------ */
    public void clearSecurityConstraints()
    {
        _constraintMap.clear();
    }

    /* ------------------------------------------------------------ */
    public boolean checkSecurityConstraints(
        String pathInContext,
        HttpRequest request,
        HttpResponse response)
        throws HttpException, IOException
    {
        UserRealm realm= getRealm();

        List scss= _constraintMap.getMatches(pathInContext);
        String pattern=null;
        if (scss != null && scss.size() > 0)
        {
            Object constraints= null;

            // for each path match
            // Add only constraints that have the correct method
            // break if the matching pattern changes.  This allows only
            // constraints with matching pattern and method to be combined.
            loop:
            for (int m= 0; m < scss.size(); m++)
            {
                Map.Entry entry= (Map.Entry)scss.get(m);
                Object scs= entry.getValue();
                String p=(String)entry.getKey();
                for (int c=0;c<LazyList.size(scs);c++)
                {
                	SecurityConstraint sc=(SecurityConstraint)LazyList.get(scs,c);
					if (!sc.forMethod(request.getMethod()))
						continue;
						
					if (pattern!=null && !pattern.equals(p))
						break loop;
					pattern=p;	
	                constraints= LazyList.add(constraints, sc);
                }
            }
            
            return SecurityConstraint.check(
                LazyList.getList(constraints),
                _authenticator,
                realm,
                pathInContext,
                request,
                response);
        }
        request.setUserPrincipal(HttpRequest.__NOT_CHECKED);
        return true;
    }

    /* ------------------------------------------------------------ */
    /** Set null path redirection.
     * @param b if true a /context request will be redirected to
     * /context/ if there is not path in the context.
     */
    public void setRedirectNullPath(boolean b)
    {
        _redirectNullPath=b;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return True if a /context request is redirected to /context/ if
     * there is not path in the context.
     */
    public boolean isRedirectNullPath()
    {
        return _redirectNullPath;
    }



    /* ------------------------------------------------------------ */
    /** Set the permissions to be used for this context.
     * The collection of permissions set here are used for all classes
     * loaded by this context.  This is simpler that creating a
     * security policy file, as not all code sources may be statically
     * known.
     * @param permissions
     */
    public void setPermissions(PermissionCollection permissions)
    {
        _permissions=permissions;
    }

    /* ------------------------------------------------------------ */
    /** Get the permissions to be used for this context.
     */
    public PermissionCollection getPermissions()
    {
        return _permissions;
    }

    /* ------------------------------------------------------------ */
    /** Add a permission to this context.
     * The collection of permissions set here are used for all classes
     * loaded by this context.  This is simpler that creating a
     * security policy file, as not all code sources may be statically
     * known.
     * @param permission
     */
    public void addPermission(Permission permission)
    {
        if (_permissions==null)
            _permissions=new Permissions();
        _permissions.add(permission);
    }

    /* ------------------------------------------------------------ */
    /** Handler request.
     * Determine the path within the context and then call
     * handle(pathInContext,request,response).
     * @param request
     * @param response
     * @return True if the request has been handled.
     * @exception HttpException
     * @exception IOException
     */
    public void handle(HttpRequest request,
                       HttpResponse response)
        throws HttpException, IOException
    {
        if (!isStarted() || _gracefulStop)
            return;

        // reject requests by real host
        if (_hosts!=null && _hosts.size()>0)
        {
            Object o = request.getHttpConnection().getConnection();
            if (o instanceof Socket)
            {
                Socket s=(Socket)o;
                if (!_hosts.contains(s.getLocalAddress()))
                {
                    if(log.isDebugEnabled())log.debug(s.getLocalAddress()+" not in "+_hosts);
                    return;
                }
            }
        }
        
        // handle stats
        if (_statsOn)
        {
            synchronized(_statsLock)
            {
                _requests++;
                _requestsActive++;
                if (_requestsActive>_requestsActiveMax)
                    _requestsActiveMax=_requestsActive;
            }
        }

        String pathInContext = URI.canonicalPath(request.getPath());
        if (pathInContext==null)
        {
            // Must be a bad request.
            throw new HttpException(HttpResponse.__400_Bad_Request);
        }

        if (_contextPath.length()>1)
            pathInContext=pathInContext.substring(_contextPath.length());

        if (_redirectNullPath && (pathInContext==null ||
                                  pathInContext.length()==0))
        {
            StringBuffer buf=request.getRequestURL();
            buf.append("/");
            String q=request.getQuery();
            if (q!=null&&q.length()!=0)
                buf.append("?"+q);
                
            response.sendRedirect(buf.toString());
            if (log.isDebugEnabled())
                log.debug(this+" consumed all of path "+
                             request.getPath()+
                             ", redirect to "+buf.toString());
            return;
        }

        String pathParams=null;
        int semi = pathInContext.lastIndexOf(';');
        if (semi>=0)
        {
            int pl = pathInContext.length()-semi;
            String ep=request.getEncodedPath();
            if(';'==ep.charAt(ep.length()-pl))
            {
                pathParams=pathInContext.substring(semi+1);
                pathInContext=pathInContext.substring(0,semi);
            }
        }

        try
        {
            handle(pathInContext,pathParams,request,response);
        }
        finally
        {
            if (_userRealm!=null && request.hasUserPrincipal())
                _userRealm.disassociate(request.getUserPrincipal());
        }
    }

    /* ------------------------------------------------------------ */
    /** Handler request.
     * Call each HttpHandler until request is handled.
     * @param pathInContext Path in context
     * @param pathParams Path parameters such as encoded Session ID
     * @param request
     * @param response
     * @return True if the request has been handled.
     * @exception HttpException
     * @exception IOException
     */
    public void handle(String pathInContext,
                       String pathParams,
                       HttpRequest request,
                       HttpResponse response)
        throws HttpException, IOException
    {
        Object old_scope= null;
        try
        {
            old_scope= enterContextScope(request,response);
            HttpHandler[] handlers= getHandlers();
            for (int k= 0; k < handlers.length; k++)
            {
                HttpHandler handler= handlers[k];
                if (handler == null)
                {
                    handlers= getHandlers();
                    k= -1;
                    continue;
                }
                if (!handler.isStarted())
                {
                    if (log.isDebugEnabled())
                        log.debug(handler + " not started in " + this);
                    continue;
                }
                if (log.isDebugEnabled())
                    log.debug("Handler " + handler);
                handler.handle(pathInContext, pathParams, request, response);
                if (request.isHandled())
                {
                    if (log.isDebugEnabled())
                        log.debug("Handled by " + handler);
                    return;
                }
            }
            return;
        }
        finally
        {
            leaveContextScope(request, response, old_scope);
        }
    }

    /* ------------------------------------------------------------ */
    /** Enter the context scope.
     * This method is called (by handle or servlet dispatchers) to indicate that
     * request handling is entering the scope of this context.  The opaque scope object
     * returned, should be passed to the leaveContextScope method.
     */
    public Object enterContextScope(HttpRequest request, HttpResponse response)
    {
        // Save the thread context loader
        Thread thread = Thread.currentThread();
        ClassLoader cl=thread.getContextClassLoader();
        HttpContext c=response.getHttpContext();

        Scope scope=null;
        if (cl!=HttpContext.class.getClassLoader() || c!=null)
        {
            scope=new Scope();
            scope._classLoader=cl;
            scope._httpContext=c;
        }
        
        if (_loader!=null)
            thread.setContextClassLoader(_loader);
        response.setHttpContext(this);
            
        return scope;
    }
    
    /* ------------------------------------------------------------ */
    /** Leave the context scope.
     * This method is called (by handle or servlet dispatchers) to indicate that
     * request handling is leaveing the scope of this context.  The opaque scope object
     * returned by enterContextScope should be passed in.
     */
    public void leaveContextScope(HttpRequest request, HttpResponse response,Object oldScope)
    {
        if (oldScope==null)
        {
            Thread.currentThread()
                .setContextClassLoader(HttpContext.class.getClassLoader());
            response.setHttpContext(null);
        }
        else
        {
            Scope old = (Scope)oldScope;
            Thread.currentThread().setContextClassLoader(old._classLoader);
            response.setHttpContext(old._httpContext);
        }
    }
    

    /* ------------------------------------------------------------ */
    public String getHttpContextName()
    {
        if (_contextName==null)
            _contextName = (_vhosts.size()>1?(_vhosts.toString()+":"):"")+_contextPath;
        return _contextName;
    }

    /* ------------------------------------------------------------ */
    public void setHttpContextName(String s)
    {
        _contextName=s;
    }
    
    /* ------------------------------------------------------------ */
    public String toString()
    {
        return "HttpContext["+getContextPath()+","+getHttpContextName()+"]";
    }

    /* ------------------------------------------------------------ */
    public String toString(boolean detail)
    {
        return "HttpContext["+getContextPath()+","+getHttpContextName()+"]" +
            (detail?("="+_handlers):"");
    }

    
    /* ------------------------------------------------------------ */
    protected synchronized void doStart()
        throws Exception
    {
        if (isStarted())
            return;

        if (_httpServer.getServerClasses()!=null)
            _serverClasses=_httpServer.getServerClasses();
        if (_httpServer.getSystemClasses()!=null)
            _systemClasses=_httpServer.getSystemClasses();
        
        _resources.start();
        
        statsReset();

        if (_httpServer==null)
            throw new IllegalStateException("No server for "+this);

        // start the context itself
        _resources.getMimeMap();
        _resources.getEncodingMap();

        // Setup realm
        if (_userRealm==null && _authenticator!=null)
        {
            _userRealm=_httpServer.getRealm(_realmName);
            if (_userRealm==null)
                log.warn("No Realm: "+_realmName);
        }

        // setup the context loader
        initClassLoader(false);

        // Set attribute if needed
        String attr = getInitParameter(__fileClassPathAttr);
        if (attr!=null && attr.length()>0)
            setAttribute(attr,getFileClassPath());

        // Start the handlers
        Thread thread = Thread.currentThread();
        ClassLoader lastContextLoader=thread.getContextClassLoader();
        try
        {
            if (_loader!=null)
                thread.setContextClassLoader(_loader);

            if (_requestLog!=null)
                _requestLog.start();
            
            startHandlers();
        }
        finally
        {
            thread.setContextClassLoader(lastContextLoader);
            getHandlers();
        }

    }

    /* ------------------------------------------------------------ */
    /** Start the handlers.
     * This is called by start after the classloader has been
     * initialized and set as the thread context loader.
     * It may be specialized to provide custom handling
     * before any handlers are started.
     * @exception Exception
     */
    protected void startHandlers()
        throws Exception
    {
        // Prepare a multi exception
        MultiException mx = new MultiException();

        Iterator handlers = _handlers.iterator();
        while(handlers.hasNext())
        {
            HttpHandler handler=(HttpHandler)handlers.next();
            if (!handler.isStarted())
                try{handler.start();}catch(Exception e){mx.add(e);}
        }
        mx.ifExceptionThrow();
    }

    /* ------------------------------------------------------------ */
    /** Stop the context.
     * @param graceful If true and statistics are on, then this method will wait
     * for requestsActive to go to zero before calling stop()
     */
    public void stop(boolean graceful)
        throws InterruptedException
    {
        boolean gs=_gracefulStop;
        try
        {
            _gracefulStop=true;
            
            // wait for all requests to complete.
            while (graceful && _statsOn && _requestsActive>0 && _httpServer!=null)
                try {Thread.sleep(100);}
            catch (InterruptedException e){throw e;}
            catch (Exception e){LogSupport.ignore(log,e);}
            
            stop();
        }
        finally
        {
            _gracefulStop=gs;
        }
    }

    /* ------------------------------------------------------------ */
    /** Stop the context.
     */
    protected void doStop()
        throws Exception
    {
        if (_httpServer==null)
            throw new InterruptedException("Destroy called");

        synchronized(this)
        {
            // Notify the container for the stop
            Thread thread = Thread.currentThread();
            ClassLoader lastContextLoader=thread.getContextClassLoader();
            try
            {
                if (_loader!=null)
                    thread.setContextClassLoader(_loader);
                Iterator handlers = _handlers.iterator();
                while(handlers.hasNext())
                {
                    HttpHandler handler=(HttpHandler)handlers.next();
                    if (handler.isStarted())
                    {
                        try{handler.stop();}
                        catch(Exception e){log.warn(LogSupport.EXCEPTION,e);}
                    }
                }
                
                if (_requestLog!=null)
                    _requestLog.stop();
            }
            finally
            {
                thread.setContextClassLoader(lastContextLoader);
            }
            
            // TODO this is a poor test
            if (_loader instanceof ContextLoader)
            {
                ((ContextLoader)_loader).destroy();
                LogFactory.release(_loader);
            }
            
            _loader=null;
        }
        _resources.flushCache();
        _resources.stop();
    }


    /* ------------------------------------------------------------ */
    /** Destroy a context.
     * Destroy a context and remove it from the HttpServer. The
     * HttpContext must be stopped before it can be destroyed.
     */
    public void destroy()
    {
        if (isStarted())
            throw new IllegalStateException("Started");

        if (_httpServer!=null)
            _httpServer.removeContext(this);

        _httpServer=null;
        
        if (_handlers!=null)
            _handlers.clear();
        
        _handlers=null;
        _parent=null;
        _loader=null;
        if (_attributes!=null)
            _attributes.clear();
        _attributes=null;
        if (_initParams!=null)
            _initParams.clear();
        _initParams=null;
        if (_vhosts!=null)
            _vhosts.clear();
        _vhosts=null;
        _hosts=null;
        _tmpDir=null;

        _permissions=null;
        
        removeComponent(_resources);
        if (_resources!=null)
        {
            _resources.flushCache();
            if (_resources.isStarted())
                try{_resources.stop();}catch(Exception e){LogSupport.ignore(log,e);}
                _resources.destroy();
        }
        _resources=null;
        
        super.destroy();
        
    }


    /* ------------------------------------------------------------ */
    /** Set the request log.
     * @param log RequestLog to use.
     */
    public void setRequestLog(RequestLog log)
    {
        _requestLog=log;
    }

    /* ------------------------------------------------------------ */
    public RequestLog getRequestLog()
    {
        return _requestLog;
    }


    /* ------------------------------------------------------------ */
    /** Send an error response.
     * This method may be specialized to provide alternative error handling for
     * errors generated by the container.  The default implemenation calls HttpResponse.sendError
     * @param response the response to send
     * @param code The error code
     * @param msg The message for the error or null for the default
     * @throws IOException Problem sending response.
     */
    public void sendError(HttpResponse response,int code,String msg)
    	throws IOException
    {
        response.sendError(code,msg);
    }
    
    /* ------------------------------------------------------------ */
    /** Send an error response.
     * This method obtains the responses context and call sendError for context specific
     * error handling.
     * @param response the response to send
     * @param code The error code
     * @param msg The message for the error or null for the default
     * @throws IOException Problem sending response.
     */
    public static void sendContextError(HttpResponse response,int code,String msg)
    	throws IOException
    {
        HttpContext context = response.getHttpContext();
        if (context!=null)
            context.sendError(response,code,msg);
        else
            response.sendError(code,msg);
    }
    
    /* ------------------------------------------------------------ */
    /** True set statistics recording on for this context.
     * @param on If true, statistics will be recorded for this context.
     */
    public void setStatsOn(boolean on)
    {
        log.info("setStatsOn "+on+" for "+this);
        _statsOn=on;
        statsReset();
    }

    /* ------------------------------------------------------------ */
    public boolean getStatsOn() {return _statsOn;}

    /* ------------------------------------------------------------ */
    public long getStatsOnMs()
    {return _statsOn?(System.currentTimeMillis()-_statsStartedAt):0;}

    /* ------------------------------------------------------------ */
    public void statsReset()
    {
        synchronized(_statsLock)
        {
            if (_statsOn)
                _statsStartedAt=System.currentTimeMillis();
            _requests=0;
            _requestsActiveMax=_requestsActive;
            _responses1xx=0;
            _responses2xx=0;
            _responses3xx=0;
            _responses4xx=0;
            _responses5xx=0;
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of requests handled by this context
     * since last call of statsReset(). If setStatsOn(false) then this
     * is undefined.
     */
    public int getRequests() {return _requests;}

    /* ------------------------------------------------------------ */
    /**
     * @return Number of requests currently active.
     * Undefined if setStatsOn(false).
     */
    public int getRequestsActive() {return _requestsActive;}

    /* ------------------------------------------------------------ */
    /**
     * @return Maximum number of active requests
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getRequestsActiveMax() {return _requestsActiveMax;}

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of responses with a 2xx status returned
     * by this context since last call of statsReset(). Undefined if
     * if setStatsOn(false).
     */
    public int getResponses1xx() {return _responses1xx;}

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of responses with a 100 status returned
     * by this context since last call of statsReset(). Undefined if
     * if setStatsOn(false).
     */
    public int getResponses2xx() {return _responses2xx;}

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of responses with a 3xx status returned
     * by this context since last call of statsReset(). Undefined if
     * if setStatsOn(false).
     */
    public int getResponses3xx() {return _responses3xx;}

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of responses with a 4xx status returned
     * by this context since last call of statsReset(). Undefined if
     * if setStatsOn(false).
     */
    public int getResponses4xx() {return _responses4xx;}

    /* ------------------------------------------------------------ */
    /**
     * @return Get the number of responses with a 5xx status returned
     * by this context since last call of statsReset(). Undefined if
     * if setStatsOn(false).
     */
    public int getResponses5xx() {return _responses5xx;}


    /* ------------------------------------------------------------ */
    /** Log a request and response.
     * Statistics are also collected by this method.
     * @param request
     * @param response
     */
    public void log(HttpRequest request,
                    HttpResponse response,
                    int length)
    {
        if (_statsOn)
        {
            synchronized(_statsLock)
            {
                if (--_requestsActive<0)
                    _requestsActive=0;

                if (response!=null)
                {
                    switch(response.getStatus()/100)
                    {
                      case 1: _responses1xx++;break;
                      case 2: _responses2xx++;break;
                      case 3: _responses3xx++;break;
                      case 4: _responses4xx++;break;
                      case 5: _responses5xx++;break;
                    }
                }
            }
        }

        if (_requestLog!=null &&
            request!=null &&
            response!=null)
            _requestLog.log(request,response,length);
        else if (_httpServer!=null)
            _httpServer.log(request,response,length);
    }

    

    /* ------------------------------------------------------------ */
    /* Class to save scope of nested context calls
     */
    private static class Scope 
    {
        ClassLoader _classLoader;
        HttpContext _httpContext;
    }

    /* 
     * @see org.mortbay.http.HttpHandler#getName()
     */
    public String getName()
    {
        return this.getContextPath();
    }

    /* 
     * @see org.mortbay.http.HttpHandler#getHttpContext()
     */
    public HttpContext getHttpContext()
    {
        return this;
    }

    /* 
     * @see org.mortbay.http.HttpHandler#initialize(org.mortbay.http.HttpContext)
     */
    public void initialize(HttpContext context)
    {
        throw new UnsupportedOperationException();
    }   
    
    
    /**
     * @return
     */
    public Resource getBaseResource()
    {
        return _resources.getBaseResource();
    }
    /**
     * @param type
     * @return
     */
    public String getEncodingByMimeType(String type)
    {
        return _resources.getEncodingByMimeType(type);
    }
    /**
     * @return
     */
    public Map getEncodingMap()
    {
        return _resources.getEncodingMap();
    }
    /**
     * @return
     */
    public int getMaxCachedFileSize()
    {
        return _resources.getMaxCachedFileSize();
    }
    /**
     * @return
     */
    public int getMaxCacheSize()
    {
        return _resources.getMaxCacheSize();
    }
    /**
     * @param filename
     * @return
     */
    public String getMimeByExtension(String filename)
    {
        return _resources.getMimeByExtension(filename);
    }
    /**
     * @return
     */
    public Map getMimeMap()
    {
        return _resources.getMimeMap();
    }
    /**
     * @param pathInContext
     * @return
     * @throws IOException
     */
    public Resource getResource(String pathInContext) throws IOException
    {
        return _resources.getResource(pathInContext);
    }
    /**
     * @return
     */
    public String getResourceBase()
    {
        return _resources.getResourceBase();
    }
    /**
     * @param resource
     * @return
     */
    public ResourceMetaData getResourceMetaData(Resource resource)
    {
        return _resources.getResourceMetaData(resource);
    }
    /**
     * @param base
     */
    public void setBaseResource(Resource base)
    {
        _resources.setBaseResource(base);
    }
    /**
     * @param encodingMap
     */
    public void setEncodingMap(Map encodingMap)
    {
        _resources.setEncodingMap(encodingMap);
    }
    /**
     * @param maxCachedFileSize
     */
    public void setMaxCachedFileSize(int maxCachedFileSize)
    {
        _resources.setMaxCachedFileSize(maxCachedFileSize);
    }
    /**
     * @param maxCacheSize
     */
    public void setMaxCacheSize(int maxCacheSize)
    {
        _resources.setMaxCacheSize(maxCacheSize);
    }
    /**
     * @param mimeMap
     */
    public void setMimeMap(Map mimeMap)
    {
        _resources.setMimeMap(mimeMap);
    }
    /**
     * @param extension
     * @param type
     */
    public void setMimeMapping(String extension, String type)
    {
        _resources.setMimeMapping(extension, type);
    }
    /**
     * @param resourceBase
     */
    public void setResourceBase(String resourceBase)
    {
        _resources.setResourceBase(resourceBase);
    }
    /**
     * @param mimeType
     * @param encoding
     */
    public void setTypeEncoding(String mimeType, String encoding)
    {
        _resources.setTypeEncoding(mimeType, encoding);
    }
}
