view src/org/eclipse/jetty/server/handler/ContextHandler.java @ 841:f4d7e9fd3f67

remove ConnectHandler
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 19 Sep 2016 14:26:30 -0600
parents 83765eb09bef
children 22a4e93ed20e
line wrap: on
line source

//
//  ========================================================================
//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server.handler;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HandlerContainer;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AggregateLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jetty.util.resource.Resource;

/* ------------------------------------------------------------ */
/**
 * ContextHandler.
 *
 * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
 *
 * <p>
 * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
 * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
 * <p>
 * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
 * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
 * 
 * @org.apache.xbean.XBean description="Creates a basic HTTP context"
 */
public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
{
	private static final Logger LOG = LoggerFactory.getLogger(ContextHandler.class);

	private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();

	/**
	 * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
	 * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
	 * for the attribute value.
	 */
	public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";

	/* ------------------------------------------------------------ */
	/**
	 * Get the current ServletContext implementation.
	 *
	 * @return ServletContext implementation
	 */
	public static Context getCurrentContext()
	{
		return __context.get();
	}

	protected Context _scontext;

	private final AttributesMap _attributes;
	private final AttributesMap _contextAttributes;
	private final Map<String, String> _initParams;
	private ClassLoader _classLoader;
	private String _contextPath = "/";
	private String _displayName;
	private Resource _baseResource;
	private MimeTypes _mimeTypes;
	private Map<String, String> _localeEncodingMap;
	private String[] _welcomeFiles;
	private ErrorHandler _errorHandler;
	private String[] _vhosts;
	private Set<String> _connectors;
	private EventListener[] _eventListeners;
	private Logger _logger;
	private boolean _allowNullPathInfo;
	private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
	private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
	private boolean _compactPath = false;
	private boolean _aliasesAllowed = false;

	private Object _contextListeners;
	private Object _contextAttributeListeners;
	private Object _requestListeners;
	private Object _requestAttributeListeners;
	private Object _durableListeners;
	private Map<String, Object> _managedAttributes;
	private String[] _protectedTargets;
	private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();

	private boolean _shutdown = false;
	private boolean _available = true;
	private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE

	private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3;

	/* ------------------------------------------------------------ */
	/**
	 *
	 */
	public ContextHandler()
	{
		super();
		_scontext = new Context();
		_attributes = new AttributesMap();
		_contextAttributes = new AttributesMap();
		_initParams = new HashMap<String, String>();
		addAliasCheck(new ApproveNonExistentDirectoryAliases());
	}

	/* ------------------------------------------------------------ */
	/**
	 *
	 */
	protected ContextHandler(Context context)
	{
		super();
		_scontext = context;
		_attributes = new AttributesMap();
		_contextAttributes = new AttributesMap();
		_initParams = new HashMap<String, String>();
		addAliasCheck(new ApproveNonExistentDirectoryAliases());
	}

	/* ------------------------------------------------------------ */
	/**
	 *
	 */
	public ContextHandler(String contextPath)
	{
		this();
		setContextPath(contextPath);
	}

	/* ------------------------------------------------------------ */
	/**
	 *
	 */
	public ContextHandler(HandlerContainer parent, String contextPath)
	{
		this();
		setContextPath(contextPath);
		if (parent instanceof HandlerWrapper)
			((HandlerWrapper)parent).setHandler(this);
		else if (parent instanceof HandlerCollection)
			((HandlerCollection)parent).addHandler(this);
	}

	/* ------------------------------------------------------------ */
	@Override
	public void dump(Appendable out, String indent) throws IOException
	{
		dumpThis(out);
		dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(),
				_attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet());
	}

	/* ------------------------------------------------------------ */
	public Context getServletContext()
	{
		return _scontext;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return the allowNullPathInfo true if /context is not redirected to /context/
	 */
	public boolean getAllowNullPathInfo()
	{
		return _allowNullPathInfo;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param allowNullPathInfo
	 *            true if /context is not redirected to /context/
	 */
	public void setAllowNullPathInfo(boolean allowNullPathInfo)
	{
		_allowNullPathInfo = allowNullPathInfo;
	}

	/* ------------------------------------------------------------ */
	@Override
	public void setServer(Server server)
	{
		if (_errorHandler != null)
		{
			Server old_server = getServer();
			if (old_server != null && old_server != server)
				old_server.getContainer().update(this,_errorHandler,null,"error",true);
			super.setServer(server);
			if (server != null && server != old_server)
				server.getContainer().update(this,null,_errorHandler,"error",true);
			_errorHandler.setServer(server);
		}
		else
			super.setServer(server);
	}

	/* ------------------------------------------------------------ */
	/**
	 * 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 vhosts
	 *            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 be
	 *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
	 */
	public void setVirtualHosts(String[] vhosts)
	{
		if (vhosts == null)
		{
			_vhosts = vhosts;
		}
		else
		{
			_vhosts = new String[vhosts.length];
			for (int i = 0; i < vhosts.length; i++)
				_vhosts[i] = normalizeHostname(vhosts[i]);
		}
	}

	/* ------------------------------------------------------------ */
	/** Either set virtual hosts or add to an existing set of virtual hosts.
	 *
	 * @param virtualHosts
	 *            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 be
	 *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
	 */
	public void addVirtualHosts(String[] virtualHosts)
	{
		if (virtualHosts == null)  // since this is add, we don't null the old ones
		{
			return;
		}
		else
		{
			List<String> currentVirtualHosts = null;
			if (_vhosts != null)
			{
				currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
			}
			else
			{
				currentVirtualHosts = new ArrayList<String>();
			}

			for (int i = 0; i < virtualHosts.length; i++)
			{
				String normVhost = normalizeHostname(virtualHosts[i]);
				if (!currentVirtualHosts.contains(normVhost))
				{
					currentVirtualHosts.add(normVhost);
				}
			}
			_vhosts = currentVirtualHosts.toArray(new String[0]);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
	 *
	 *  @param virtualHosts
	 *            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 be
	 *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
	 */
	public void removeVirtualHosts(String[] virtualHosts)
	{
		if (virtualHosts == null)
		{
			return; // do nothing
		}
		else if ( _vhosts == null || _vhosts.length == 0)
		{
			return; // do nothing
		}
		else
		{
			List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));

			for (int i = 0; i < virtualHosts.length; i++)
			{
				String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
				if (existingVirtualHosts.contains(toRemoveVirtualHost))
				{
					existingVirtualHosts.remove(toRemoveVirtualHost);
				}
			}

			if (existingVirtualHosts.isEmpty())
			{
				_vhosts = null; // if we ended up removing them all, just null out _vhosts
			}
			else
			{
				_vhosts = existingVirtualHosts.toArray(new String[0]);
			}
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * 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. Host names may start with '*.' to wildcard one level of names.
	 */
	public String[] getVirtualHosts()
	{
		return _vhosts;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return an array of connector names that this context will accept a request from.
	 */
	public String[] getConnectorNames()
	{
		if (_connectors == null || _connectors.size() == 0)
			return null;

		return _connectors.toArray(new String[_connectors.size()]);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the names of accepted connectors.
	 *
	 * Names are either "host:port" or a specific configured name for a connector.
	 *
	 * @param connectors
	 *            If non null, an array of connector names that this context will accept a request from.
	 */
	public void setConnectorNames(String[] connectors)
	{
		if (connectors == null || connectors.length == 0)
			_connectors = null;
		else
			_connectors = new HashSet<String>(Arrays.asList(connectors));
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
	 */
	public Object getAttribute(String name)
	{
		return _attributes.getAttribute(name);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#getAttributeNames()
	 */
	@SuppressWarnings("unchecked")
	public Enumeration getAttributeNames()
	{
		return AttributesMap.getAttributeNamesCopy(_attributes);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the attributes.
	 */
	public Attributes getAttributes()
	{
		return _attributes;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the classLoader.
	 */
	public ClassLoader getClassLoader()
	{
		return _classLoader;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Make best effort to extract a file classpath from the context classloader
	 *
	 * @return Returns the classLoader.
	 */
	public String getClassPath()
	{
		if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
			return null;
		URLClassLoader loader = (URLClassLoader)_classLoader;
		URL[] urls = loader.getURLs();
		StringBuilder classpath = new StringBuilder();
		for (int i = 0; i < urls.length; i++)
		{
			try
			{
				Resource resource = newResource(urls[i]);
				File file = resource.getFile();
				if (file != null && file.exists())
				{
					if (classpath.length() > 0)
						classpath.append(File.pathSeparatorChar);
					classpath.append(file.getAbsolutePath());
				}
			}
			catch (IOException e)
			{
				LOG.debug("",e);
			}
		}
		if (classpath.length() == 0)
			return null;
		return classpath.toString();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the _contextPath.
	 */
	public String getContextPath()
	{
		return _contextPath;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
	 */
	public String getInitParameter(String name)
	{
		return _initParams.get(name);
	}

	/* ------------------------------------------------------------ */
	/*
	 */
	public String setInitParameter(String name, String value)
	{
		return _initParams.put(name,value);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#getInitParameterNames()
	 */
	@SuppressWarnings("rawtypes")
	public Enumeration getInitParameterNames()
	{
		return Collections.enumeration(_initParams.keySet());
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the initParams.
	 */
	public Map<String, String> getInitParams()
	{
		return _initParams;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#getServletContextName()
	 */
	public String getDisplayName()
	{
		return _displayName;
	}

	/* ------------------------------------------------------------ */
	public EventListener[] getEventListeners()
	{
		return _eventListeners;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the context event listeners.
	 *
	 * @param eventListeners
	 *            the event listeners
	 * @see ServletContextListener
	 * @see ServletContextAttributeListener
	 * @see ServletRequestListener
	 * @see ServletRequestAttributeListener
	 */
	public void setEventListeners(EventListener[] eventListeners)
	{
		_contextListeners = null;
		_contextAttributeListeners = null;
		_requestListeners = null;
		_requestAttributeListeners = null;

		_eventListeners = eventListeners;

		for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
		{
			EventListener listener = _eventListeners[i];

			if (listener instanceof ServletContextListener)
				_contextListeners = LazyList.add(_contextListeners,listener);

			if (listener instanceof ServletContextAttributeListener)
				_contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);

			if (listener instanceof ServletRequestListener)
				_requestListeners = LazyList.add(_requestListeners,listener);

			if (listener instanceof ServletRequestAttributeListener)
				_requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Add a context event listeners.
	 *
	 * @see ServletContextListener
	 * @see ServletContextAttributeListener
	 * @see ServletRequestListener
	 * @see ServletRequestAttributeListener
	 */
	public void addEventListener(EventListener listener)
	{
		//Only listeners added before the context starts last through a stop/start cycle
		if (!(isStarted() || isStarting()))
			_durableListeners = LazyList.add(_durableListeners, listener);
		
		setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class));
	}
	
   
	/**
	 * Apply any necessary restrictions on a programmatically added
	 * listener.
	 * 
	 * Superclasses should implement.
	 * 
	 * @param listener
	 */
	public void restrictEventListener (EventListener listener)
	{
	}

	

	/* ------------------------------------------------------------ */
	/**
	 * @return true if this context is accepting new requests
	 */
	public boolean isShutdown()
	{
		synchronized (this)
		{
			return !_shutdown;
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
	 * requests can complete, but no new requests are accepted.
	 *
	 * @param shutdown
	 *            true if this context is (not?) accepting new requests
	 */
	public void setShutdown(boolean shutdown)
	{
		synchronized (this)
		{
			_shutdown = shutdown;
			_availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return false if this context is unavailable (sends 503)
	 */
	public boolean isAvailable()
	{
		synchronized (this)
		{
			return _available;
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set Available status.
	 */
	public void setAvailable(boolean available)
	{
		synchronized (this)
		{
			_available = available;
			_availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
		}
	}

	/* ------------------------------------------------------------ */
	public Logger getLogger()
	{
		return _logger;
	}

	/* ------------------------------------------------------------ */
	public void setLogger(Logger logger)
	{
		_logger = logger;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see org.eclipse.thread.AbstractLifeCycle#doStart()
	 */
	@Override
	protected void doStart() throws Exception
	{
		_availability = __STOPPED;

		if (_contextPath == null)
			throw new IllegalStateException("Null contextPath");

		_logger = LoggerFactory.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
		ClassLoader old_classloader = null;
		Thread current_thread = null;
		Context old_context = null;

		try
		{
			// Set the classloader
			if (_classLoader != null)
			{
				current_thread = Thread.currentThread();
				old_classloader = current_thread.getContextClassLoader();
				current_thread.setContextClassLoader(_classLoader);
			}

			if (_mimeTypes == null)
				_mimeTypes = new MimeTypes();

			old_context = __context.get();
			__context.set(_scontext);

			// defers the calling of super.doStart()
			startContext();

			synchronized(this)
			{
				_availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE;
			}
		}
		finally
		{
			__context.set(old_context);

			// reset the classloader
			if (_classLoader != null)
			{
				current_thread.setContextClassLoader(old_classloader);
			}

		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
	 * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
	 *
	 * @see org.eclipse.jetty.server.handler.ContextHandler.Context
	 */
	protected void startContext() throws Exception
	{
		String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
		if (managedAttributes != null)
		{
			_managedAttributes = new HashMap<String, Object>();
			String[] attributes = managedAttributes.split(",");
			for (String attribute : attributes)
				_managedAttributes.put(attribute,null);

			Enumeration e = _scontext.getAttributeNames();
			while (e.hasMoreElements())
			{
				String name = (String)e.nextElement();
				Object value = _scontext.getAttribute(name);
				checkManagedAttribute(name,value);
			}
		}

		super.doStart();

		if (_errorHandler != null)
			_errorHandler.start();

		// Context listeners
		if (_contextListeners != null)
		{
			ServletContextEvent event = new ServletContextEvent(_scontext);
			for (int i = 0; i < LazyList.size(_contextListeners); i++)
			{
				callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event);
			}
		}
	}

	/* ------------------------------------------------------------ */
	public void callContextInitialized (ServletContextListener l, ServletContextEvent e)
	{
		l.contextInitialized(e);
	}

	/* ------------------------------------------------------------ */
	public void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
	{
		l.contextDestroyed(e);
	}
	
	/* ------------------------------------------------------------ */
	/*
	 * @see org.eclipse.thread.AbstractLifeCycle#doStop()
	 */
	@Override
	protected void doStop() throws Exception
	{
		_availability = __STOPPED;

		ClassLoader old_classloader = null;
		Thread current_thread = null;

		Context old_context = __context.get();
		__context.set(_scontext);
		try
		{
			// Set the classloader
			if (_classLoader != null)
			{
				current_thread = Thread.currentThread();
				old_classloader = current_thread.getContextClassLoader();
				current_thread.setContextClassLoader(_classLoader);
			}

			super.doStop();

			// Context listeners
			if (_contextListeners != null)
			{
				ServletContextEvent event = new ServletContextEvent(_scontext);
				for (int i = LazyList.size(_contextListeners); i-- > 0;)
				{
					((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
				}
			}
			
			//remove all non-durable listeners
			setEventListeners((EventListener[])LazyList.toArray(_durableListeners, EventListener.class));
			_durableListeners = null;

			if (_errorHandler != null)
				_errorHandler.stop();

			Enumeration e = _scontext.getAttributeNames();
			while (e.hasMoreElements())
			{
				String name = (String)e.nextElement();
				checkManagedAttribute(name,null);
			}
		}
		finally
		{
			LOG.info("stopped {}",this);
			__context.set(old_context);
			// reset the classloader
			if (_classLoader != null)
				current_thread.setContextClassLoader(old_classloader);
		}

		_contextAttributes.clearAttributes();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
	{
		DispatcherType dispatch = baseRequest.getDispatcherType();

		switch (_availability)
		{
			case __STOPPED:
			case __SHUTDOWN:
				return false;
			case __UNAVAILABLE:
				baseRequest.setHandled(true);
				response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
				return false;
			default:
				if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
					return false;
		}

		// Check the vhosts
		if (_vhosts != null && _vhosts.length > 0)
		{
			String vhost = normalizeHostname(baseRequest.getServerName());

			boolean match = false;

			// TODO non-linear lookup
			for (int i = 0; !match && i < _vhosts.length; i++)
			{
				String contextVhost = _vhosts[i];
				if (contextVhost == null)
					continue;
				if (contextVhost.startsWith("*."))
				{
					// wildcard only at the beginning, and only for one additional subdomain level
					match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
				}
				else
					match = contextVhost.equalsIgnoreCase(vhost);
			}
			if (!match)
				return false;
		}

		// Check the connector
		if (_connectors != null && _connectors.size() > 0)
		{
			String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName();
			if (connector == null || !_connectors.contains(connector))
				return false;
		}

		// Are we not the root context?
		if (_contextPath.length() > 1)
		{
			// reject requests that are not for us
			if (!target.startsWith(_contextPath))
				return false;
			if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
				return false;

			// redirect null path infos
			if (!_allowNullPathInfo && _contextPath.length() == target.length())
			{
				// context request must end with /
				baseRequest.setHandled(true);
				if (baseRequest.getQueryString() != null)
					response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
				else
					response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
				return false;
			}
		}

		return true;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
	{
		if (LOG.isDebugEnabled())
			LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);

		Context old_context = null;
		String old_context_path = null;
		String old_servlet_path = null;
		String old_path_info = null;
		ClassLoader old_classloader = null;
		Thread current_thread = null;
		String pathInfo = target;

		DispatcherType dispatch = baseRequest.getDispatcherType();

		old_context = baseRequest.getContext();

		// Are we already in this context?
		if (old_context != _scontext)
		{
			// check the target.
			if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getAsyncContinuation().isExpired()))
			{
				if (_compactPath)
					target = URIUtil.compactPath(target);
				if (!checkContext(target,baseRequest,response))
					return;

				if (target.length() > _contextPath.length())
				{
					if (_contextPath.length() > 1)
						target = target.substring(_contextPath.length());
					pathInfo = target;
				}
				else if (_contextPath.length() == 1)
				{
					target = URIUtil.SLASH;
					pathInfo = URIUtil.SLASH;
				}
				else
				{
					target = URIUtil.SLASH;
					pathInfo = null;
				}
			}

			// Set the classloader
			if (_classLoader != null)
			{
				current_thread = Thread.currentThread();
				old_classloader = current_thread.getContextClassLoader();
				current_thread.setContextClassLoader(_classLoader);
			}
		}

		try
		{
			old_context_path = baseRequest.getContextPath();
			old_servlet_path = baseRequest.getServletPath();
			old_path_info = baseRequest.getPathInfo();

			// Update the paths
			baseRequest.setContext(_scontext);
			__context.set(_scontext);
			if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
			{
				if (_contextPath.length() == 1)
					baseRequest.setContextPath("");
				else
					baseRequest.setContextPath(_contextPath);
				baseRequest.setServletPath(null);
				baseRequest.setPathInfo(pathInfo);
			}

			if (LOG.isDebugEnabled())
				LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);

			// start manual inline of nextScope(target,baseRequest,request,response);
			if (never())
				nextScope(target,baseRequest,request,response);
			else if (_nextScope != null)
				_nextScope.doScope(target,baseRequest,request,response);
			else if (_outerScope != null)
				_outerScope.doHandle(target,baseRequest,request,response);
			else
				doHandle(target,baseRequest,request,response);
			// end manual inline (pathentic attempt to reduce stack depth)
		}
		finally
		{
			if (old_context != _scontext)
			{
				// reset the classloader
				if (_classLoader != null)
				{
					current_thread.setContextClassLoader(old_classloader);
				}

				// reset the context and servlet path.
				baseRequest.setContext(old_context);
				__context.set(old_context);
				baseRequest.setContextPath(old_context_path);
				baseRequest.setServletPath(old_servlet_path);
				baseRequest.setPathInfo(old_path_info);
			}
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
	{
		final DispatcherType dispatch = baseRequest.getDispatcherType();
		final boolean new_context = baseRequest.takeNewContext();
		try
		{
			if (new_context)
			{
				// Handle the REALLY SILLY request events!
				if (_requestAttributeListeners != null)
				{
					final int s = LazyList.size(_requestAttributeListeners);
					for (int i = 0; i < s; i++)
						baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
				}

				if (_requestListeners != null)
				{
					final int s = LazyList.size(_requestListeners);
					final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
					for (int i = 0; i < s; i++)
						((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
				}
			}

			if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
				throw new HttpException(HttpServletResponse.SC_NOT_FOUND);

			// start manual inline of nextHandle(target,baseRequest,request,response);
			// noinspection ConstantIfStatement
			if (never())
				nextHandle(target,baseRequest,request,response);
			else if (_nextScope != null && _nextScope == _handler)
				_nextScope.doHandle(target,baseRequest,request,response);
			else if (_handler != null)
				_handler.handle(target,baseRequest,request,response);
			// end manual inline
		}
		catch (HttpException e)
		{
			LOG.debug("",e);
			baseRequest.setHandled(true);
			response.sendError(e.getStatus(),e.getReason());
		}
		finally
		{
			// Handle more REALLY SILLY request events!
			if (new_context)
			{
				if (_requestListeners != null)
				{
					final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
					for (int i = LazyList.size(_requestListeners); i-- > 0;)
						((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
				}

				if (_requestAttributeListeners != null)
				{
					for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
						baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
				}
			}
		}
	}

	/* ------------------------------------------------------------ */
	/*
	 * Handle a runnable in this context
	 */
	public void handle(Runnable runnable)
	{
		ClassLoader old_classloader = null;
		Thread current_thread = null;
		Context old_context = null;
		try
		{
			old_context = __context.get();
			__context.set(_scontext);

			// Set the classloader
			if (_classLoader != null)
			{
				current_thread = Thread.currentThread();
				old_classloader = current_thread.getContextClassLoader();
				current_thread.setContextClassLoader(_classLoader);
			}

			runnable.run();
		}
		finally
		{
			__context.set(old_context);
			if (old_classloader != null)
			{
				current_thread.setContextClassLoader(old_classloader);
			}
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
	 * the target is protected, 404 is returned. 
	 */
	/* ------------------------------------------------------------ */
	public boolean isProtectedTarget(String target)
	{
		if (target == null || _protectedTargets == null)
			return false;
		
		while (target.startsWith("//"))
			target=URIUtil.compactPath(target);
		
		boolean isProtected = false;
		int i=0;
		while (!isProtected && i<_protectedTargets.length)
		{
			isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
		}
		return isProtected;
	}
	
	
	public void setProtectedTargets (String[] targets)
	{
		if (targets == null)
		{
			_protectedTargets = null;
			return;
		}
		
		_protectedTargets = new String[targets.length];
		System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
	}
	
	public String[] getProtectedTargets ()
	{
		if (_protectedTargets == null)
			return null;
		
		String[] tmp = new String[_protectedTargets.length];
		System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
		return tmp;
	}
	

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
	 */
	public void removeAttribute(String name)
	{
		checkManagedAttribute(name,null);
		_attributes.removeAttribute(name);
	}

	/* ------------------------------------------------------------ */
	/*
	 * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
	 * a context. No attribute listener events are triggered by this API.
	 *
	 * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
	 */
	public void setAttribute(String name, Object value)
	{
		checkManagedAttribute(name,value);
		_attributes.setAttribute(name,value);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param attributes
	 *            The attributes to set.
	 */
	public void setAttributes(Attributes attributes)
	{
		_attributes.clearAttributes();
		_attributes.addAll(attributes);
		Enumeration e = _attributes.getAttributeNames();
		while (e.hasMoreElements())
		{
			String name = (String)e.nextElement();
			checkManagedAttribute(name,attributes.getAttribute(name));
		}
	}

	/* ------------------------------------------------------------ */
	public void clearAttributes()
	{
		Enumeration e = _attributes.getAttributeNames();
		while (e.hasMoreElements())
		{
			String name = (String)e.nextElement();
			checkManagedAttribute(name,null);
		}
		_attributes.clearAttributes();
	}

	/* ------------------------------------------------------------ */
	public void checkManagedAttribute(String name, Object value)
	{
		if (_managedAttributes != null && _managedAttributes.containsKey(name))
		{
			setManagedAttribute(name,value);
		}
	}

	/* ------------------------------------------------------------ */
	public void setManagedAttribute(String name, Object value)
	{
		Object old = _managedAttributes.put(name,value);
		getServer().getContainer().update(this,old,value,name,true);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param classLoader
	 *            The classLoader to set.
	 */
	public void setClassLoader(ClassLoader classLoader)
	{
		_classLoader = classLoader;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param contextPath
	 *            The _contextPath to set.
	 */
	public void setContextPath(String contextPath)
	{
		if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
			throw new IllegalArgumentException("ends with /");
		_contextPath = contextPath;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param servletContextName
	 *            The servletContextName to set.
	 */
	public void setDisplayName(String servletContextName)
	{
		_displayName = servletContextName;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the resourceBase.
	 */
	public Resource getBaseResource()
	{
		if (_baseResource == null)
			return null;
		return _baseResource;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the base resource as a string.
	 */
	public String getResourceBase()
	{
		if (_baseResource == null)
			return null;
		return _baseResource.toString();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param base
	 *            The resourceBase to set.
	 */
	public void setBaseResource(Resource base)
	{
		_baseResource = base;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param resourceBase
	 *            The base resource as a string.
	 */
	public void setResourceBase(String resourceBase)
	{
		try
		{
			setBaseResource(newResource(resourceBase));
		}
		catch (Exception e)
		{
			LOG.warn(e.toString());
			LOG.debug("",e);
			throw new IllegalArgumentException(resourceBase);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return True if aliases are allowed
	 */
	public boolean isAliases()
	{
		return _aliasesAllowed;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param aliases
	 *            aliases are allowed
	 */
	public void setAliases(boolean aliases)
	{
		_aliasesAllowed = aliases;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the mimeTypes.
	 */
	public MimeTypes getMimeTypes()
	{
		if (_mimeTypes == null)
			_mimeTypes = new MimeTypes();
		return _mimeTypes;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param mimeTypes
	 *            The mimeTypes to set.
	 */
	public void setMimeTypes(MimeTypes mimeTypes)
	{
		_mimeTypes = mimeTypes;
	}

	/* ------------------------------------------------------------ */
	/**
	 */
	public void setWelcomeFiles(String[] files)
	{
		_welcomeFiles = files;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return The names of the files which the server should consider to be welcome files in this context.
	 * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
	 * @see #setWelcomeFiles
	 */
	public String[] getWelcomeFiles()
	{
		return _welcomeFiles;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the errorHandler.
	 */
	public ErrorHandler getErrorHandler()
	{
		return _errorHandler;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param errorHandler
	 *            The errorHandler to set.
	 */
	public void setErrorHandler(ErrorHandler errorHandler)
	{
		if (errorHandler != null)
			errorHandler.setServer(getServer());
		if (getServer() != null)
			getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true);
		_errorHandler = errorHandler;
	}

	/* ------------------------------------------------------------ */
	public int getMaxFormContentSize()
	{
		return _maxFormContentSize;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the maximum size of a form post, to protect against DOS attacks from large forms.
	 * @param maxSize
	 */
	public void setMaxFormContentSize(int maxSize)
	{
		_maxFormContentSize = maxSize;
	}

	/* ------------------------------------------------------------ */
	public int getMaxFormKeys()
	{
		return _maxFormKeys;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
	 * @param max
	 */
	public void setMaxFormKeys(int max)
	{
		_maxFormKeys = max;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return True if URLs are compacted to replace multiple '/'s with a single '/'
	 */
	public boolean isCompactPath()
	{
		return _compactPath;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param compactPath
	 *            True if URLs are compacted to replace multiple '/'s with a single '/'
	 */
	public void setCompactPath(boolean compactPath)
	{
		_compactPath = compactPath;
	}

	/* ------------------------------------------------------------ */
	@Override
	public String toString()
	{
		String[] vhosts = getVirtualHosts();

		StringBuilder b = new StringBuilder();

		Package pkg = getClass().getPackage();
		if (pkg != null)
		{
			String p = pkg.getName();
			if (p != null && p.length() > 0)
			{
				String[] ss = p.split("\\.");
				for (String s : ss)
					b.append(s.charAt(0)).append('.');
			}
		}
		b.append(getClass().getSimpleName());
		b.append('{').append(getContextPath()).append(',').append(getBaseResource());

		if (vhosts != null && vhosts.length > 0)
			b.append(',').append(vhosts[0]);
		b.append('}');

		return b.toString();
	}

	/* ------------------------------------------------------------ */
	public void addLocaleEncoding(String locale, String encoding)
	{
		if (_localeEncodingMap == null)
			_localeEncodingMap = new HashMap<String, String>();
		_localeEncodingMap.put(locale,encoding);
	}

	/* ------------------------------------------------------------ */
	public String getLocaleEncoding(String locale)
	{
		if (_localeEncodingMap == null)
			return null;
		String encoding = _localeEncodingMap.get(locale);
		return encoding;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
	 * language is looked up.
	 *
	 * @param locale
	 *            a <code>Locale</code> value
	 * @return a <code>String</code> representing the character encoding for the locale or null if none found.
	 */
	public String getLocaleEncoding(Locale locale)
	{
		if (_localeEncodingMap == null)
			return null;
		String encoding = _localeEncodingMap.get(locale.toString());
		if (encoding == null)
			encoding = _localeEncodingMap.get(locale.getLanguage());
		return encoding;
	}

	/* ------------------------------------------------------------ */
	/*
	 */
	public Resource getResource(String path) throws MalformedURLException
	{
		if (path == null || !path.startsWith(URIUtil.SLASH))
			throw new MalformedURLException(path);

		if (_baseResource == null)
			return null;

		try
		{
			path = URIUtil.canonicalPath(path);
			Resource resource = _baseResource.addPath(path);
			
			if (checkAlias(path,resource))
				return resource;
			return null;
		}
		catch (Exception e)
		{
			LOG.trace("",e);
		}

		return null;
	}

	/* ------------------------------------------------------------ */
	public boolean checkAlias(String path, Resource resource)
	{
		// Is the resource aliased?
		if (!_aliasesAllowed && resource.getAlias() != null)
		{
			if (LOG.isDebugEnabled())
				LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());

			// alias checks
			for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
			{
				AliasCheck check = i.next();
				if (check.check(path,resource))
				{
					if (LOG.isDebugEnabled())
						LOG.debug("Aliased resource: " + resource + " approved by " + check);
					return true;
				}
			}
			return false;
		}
		return true;
	}
	
	/* ------------------------------------------------------------ */
	/**
	 * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
	 */
	public Resource newResource(URL url) throws IOException
	{
		return Resource.newResource(url);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
	 *
	 * @param urlOrPath
	 *            The URL or path to convert
	 * @return The Resource for the URL/path
	 * @throws IOException
	 *             The Resource could not be created.
	 */
	public Resource newResource(String urlOrPath) throws IOException
	{
		return Resource.newResource(urlOrPath);
	}

	/* ------------------------------------------------------------ */
	/*
	 */
	public Set<String> getResourcePaths(String path)
	{
		try
		{
			path = URIUtil.canonicalPath(path);
			Resource resource = getResource(path);

			if (resource != null && resource.exists())
			{
				if (!path.endsWith(URIUtil.SLASH))
					path = path + URIUtil.SLASH;

				String[] l = resource.list();
				if (l != null)
				{
					HashSet<String> set = new HashSet<String>();
					for (int i = 0; i < l.length; i++)
						set.add(path + l[i]);
					return set;
				}
			}
		}
		catch (Exception e)
		{
			LOG.trace("",e);
		}
		return Collections.emptySet();
	}

	/* ------------------------------------------------------------ */
	private String normalizeHostname(String host)
	{
		if (host == null)
			return null;

		if (host.endsWith("."))
			return host.substring(0,host.length() - 1);

		return host;
	}
	
	/* ------------------------------------------------------------ */
	/**
	 * Add an AliasCheck instance to possibly permit aliased resources
	 * @param check The alias checker
	 */
	public void addAliasCheck(AliasCheck check)
	{
		_aliasChecks.add(check);
	}
	
	/* ------------------------------------------------------------ */
	/**
	 * @return Mutable list of Alias checks
	 */
	public List<AliasCheck> getAliasChecks()
	{
		return _aliasChecks;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Context.
	 * <p>
	 * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
	 * </p>
	 *
	 *
	 */
	public final class Context implements ServletContext
	{
		protected int _majorVersion = 3;
		protected int _minorVersion = 0;

		/* ------------------------------------------------------------ */
		protected Context()
		{
		}

		/* ------------------------------------------------------------ */
		public ContextHandler getContextHandler()
		{
			// TODO reduce visibility of this method
			return ContextHandler.this;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getContext(java.lang.String)
		 */
		@Override
		public ServletContext getContext(String uripath)
		{
			List<ContextHandler> contexts = new ArrayList<ContextHandler>();
			Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
			String matched_path = null;

			for (Handler handler : handlers)
			{
				if (handler == null)
					continue;
				ContextHandler ch = (ContextHandler)handler;
				String context_path = ch.getContextPath();

				if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
						|| "/".equals(context_path))
				{
					// look first for vhost matching context only
					if (getVirtualHosts() != null && getVirtualHosts().length > 0)
					{
						if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
						{
							for (String h1 : getVirtualHosts())
								for (String h2 : ch.getVirtualHosts())
									if (h1.equals(h2))
									{
										if (matched_path == null || context_path.length() > matched_path.length())
										{
											contexts.clear();
											matched_path = context_path;
										}

										if (matched_path.equals(context_path))
											contexts.add(ch);
									}
						}
					}
					else
					{
						if (matched_path == null || context_path.length() > matched_path.length())
						{
							contexts.clear();
							matched_path = context_path;
						}

						if (matched_path.equals(context_path))
							contexts.add(ch);
					}
				}
			}

			if (contexts.size() > 0)
				return contexts.get(0)._scontext;

			// try again ignoring virtual hosts
			matched_path = null;
			for (Handler handler : handlers)
			{
				if (handler == null)
					continue;
				ContextHandler ch = (ContextHandler)handler;
				String context_path = ch.getContextPath();

				if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
						|| "/".equals(context_path))
				{
					if (matched_path == null || context_path.length() > matched_path.length())
					{
						contexts.clear();
						matched_path = context_path;
					}

					if (matched_path.equals(context_path))
						contexts.add(ch);
				}
			}

			if (contexts.size() > 0)
				return contexts.get(0)._scontext;
			return null;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getMajorVersion()
		 */
		@Override
		public int getMajorVersion()
		{
			return 3;
		}
	  

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
		 */
		@Override
		public String getMimeType(String file)
		{
			if (_mimeTypes == null)
				return null;
			Buffer mime = _mimeTypes.getMimeByExtension(file);
			if (mime != null)
				return mime.toString();
			return null;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getMinorVersion()
		 */
		@Override
		public int getMinorVersion()
		{
			return 0;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
		 */
		@Override
		public RequestDispatcher getNamedDispatcher(String name)
		{
			throw new UnsupportedOperationException();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
		 */
		@Override
		public RequestDispatcher getRequestDispatcher(String uriInContext)
		{
			throw new UnsupportedOperationException();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
		 */
		@Override
		public String getRealPath(String path)
		{
			if (path == null)
				return null;
			if (path.length() == 0)
				path = URIUtil.SLASH;
			else if (path.charAt(0) != '/')
				path = URIUtil.SLASH + path;

			try
			{
				Resource resource = ContextHandler.this.getResource(path);
				if (resource != null)
				{
					File file = resource.getFile();
					if (file != null)
						return file.getCanonicalPath();
				}
			}
			catch (Exception e)
			{
				LOG.trace("",e);
			}

			return null;
		}

		/* ------------------------------------------------------------ */
		@Override
		public URL getResource(String path) throws MalformedURLException
		{
			Resource resource = ContextHandler.this.getResource(path);
			if (resource != null && resource.exists())
				return resource.getURL();
			return null;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
		 */
		@Override
		public InputStream getResourceAsStream(String path)
		{
			try
			{
				URL url = getResource(path);
				if (url == null)
					return null;
				Resource r = Resource.newResource(url);
				return r.getInputStream();
			}
			catch (Exception e)
			{
				LOG.trace("",e);
				return null;
			}
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
		 */
		@Override
		public Set getResourcePaths(String path)
		{
			return ContextHandler.this.getResourcePaths(path);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getServerInfo()
		 */
		@Override
		public String getServerInfo()
		{
			return "jetty/" + Server.getVersion();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getServlet(java.lang.String)
		 */
		@Override
		@Deprecated
		public Servlet getServlet(String name) throws ServletException
		{
			throw new UnsupportedOperationException();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getServletNames()
		 */
		@SuppressWarnings("unchecked")
		@Override
		@Deprecated
		public Enumeration getServletNames()
		{
			throw new UnsupportedOperationException();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getServlets()
		 */
		@SuppressWarnings("unchecked")
		@Override
		@Deprecated
		public Enumeration getServlets()
		{
			throw new UnsupportedOperationException();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
		 */
		@Override
		public void log(Exception exception, String msg)
		{
			_logger.warn(msg,exception);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#log(java.lang.String)
		 */
		@Override
		public void log(String msg)
		{
			_logger.info(msg);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
		 */
		@Override
		public void log(String message, Throwable throwable)
		{
			_logger.warn(message,throwable);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
		 */
		@Override
		public String getInitParameter(String name)
		{
			return ContextHandler.this.getInitParameter(name);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getInitParameterNames()
		 */
		@SuppressWarnings("unchecked")
		@Override
		public Enumeration getInitParameterNames()
		{
			return ContextHandler.this.getInitParameterNames();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
		 */
		@Override
		public synchronized Object getAttribute(String name)
		{
			Object o = ContextHandler.this.getAttribute(name);
			if (o == null && _contextAttributes != null)
				o = _contextAttributes.getAttribute(name);
			return o;
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getAttributeNames()
		 */
		@SuppressWarnings("unchecked")
		@Override
		public synchronized Enumeration getAttributeNames()
		{
			HashSet<String> set = new HashSet<String>();
			if (_contextAttributes != null)
			{
				Enumeration<String> e = _contextAttributes.getAttributeNames();
				while (e.hasMoreElements())
					set.add(e.nextElement());
			}
			Enumeration<String> e = _attributes.getAttributeNames();
			while (e.hasMoreElements())
				set.add(e.nextElement());

			return Collections.enumeration(set);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
		 */
		@Override
		public synchronized void setAttribute(String name, Object value)
		{
			checkManagedAttribute(name,value);
			Object old_value = _contextAttributes.getAttribute(name);

			if (value == null)
				_contextAttributes.removeAttribute(name);
			else
				_contextAttributes.setAttribute(name,value);

			if (_contextAttributeListeners != null)
			{
				ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);

				for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
				{
					ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);

					if (old_value == null)
						l.attributeAdded(event);
					else if (value == null)
						l.attributeRemoved(event);
					else
						l.attributeReplaced(event);
				}
			}
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
		 */
		@Override
		public synchronized void removeAttribute(String name)
		{
			checkManagedAttribute(name,null);

			if (_contextAttributes == null)
			{
				// Set it on the handler
				_attributes.removeAttribute(name);
				return;
			}

			Object old_value = _contextAttributes.getAttribute(name);
			_contextAttributes.removeAttribute(name);
			if (old_value != null)
			{
				if (_contextAttributeListeners != null)
				{
					ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);

					for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
						((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
				}
			}
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletContext#getServletContextName()
		 */
		@Override
		public String getServletContextName()
		{
			String name = ContextHandler.this.getDisplayName();
			if (name == null)
				name = ContextHandler.this.getContextPath();
			return name;
		}

		/* ------------------------------------------------------------ */
		@Override
		public String getContextPath()
		{
			if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
				return "";

			return _contextPath;
		}

		/* ------------------------------------------------------------ */
		@Override
		public String toString()
		{
			return "ServletContext@" + ContextHandler.this.toString();
		}

		/* ------------------------------------------------------------ */
		@Override
		public boolean setInitParameter(String name, String value)
		{
			if (ContextHandler.this.getInitParameter(name) != null)
				return false;
			ContextHandler.this.getInitParams().put(name,value);
			return true;
		}

		/* ------------------------------------------------------------ */
		final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";

		@Override
		public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Dynamic addFilter(String filterName, Filter filter)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Dynamic addFilter(String filterName, String className)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends Filter> T createFilter(Class<T> c) throws ServletException
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public FilterRegistration getFilterRegistration(String filterName)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Map<String, ? extends FilterRegistration> getFilterRegistrations()
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public ServletRegistration getServletRegistration(String servletName)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public Map<String, ? extends ServletRegistration> getServletRegistrations()
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public SessionCookieConfig getSessionCookieConfig()
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public void addListener(String className)
		{
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends EventListener> void addListener(T t)
		{            
			throw new UnsupportedOperationException();
		}

		@Override
		public void addListener(Class<? extends EventListener> listenerClass)
		{            
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
		{
			try
			{
				return clazz.newInstance();
			}
			catch (InstantiationException e)
			{
				throw new ServletException(e);
			}
			catch (IllegalAccessException e)
			{
				throw new ServletException(e);
			}
		}

		@Override
		public ClassLoader getClassLoader()
		{
			AccessController.checkPermission(new RuntimePermission("getClassLoader"));
			return _classLoader;
		}

		@Override
		public int getEffectiveMajorVersion()
		{
			return _majorVersion;
		}

		@Override
		public int getEffectiveMinorVersion()
		{
			return _minorVersion;
		}

		public void setEffectiveMajorVersion (int v)
		{
			_majorVersion = v;
		}
		
		public void setEffectiveMinorVersion (int v)
		{
			_minorVersion = v;
		}
		
		@Override
		public JspConfigDescriptor getJspConfigDescriptor()
		{
			throw new UnsupportedOperationException();
		}

		public void setJspConfigDescriptor(JspConfigDescriptor d)
		{
			throw new UnsupportedOperationException();
		}
		
		@Override
		public void declareRoles(String... roleNames)
		{
			throw new UnsupportedOperationException();
		}
	}

	private static class CLDump implements Dumpable
	{
		final ClassLoader _loader;

		CLDump(ClassLoader loader)
		{
			_loader = loader;
		}

		public String dump()
		{
			return AggregateLifeCycle.dump(this);
		}

		public void dump(Appendable out, String indent) throws IOException
		{
			out.append(String.valueOf(_loader)).append("\n");

			if (_loader != null)
			{
				Object parent = _loader.getParent();
				if (parent != null)
				{
					if (!(parent instanceof Dumpable))
						parent = new CLDump((ClassLoader)parent);

					if (_loader instanceof URLClassLoader)
						AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
					else
						AggregateLifeCycle.dump(out,indent,Collections.singleton(parent));
				}
			}
		}
	}
	
	
	/* ------------------------------------------------------------ */
	/** Interface to check aliases
	 */
	public interface AliasCheck
	{
		/* ------------------------------------------------------------ */
		/** Check an alias
		 * @param path The path the aliased resource was created for
		 * @param resource The aliased resourced
		 * @return True if the resource is OK to be served.
		 */
		boolean check(String path, Resource resource);
	}
	
	
	/* ------------------------------------------------------------ */
	/** Approve Aliases with same suffix.
	 * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
	 * approved because both the resource and alias end with ".html".
	 */
	@Deprecated
	public static class ApproveSameSuffixAliases implements AliasCheck
	{
		{
			LOG.warn("ApproveSameSuffixAlias is not safe for production");
		}
		
		public boolean check(String path, Resource resource)
		{
			int dot = path.lastIndexOf('.');
			if (dot<0)
				return false;
			String suffix=path.substring(dot);
			return resource.toString().endsWith(suffix);
		}
	}
	
	
	/* ------------------------------------------------------------ */
	/** Approve Aliases with a path prefix.
	 * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
	 * approved because both the resource and alias end with "/foobar.html".
	 */
	@Deprecated
	public static class ApprovePathPrefixAliases implements AliasCheck
	{
		{
			LOG.warn("ApprovePathPrefixAliases is not safe for production");
		}
		
		public boolean check(String path, Resource resource)
		{
			int slash = path.lastIndexOf('/');
			if (slash<0 || slash==path.length()-1)
				return false;
			String suffix=path.substring(slash);
			return resource.toString().endsWith(suffix);
		}
	}
	
	/* ------------------------------------------------------------ */
	/** Approve Aliases of a non existent directory.
	 * If a directory "/foobar/" does not exist, then the resource is 
	 * aliased to "/foobar".  Accept such aliases.
	 */
	public static class ApproveNonExistentDirectoryAliases implements AliasCheck
	{
		public boolean check(String path, Resource resource)
		{
			if (resource.exists())
				return false;
			
			String a=resource.getAlias().toString();
			String r=resource.getURL().toString();
			
			if (a.length()>r.length())
				return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
			else
				return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
		}
	}
	
}