View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.security.AccessController;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.EventListener;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.concurrent.Future;
40  
41  import javax.servlet.DispatcherType;
42  import javax.servlet.Filter;
43  import javax.servlet.FilterRegistration;
44  import javax.servlet.FilterRegistration.Dynamic;
45  import javax.servlet.RequestDispatcher;
46  import javax.servlet.Servlet;
47  import javax.servlet.ServletContext;
48  import javax.servlet.ServletContextAttributeEvent;
49  import javax.servlet.ServletContextAttributeListener;
50  import javax.servlet.ServletContextEvent;
51  import javax.servlet.ServletContextListener;
52  import javax.servlet.ServletException;
53  import javax.servlet.ServletRegistration;
54  import javax.servlet.ServletRequestAttributeListener;
55  import javax.servlet.ServletRequestEvent;
56  import javax.servlet.ServletRequestListener;
57  import javax.servlet.SessionCookieConfig;
58  import javax.servlet.SessionTrackingMode;
59  import javax.servlet.descriptor.JspConfigDescriptor;
60  import javax.servlet.http.HttpServletRequest;
61  import javax.servlet.http.HttpServletResponse;
62  
63  import org.eclipse.jetty.http.MimeTypes;
64  import org.eclipse.jetty.server.ClassLoaderDump;
65  import org.eclipse.jetty.server.Dispatcher;
66  import org.eclipse.jetty.server.Handler;
67  import org.eclipse.jetty.server.HandlerContainer;
68  import org.eclipse.jetty.server.Request;
69  import org.eclipse.jetty.server.Server;
70  import org.eclipse.jetty.util.ArrayUtil;
71  import org.eclipse.jetty.util.Attributes;
72  import org.eclipse.jetty.util.AttributesMap;
73  import org.eclipse.jetty.util.FutureCallback;
74  import org.eclipse.jetty.util.LazyList;
75  import org.eclipse.jetty.util.Loader;
76  import org.eclipse.jetty.util.StringUtil;
77  import org.eclipse.jetty.util.URIUtil;
78  import org.eclipse.jetty.util.annotation.ManagedAttribute;
79  import org.eclipse.jetty.util.annotation.ManagedObject;
80  import org.eclipse.jetty.util.component.Graceful;
81  import org.eclipse.jetty.util.log.Log;
82  import org.eclipse.jetty.util.log.Logger;
83  import org.eclipse.jetty.util.resource.Resource;
84  
85  /* ------------------------------------------------------------ */
86  /**
87   * ContextHandler.
88   *
89   * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
90   *
91   * <p>
92   * 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
93   * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
94   * <p>
95   * 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
96   * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
97   *
98   * @org.apache.xbean.XBean description="Creates a basic HTTP context"
99   */
100 @ManagedObject("URI Context")
101 public class ContextHandler extends ScopedHandler implements Attributes, Graceful
102 {
103 
104     private static final Logger LOG = Log.getLogger(ContextHandler.class);
105 
106     private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
107 
108     /**
109      * 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
110      * 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
111      * for the attribute value.
112      */
113     public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
114 
115     /* ------------------------------------------------------------ */
116     /**
117      * Get the current ServletContext implementation.
118      *
119      * @return ServletContext implementation
120      */
121     public static Context getCurrentContext()
122     {
123         return __context.get();
124     }
125 
126     protected Context _scontext;
127 
128     private final AttributesMap _attributes;
129     private final AttributesMap _contextAttributes;
130 
131     private final Map<String, String> _initParams;
132 
133     private ClassLoader _classLoader;
134     private String _contextPath = "/";
135 
136     private String _displayName;
137 
138     private Resource _baseResource;
139     private MimeTypes _mimeTypes;
140     private Map<String, String> _localeEncodingMap;
141 
142     private String[] _welcomeFiles;
143 
144     private ErrorHandler _errorHandler;
145 
146     private String[] _vhosts;
147 
148     private EventListener[] _eventListeners;
149     private Logger _logger;
150 
151     private boolean _allowNullPathInfo;
152 
153     private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
154 
155     private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue();
156 
157     private boolean _compactPath = false;
158 
159     private boolean _aliases = false;
160 
161     private Object _contextListeners;
162     private Object _contextAttributeListeners;
163     private Object _requestListeners;
164     private Object _requestAttributeListeners;
165     private Map<String, Object> _managedAttributes;
166     private String[] _protectedTargets;
167 
168     public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
169     private volatile Availability _availability;
170 
171     /* ------------------------------------------------------------ */
172     /**
173      *
174      */
175     public ContextHandler()
176     {
177         super();
178         _scontext = new Context();
179         _attributes = new AttributesMap();
180         _contextAttributes = new AttributesMap();
181         _initParams = new HashMap<String, String>();
182     }
183 
184     /* ------------------------------------------------------------ */
185     /**
186      *
187      */
188     protected ContextHandler(Context context)
189     {
190         super();
191         _scontext = context;
192         _attributes = new AttributesMap();
193         _contextAttributes = new AttributesMap();
194         _initParams = new HashMap<String, String>();
195     }
196 
197     /* ------------------------------------------------------------ */
198     /**
199      *
200      */
201     public ContextHandler(String contextPath)
202     {
203         this();
204         setContextPath(contextPath);
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      *
210      */
211     public ContextHandler(HandlerContainer parent, String contextPath)
212     {
213         this();
214         setContextPath(contextPath);
215         if (parent instanceof HandlerWrapper)
216             ((HandlerWrapper)parent).setHandler(this);
217         else if (parent instanceof HandlerCollection)
218             ((HandlerCollection)parent).addHandler(this);
219     }
220 
221     /* ------------------------------------------------------------ */
222     @Override
223     public void dump(Appendable out, String indent) throws IOException
224     {
225         dumpBeans(out,indent,
226             Collections.singletonList(new ClassLoaderDump(getClassLoader())),
227             _initParams.entrySet(),
228             _attributes.getAttributeEntrySet(),
229             _contextAttributes.getAttributeEntrySet());
230     }
231 
232     /* ------------------------------------------------------------ */
233     public Context getServletContext()
234     {
235         return _scontext;
236     }
237 
238     /* ------------------------------------------------------------ */
239     /**
240      * @return the allowNullPathInfo true if /context is not redirected to /context/
241      */
242     @ManagedAttribute("Checks if the /context is not redirected to /context/")
243     public boolean getAllowNullPathInfo()
244     {
245         return _allowNullPathInfo;
246     }
247 
248     /* ------------------------------------------------------------ */
249     /**
250      * @param allowNullPathInfo
251      *            true if /context is not redirected to /context/
252      */
253     public void setAllowNullPathInfo(boolean allowNullPathInfo)
254     {
255         _allowNullPathInfo = allowNullPathInfo;
256     }
257 
258     /* ------------------------------------------------------------ */
259     @Override
260     public void setServer(Server server)
261     {
262         super.setServer(server);
263         if (_errorHandler != null)
264             _errorHandler.setServer(server);
265     }
266 
267     /* ------------------------------------------------------------ */
268     /**
269      * 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
270      * 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
271      * matching virtual host name.
272      *
273      * @param vhosts
274      *            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
275      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
276      */
277     public void setVirtualHosts(String[] vhosts)
278     {
279         if (vhosts == null)
280         {
281             _vhosts = vhosts;
282         }
283         else
284         {
285             _vhosts = new String[vhosts.length];
286             for (int i = 0; i < vhosts.length; i++)
287                 _vhosts[i] = normalizeHostname(vhosts[i]);
288         }
289     }
290 
291     /* ------------------------------------------------------------ */
292     /** Either set virtual hosts or add to an existing set of virtual hosts.
293      *
294      * @param virtualHosts
295      *            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
296      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
297      */
298     public void addVirtualHosts(String[] virtualHosts)
299     {
300         if (virtualHosts == null)  // since this is add, we don't null the old ones
301         {
302             return;
303         }
304         else
305         {
306             List<String> currentVirtualHosts = null;
307             if (_vhosts != null)
308             {
309                 currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
310             }
311             else
312             {
313                 currentVirtualHosts = new ArrayList<String>();
314             }
315 
316             for (int i = 0; i < virtualHosts.length; i++)
317             {
318                 String normVhost = normalizeHostname(virtualHosts[i]);
319                 if (!currentVirtualHosts.contains(normVhost))
320                 {
321                     currentVirtualHosts.add(normVhost);
322                 }
323             }
324             _vhosts = currentVirtualHosts.toArray(new String[0]);
325         }
326     }
327 
328     /* ------------------------------------------------------------ */
329     /**
330      * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
331      *
332      *  @param virtualHosts
333      *            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
334      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
335      */
336     public void removeVirtualHosts(String[] virtualHosts)
337     {
338         if (virtualHosts == null)
339         {
340             return; // do nothing
341         }
342         else if ( _vhosts == null || _vhosts.length == 0)
343         {
344             return; // do nothing
345         }
346         else
347         {
348             List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
349 
350             for (int i = 0; i < virtualHosts.length; i++)
351             {
352                 String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
353                 if (existingVirtualHosts.contains(toRemoveVirtualHost))
354                 {
355                     existingVirtualHosts.remove(toRemoveVirtualHost);
356                 }
357             }
358 
359             if (existingVirtualHosts.isEmpty())
360             {
361                 _vhosts = null; // if we ended up removing them all, just null out _vhosts
362             }
363             else
364             {
365                 _vhosts = existingVirtualHosts.toArray(new String[0]);
366             }
367         }
368     }
369 
370     /* ------------------------------------------------------------ */
371     /**
372      * 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
373      * 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
374      * matching virtual host name.
375      *
376      * @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
377      *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
378      */
379     @ManagedAttribute("Virtual hosts accepted by the context")
380     public String[] getVirtualHosts()
381     {
382         return _vhosts;
383     }
384 
385     /* ------------------------------------------------------------ */
386     /*
387      * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
388      */
389     @Override
390     public Object getAttribute(String name)
391     {
392         return _attributes.getAttribute(name);
393     }
394 
395     /* ------------------------------------------------------------ */
396     /*
397      * @see javax.servlet.ServletContext#getAttributeNames()
398      */
399     @SuppressWarnings("unchecked")
400     @Override
401     public Enumeration getAttributeNames()
402     {
403         return AttributesMap.getAttributeNamesCopy(_attributes);
404     }
405 
406     /* ------------------------------------------------------------ */
407     /**
408      * @return Returns the attributes.
409      */
410     public Attributes getAttributes()
411     {
412         return _attributes;
413     }
414 
415     /* ------------------------------------------------------------ */
416     /**
417      * @return Returns the classLoader.
418      */
419     public ClassLoader getClassLoader()
420     {
421         return _classLoader;
422     }
423 
424     /* ------------------------------------------------------------ */
425     /**
426      * Make best effort to extract a file classpath from the context classloader
427      *
428      * @return Returns the classLoader.
429      */
430     @ManagedAttribute("The file classpath")
431     public String getClassPath()
432     {
433         if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
434             return null;
435         URLClassLoader loader = (URLClassLoader)_classLoader;
436         URL[] urls = loader.getURLs();
437         StringBuilder classpath = new StringBuilder();
438         for (int i = 0; i < urls.length; i++)
439         {
440             try
441             {
442                 Resource resource = newResource(urls[i]);
443                 File file = resource.getFile();
444                 if (file != null && file.exists())
445                 {
446                     if (classpath.length() > 0)
447                         classpath.append(File.pathSeparatorChar);
448                     classpath.append(file.getAbsolutePath());
449                 }
450             }
451             catch (IOException e)
452             {
453                 LOG.debug(e);
454             }
455         }
456         if (classpath.length() == 0)
457             return null;
458         return classpath.toString();
459     }
460 
461     /* ------------------------------------------------------------ */
462     /**
463      * @return Returns the _contextPath.
464      */
465     @ManagedAttribute("True if URLs are compacted to replace the multiple '/'s with a single '/'")
466     public String getContextPath()
467     {
468         return _contextPath;
469     }
470 
471     /* ------------------------------------------------------------ */
472     /*
473      * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
474      */
475     public String getInitParameter(String name)
476     {
477         return _initParams.get(name);
478     }
479 
480     /* ------------------------------------------------------------ */
481     /*
482      */
483     public String setInitParameter(String name, String value)
484     {
485         return _initParams.put(name,value);
486     }
487 
488     /* ------------------------------------------------------------ */
489     /*
490      * @see javax.servlet.ServletContext#getInitParameterNames()
491      */
492     @SuppressWarnings("rawtypes")
493     public Enumeration getInitParameterNames()
494     {
495         return Collections.enumeration(_initParams.keySet());
496     }
497 
498     /* ------------------------------------------------------------ */
499     /**
500      * @return Returns the initParams.
501      */
502     @ManagedAttribute("Initial Parameter map for the context")
503     public Map<String, String> getInitParams()
504     {
505         return _initParams;
506     }
507 
508     /* ------------------------------------------------------------ */
509     /*
510      * @see javax.servlet.ServletContext#getServletContextName()
511      */
512     @ManagedAttribute(value="Display name of the Context", readonly=true)
513     public String getDisplayName()
514     {
515         return _displayName;
516     }
517 
518     /* ------------------------------------------------------------ */
519     public EventListener[] getEventListeners()
520     {
521         return _eventListeners;
522     }
523 
524     /* ------------------------------------------------------------ */
525     /**
526      * Set the context event listeners.
527      *
528      * @param eventListeners
529      *            the event listeners
530      * @see ServletContextListener
531      * @see ServletContextAttributeListener
532      * @see ServletRequestListener
533      * @see ServletRequestAttributeListener
534      */
535     public void setEventListeners(EventListener[] eventListeners)
536     {
537         _contextListeners = null;
538         _contextAttributeListeners = null;
539         _requestListeners = null;
540         _requestAttributeListeners = null;
541 
542         _eventListeners = eventListeners;
543 
544         for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
545         {
546             EventListener listener = _eventListeners[i];
547 
548             if (listener instanceof ServletContextListener)
549                 _contextListeners = LazyList.add(_contextListeners,listener);
550 
551             if (listener instanceof ServletContextAttributeListener)
552                 _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);
553 
554             if (listener instanceof ServletRequestListener)
555                 _requestListeners = LazyList.add(_requestListeners,listener);
556 
557             if (listener instanceof ServletRequestAttributeListener)
558                 _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
559         }
560     }
561 
562     /* ------------------------------------------------------------ */
563     /**
564      * Add a context event listeners.
565      *
566      * @see ServletContextListener
567      * @see ServletContextAttributeListener
568      * @see ServletRequestListener
569      * @see ServletRequestAttributeListener
570      */
571     public void addEventListener(EventListener listener)
572     {
573         setEventListeners((EventListener[])ArrayUtil.addToArray(getEventListeners(),listener,EventListener.class));
574     }
575 
576 
577     /**
578      * Apply any necessary restrictions on a programmatically added
579      * listener.
580      *
581      * Superclasses should implement.
582      *
583      * @param listener
584      */
585     public void restrictEventListener (EventListener listener)
586     {
587     }
588 
589     /* ------------------------------------------------------------ */
590     /**
591      * @return true if this context is accepting new requests
592      */
593     @ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
594     public boolean isShutdown()
595     {
596         switch(_availability)
597         {
598             case SHUTDOWN:
599                 return true;
600             default:
601                 return false;
602         }
603     }
604 
605     /* ------------------------------------------------------------ */
606     /**
607      * 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
608      * requests can complete, but no new requests are accepted.
609      *
610      */
611     @Override
612     public <C> Future<C> shutdown(final C c)
613     {
614         _availability = isRunning() ? Availability.SHUTDOWN : Availability.UNAVAILABLE;
615 
616         return new FutureCallback<>(c);
617     }
618 
619     /* ------------------------------------------------------------ */
620     /**
621      * @return false if this context is unavailable (sends 503)
622      */
623     public boolean isAvailable()
624     {
625         return _availability==Availability.AVAILABLE;
626     }
627 
628     /* ------------------------------------------------------------ */
629     /**
630      * Set Available status.
631      */
632     public void setAvailable(boolean available)
633     {
634         synchronized (this)
635         {
636             if (available && isRunning())
637                 _availability = Availability.AVAILABLE;
638             else if (!available || !isRunning())
639                 _availability = Availability.UNAVAILABLE;
640         }
641     }
642 
643     /* ------------------------------------------------------------ */
644     public Logger getLogger()
645     {
646         return _logger;
647     }
648 
649     /* ------------------------------------------------------------ */
650     public void setLogger(Logger logger)
651     {
652         _logger = logger;
653     }
654 
655     /* ------------------------------------------------------------ */
656     /*
657      * @see org.eclipse.thread.AbstractLifeCycle#doStart()
658      */
659     @Override
660     protected void doStart() throws Exception
661     {
662         _availability = Availability.STARTING;
663 
664         if (_contextPath == null)
665             throw new IllegalStateException("Null contextPath");
666 
667         _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
668         ClassLoader old_classloader = null;
669         Thread current_thread = null;
670         Context old_context = null;
671 
672         try
673         {
674             // Set the classloader
675             if (_classLoader != null)
676             {
677                 current_thread = Thread.currentThread();
678                 old_classloader = current_thread.getContextClassLoader();
679                 current_thread.setContextClassLoader(_classLoader);
680             }
681 
682             if (_mimeTypes == null)
683                 _mimeTypes = new MimeTypes();
684 
685             old_context = __context.get();
686             __context.set(_scontext);
687 
688             // defers the calling of super.doStart()
689             startContext();
690 
691             _availability = Availability.AVAILABLE;
692             LOG.info("started {}",this);
693         }
694         finally
695         {
696             __context.set(old_context);
697 
698             // reset the classloader
699             if (_classLoader != null)
700             {
701                 current_thread.setContextClassLoader(old_classloader);
702             }
703 
704         }
705     }
706 
707     /* ------------------------------------------------------------ */
708     /**
709      * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
710      * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
711      *
712      * @see org.eclipse.jetty.server.handler.ContextHandler.Context
713      */
714     protected void startContext() throws Exception
715     {
716         String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
717         if (managedAttributes != null)
718         {
719             _managedAttributes = new HashMap<String, Object>();
720             String[] attributes = managedAttributes.split(",");
721             for (String attribute : attributes)
722                 _managedAttributes.put(attribute,null);
723 
724             Enumeration e = _scontext.getAttributeNames();
725             while (e.hasMoreElements())
726             {
727                 String name = (String)e.nextElement();
728                 Object value = _scontext.getAttribute(name);
729                 checkManagedAttribute(name,value);
730             }
731         }
732 
733         super.doStart();
734 
735         // Context listeners
736         if (_contextListeners != null)
737         {
738             ServletContextEvent event = new ServletContextEvent(_scontext);
739             for (int i = 0; i < LazyList.size(_contextListeners); i++)
740             {
741                 callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event);
742             }
743         }
744     }
745 
746     /* ------------------------------------------------------------ */
747     public void callContextInitialized (ServletContextListener l, ServletContextEvent e)
748     {
749         l.contextInitialized(e);
750     }
751 
752     /* ------------------------------------------------------------ */
753     public void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
754     {
755         l.contextDestroyed(e);
756     }
757 
758     /* ------------------------------------------------------------ */
759     /*
760      * @see org.eclipse.thread.AbstractLifeCycle#doStop()
761      */
762     @Override
763     protected void doStop() throws Exception
764     {
765         _availability = Availability.UNAVAILABLE;
766 
767         ClassLoader old_classloader = null;
768         Thread current_thread = null;
769 
770         Context old_context = __context.get();
771         __context.set(_scontext);
772         try
773         {
774             // Set the classloader
775             if (_classLoader != null)
776             {
777                 current_thread = Thread.currentThread();
778                 old_classloader = current_thread.getContextClassLoader();
779                 current_thread.setContextClassLoader(_classLoader);
780             }
781 
782             super.doStop();
783 
784             // Context listeners
785             if (_contextListeners != null)
786             {
787                 ServletContextEvent event = new ServletContextEvent(_scontext);
788                 for (int i = LazyList.size(_contextListeners); i-- > 0;)
789                 {
790                     ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
791                 }
792             }
793 
794             if (_errorHandler != null)
795                 _errorHandler.stop();
796 
797             Enumeration e = _scontext.getAttributeNames();
798             while (e.hasMoreElements())
799             {
800                 String name = (String)e.nextElement();
801                 checkManagedAttribute(name,null);
802             }
803         }
804         finally
805         {
806             LOG.info("stopped {}",this);
807             __context.set(old_context);
808             // reset the classloader
809             if (_classLoader != null)
810                 current_thread.setContextClassLoader(old_classloader);
811         }
812 
813         _contextAttributes.clearAttributes();
814     }
815 
816     /* ------------------------------------------------------------ */
817     /*
818      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
819      */
820     public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
821     {
822         DispatcherType dispatch = baseRequest.getDispatcherType();
823 
824         switch (_availability)
825         {
826             case SHUTDOWN:
827             case UNAVAILABLE:
828                 baseRequest.setHandled(true);
829                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
830                 return false;
831             default:
832                 if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
833                     return false;
834         }
835 
836         // Check the vhosts
837         if (_vhosts != null && _vhosts.length > 0)
838         {
839             String vhost = normalizeHostname(baseRequest.getServerName());
840 
841             boolean match = false;
842 
843             // TODO non-linear lookup
844             for (int i = 0; !match && i < _vhosts.length; i++)
845             {
846                 String contextVhost = _vhosts[i];
847                 if (contextVhost == null)
848                     continue;
849                 if (contextVhost.startsWith("*."))
850                 {
851                     // wildcard only at the beginning, and only for one additional subdomain level
852                     match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
853                 }
854                 else
855                     match = contextVhost.equalsIgnoreCase(vhost);
856             }
857             if (!match)
858                 return false;
859         }
860 
861         // Are we not the root context?
862         if (_contextPath.length() > 1)
863         {
864             // reject requests that are not for us
865             if (!target.startsWith(_contextPath))
866                 return false;
867             if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
868                 return false;
869 
870             // redirect null path infos
871             if (!_allowNullPathInfo && _contextPath.length() == target.length())
872             {
873                 // context request must end with /
874                 baseRequest.setHandled(true);
875                 if (baseRequest.getQueryString() != null)
876                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
877                 else
878                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
879                 return false;
880             }
881         }
882 
883         return true;
884     }
885 
886     /* ------------------------------------------------------------ */
887     /**
888      * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
889      *      javax.servlet.http.HttpServletResponse)
890      */
891     @Override
892     public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
893     {
894         if (LOG.isDebugEnabled())
895             LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
896 
897         Context old_context = null;
898         String old_context_path = null;
899         String old_servlet_path = null;
900         String old_path_info = null;
901         ClassLoader old_classloader = null;
902         Thread current_thread = null;
903         String pathInfo = target;
904 
905         DispatcherType dispatch = baseRequest.getDispatcherType();
906 
907         old_context = baseRequest.getContext();
908 
909         // Are we already in this context?
910         if (old_context != _scontext)
911         {
912             // check the target.
913             if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
914             {
915                 if (_compactPath)
916                     target = URIUtil.compactPath(target);
917                 if (!checkContext(target,baseRequest,response))
918                     return;
919 
920                 if (target.length() > _contextPath.length())
921                 {
922                     if (_contextPath.length() > 1)
923                         target = target.substring(_contextPath.length());
924                     pathInfo = target;
925                 }
926                 else if (_contextPath.length() == 1)
927                 {
928                     target = URIUtil.SLASH;
929                     pathInfo = URIUtil.SLASH;
930                 }
931                 else
932                 {
933                     target = URIUtil.SLASH;
934                     pathInfo = null;
935                 }
936             }
937 
938             // Set the classloader
939             if (_classLoader != null)
940             {
941                 current_thread = Thread.currentThread();
942                 old_classloader = current_thread.getContextClassLoader();
943                 current_thread.setContextClassLoader(_classLoader);
944             }
945         }
946 
947         try
948         {
949             old_context_path = baseRequest.getContextPath();
950             old_servlet_path = baseRequest.getServletPath();
951             old_path_info = baseRequest.getPathInfo();
952 
953             // Update the paths
954             baseRequest.setContext(_scontext);
955             __context.set(_scontext);
956             if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
957             {
958                 if (_contextPath.length() == 1)
959                     baseRequest.setContextPath("");
960                 else
961                     baseRequest.setContextPath(_contextPath);
962                 baseRequest.setServletPath(null);
963                 baseRequest.setPathInfo(pathInfo);
964             }
965 
966             if (LOG.isDebugEnabled())
967                 LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
968 
969             // start manual inline of nextScope(target,baseRequest,request,response);
970             if (never())
971                 nextScope(target,baseRequest,request,response);
972             else if (_nextScope != null)
973                 _nextScope.doScope(target,baseRequest,request,response);
974             else if (_outerScope != null)
975                 _outerScope.doHandle(target,baseRequest,request,response);
976             else
977                 doHandle(target,baseRequest,request,response);
978             // end manual inline (pathentic attempt to reduce stack depth)
979         }
980         finally
981         {
982             if (old_context != _scontext)
983             {
984                 // reset the classloader
985                 if (_classLoader != null)
986                 {
987                     current_thread.setContextClassLoader(old_classloader);
988                 }
989 
990                 // reset the context and servlet path.
991                 baseRequest.setContext(old_context);
992                 __context.set(old_context);
993                 baseRequest.setContextPath(old_context_path);
994                 baseRequest.setServletPath(old_servlet_path);
995                 baseRequest.setPathInfo(old_path_info);
996             }
997         }
998     }
999 
1000     /* ------------------------------------------------------------ */
1001     /**
1002      * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
1003      *      javax.servlet.http.HttpServletResponse)
1004      */
1005     @Override
1006     public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
1007     {
1008         final DispatcherType dispatch = baseRequest.getDispatcherType();
1009         final boolean new_context = baseRequest.takeNewContext();
1010         try
1011         {
1012             if (new_context)
1013             {
1014                 // Handle the REALLY SILLY request events!
1015                 if (_requestAttributeListeners != null)
1016                 {
1017                     // TODO COW lists ???
1018                     final int s = LazyList.size(_requestAttributeListeners);
1019                     for (int i = 0; i < s; i++)
1020                         baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1021                 }
1022 
1023                 if (_requestListeners != null)
1024                 {
1025                     final int s = LazyList.size(_requestListeners);
1026                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1027                     for (int i = 0; i < s; i++)
1028                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
1029                 }
1030             }
1031 
1032             if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
1033             {
1034                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
1035                 baseRequest.setHandled(true);
1036                 return;
1037             }
1038 
1039             // start manual inline of nextHandle(target,baseRequest,request,response);
1040             // noinspection ConstantIfStatement
1041             if (never())
1042                 nextHandle(target,baseRequest,request,response);
1043             else if (_nextScope != null && _nextScope == _handler)
1044                 _nextScope.doHandle(target,baseRequest,request,response);
1045             else if (_handler != null)
1046                 _handler.handle(target,baseRequest,request,response);
1047             // end manual inline
1048         }
1049         finally
1050         {
1051             // Handle more REALLY SILLY request events!
1052             if (new_context)
1053             {
1054                 if (_requestListeners != null)
1055                 {
1056                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1057                     for (int i = LazyList.size(_requestListeners); i-- > 0;)
1058                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
1059                 }
1060 
1061                 if (_requestAttributeListeners != null)
1062                 {
1063                     for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
1064                         baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1065                 }
1066             }
1067         }
1068     }
1069 
1070     /* ------------------------------------------------------------ */
1071     /*
1072      * Handle a runnable in this context
1073      */
1074     public void handle(Runnable runnable)
1075     {
1076         ClassLoader old_classloader = null;
1077         Thread current_thread = null;
1078         Context old_context = null;
1079         try
1080         {
1081             old_context = __context.get();
1082             __context.set(_scontext);
1083 
1084             // Set the classloader
1085             if (_classLoader != null)
1086             {
1087                 current_thread = Thread.currentThread();
1088                 old_classloader = current_thread.getContextClassLoader();
1089                 current_thread.setContextClassLoader(_classLoader);
1090             }
1091 
1092             runnable.run();
1093         }
1094         finally
1095         {
1096             __context.set(old_context);
1097             if (old_classloader != null)
1098             {
1099                 current_thread.setContextClassLoader(old_classloader);
1100             }
1101         }
1102     }
1103 
1104     /* ------------------------------------------------------------ */
1105     /**
1106      * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
1107      * the target is protected, 404 is returned.
1108      */
1109     /* ------------------------------------------------------------ */
1110     public boolean isProtectedTarget(String target)
1111     {
1112         if (target == null || _protectedTargets == null)
1113             return false;
1114 
1115         while (target.startsWith("//"))
1116             target=URIUtil.compactPath(target);
1117 
1118         boolean isProtected = false;
1119         int i=0;
1120         while (!isProtected && i<_protectedTargets.length)
1121         {
1122             isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
1123         }
1124         return isProtected;
1125     }
1126 
1127 
1128     public void setProtectedTargets (String[] targets)
1129     {
1130         if (targets == null)
1131         {
1132             _protectedTargets = null;
1133             return;
1134         }
1135 
1136         _protectedTargets = new String[targets.length];
1137         System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
1138     }
1139 
1140     public String[] getProtectedTargets ()
1141     {
1142         if (_protectedTargets == null)
1143             return null;
1144 
1145         String[] tmp = new String[_protectedTargets.length];
1146         System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
1147         return tmp;
1148     }
1149 
1150 
1151     /* ------------------------------------------------------------ */
1152     /*
1153      * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
1154      */
1155     @Override
1156     public void removeAttribute(String name)
1157     {
1158         checkManagedAttribute(name,null);
1159         _attributes.removeAttribute(name);
1160     }
1161 
1162     /* ------------------------------------------------------------ */
1163     /*
1164      * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
1165      * a context. No attribute listener events are triggered by this API.
1166      *
1167      * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
1168      */
1169     @Override
1170     public void setAttribute( String name, Object value)
1171     {
1172         checkManagedAttribute(name,value);
1173         _attributes.setAttribute(name,value);
1174     }
1175 
1176     /* ------------------------------------------------------------ */
1177     /**
1178      * @param attributes
1179      *            The attributes to set.
1180      */
1181     public void setAttributes(Attributes attributes)
1182     {
1183         _attributes.clearAttributes();
1184         _attributes.addAll(attributes);
1185         Enumeration e = _attributes.getAttributeNames();
1186         while (e.hasMoreElements())
1187         {
1188             String name = (String)e.nextElement();
1189             checkManagedAttribute(name,attributes.getAttribute(name));
1190         }
1191     }
1192 
1193     /* ------------------------------------------------------------ */
1194     @Override
1195     public void clearAttributes()
1196     {
1197         Enumeration e = _attributes.getAttributeNames();
1198         while (e.hasMoreElements())
1199         {
1200             String name = (String)e.nextElement();
1201             checkManagedAttribute(name,null);
1202         }
1203         _attributes.clearAttributes();
1204     }
1205 
1206     /* ------------------------------------------------------------ */
1207     public void checkManagedAttribute(String name, Object value)
1208     {
1209         if (_managedAttributes != null && _managedAttributes.containsKey(name))
1210         {
1211             setManagedAttribute(name,value);
1212         }
1213     }
1214 
1215     /* ------------------------------------------------------------ */
1216     public void setManagedAttribute(String name, Object value)
1217     {
1218         Object old = _managedAttributes.put(name,value);
1219         updateBean(old,value);
1220     }
1221 
1222     /* ------------------------------------------------------------ */
1223     /**
1224      * @param classLoader
1225      *            The classLoader to set.
1226      */
1227     public void setClassLoader(ClassLoader classLoader)
1228     {
1229         _classLoader = classLoader;
1230     }
1231 
1232     /* ------------------------------------------------------------ */
1233     /**
1234      * @param contextPath
1235      *            The _contextPath to set.
1236      */
1237     public void setContextPath(String contextPath)
1238     {
1239         if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
1240             throw new IllegalArgumentException("ends with /");
1241         _contextPath = contextPath;
1242 
1243         if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
1244         {
1245             Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
1246             for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
1247                 ((ContextHandlerCollection)contextCollections[h]).mapContexts();
1248         }
1249     }
1250 
1251     /* ------------------------------------------------------------ */
1252     /**
1253      * @param servletContextName
1254      *            The servletContextName to set.
1255      */
1256     public void setDisplayName(String servletContextName)
1257     {
1258         _displayName = servletContextName;
1259     }
1260 
1261     /* ------------------------------------------------------------ */
1262     /**
1263      * @return Returns the resourceBase.
1264      */
1265     public Resource getBaseResource()
1266     {
1267         if (_baseResource == null)
1268             return null;
1269         return _baseResource;
1270     }
1271 
1272     /* ------------------------------------------------------------ */
1273     /**
1274      * @return Returns the base resource as a string.
1275      */
1276     @ManagedAttribute("document root for context")
1277     public String getResourceBase()
1278     {
1279         if (_baseResource == null)
1280             return null;
1281         return _baseResource.toString();
1282     }
1283 
1284     /* ------------------------------------------------------------ */
1285     /**
1286      * @param base
1287      *            The resourceBase to set.
1288      */
1289     public void setBaseResource(Resource base)
1290     {
1291         _baseResource = base;
1292     }
1293 
1294     /* ------------------------------------------------------------ */
1295     /**
1296      * @param resourceBase
1297      *            The base resource as a string.
1298      */
1299     public void setResourceBase(String resourceBase)
1300     {
1301         try
1302         {
1303             setBaseResource(newResource(resourceBase));
1304         }
1305         catch (Exception e)
1306         {
1307             LOG.warn(e.toString());
1308             LOG.debug(e);
1309             throw new IllegalArgumentException(resourceBase);
1310         }
1311     }
1312 
1313     /* ------------------------------------------------------------ */
1314     /**
1315      * @return True if aliases are allowed
1316      */
1317     @ManagedAttribute("true if alias checking is performed on resource")
1318     public boolean isAliases()
1319     {
1320         return _aliases;
1321     }
1322 
1323     /* ------------------------------------------------------------ */
1324     /**
1325      * @param aliases
1326      *            aliases are allowed
1327      */
1328     public void setAliases(boolean aliases)
1329     {
1330         _aliases = aliases;
1331     }
1332 
1333     /* ------------------------------------------------------------ */
1334     /**
1335      * @return Returns the mimeTypes.
1336      */
1337     public MimeTypes getMimeTypes()
1338     {
1339         if (_mimeTypes == null)
1340             _mimeTypes = new MimeTypes();
1341         return _mimeTypes;
1342     }
1343 
1344     /* ------------------------------------------------------------ */
1345     /**
1346      * @param mimeTypes
1347      *            The mimeTypes to set.
1348      */
1349     public void setMimeTypes(MimeTypes mimeTypes)
1350     {
1351         _mimeTypes = mimeTypes;
1352     }
1353 
1354     /* ------------------------------------------------------------ */
1355     /**
1356      */
1357     public void setWelcomeFiles(String[] files)
1358     {
1359         _welcomeFiles = files;
1360     }
1361 
1362     /* ------------------------------------------------------------ */
1363     /**
1364      * @return The names of the files which the server should consider to be welcome files in this context.
1365      * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
1366      * @see #setWelcomeFiles
1367      */
1368     @ManagedAttribute("Partial URIs of directory welcome files")
1369     public String[] getWelcomeFiles()
1370     {
1371         return _welcomeFiles;
1372     }
1373 
1374     /* ------------------------------------------------------------ */
1375     /**
1376      * @return Returns the errorHandler.
1377      */
1378     @ManagedAttribute("The error handler to use for the context")
1379     public ErrorHandler getErrorHandler()
1380     {
1381         return _errorHandler;
1382     }
1383 
1384     /* ------------------------------------------------------------ */
1385     /**
1386      * @param errorHandler
1387      *            The errorHandler to set.
1388      */
1389     public void setErrorHandler(ErrorHandler errorHandler)
1390     {
1391         if (errorHandler != null)
1392             errorHandler.setServer(getServer());
1393         updateBean(_errorHandler,errorHandler);
1394         _errorHandler = errorHandler;
1395     }
1396 
1397     /* ------------------------------------------------------------ */
1398     @ManagedAttribute("The maximum content size")
1399     public int getMaxFormContentSize()
1400     {
1401         return _maxFormContentSize;
1402     }
1403 
1404     /* ------------------------------------------------------------ */
1405     /**
1406      * Set the maximum size of a form post, to protect against DOS attacks from large forms.
1407      * @param maxSize
1408      */
1409     public void setMaxFormContentSize(int maxSize)
1410     {
1411         _maxFormContentSize = maxSize;
1412     }
1413 
1414     /* ------------------------------------------------------------ */
1415     public int getMaxFormKeys()
1416     {
1417         return _maxFormKeys;
1418     }
1419 
1420     /* ------------------------------------------------------------ */
1421     /**
1422      * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
1423      * @param max
1424      */
1425     public void setMaxFormKeys(int max)
1426     {
1427         _maxFormKeys = max;
1428     }
1429 
1430     /* ------------------------------------------------------------ */
1431     /**
1432      * @return True if URLs are compacted to replace multiple '/'s with a single '/'
1433      */
1434     public boolean isCompactPath()
1435     {
1436         return _compactPath;
1437     }
1438 
1439     /* ------------------------------------------------------------ */
1440     /**
1441      * @param compactPath
1442      *            True if URLs are compacted to replace multiple '/'s with a single '/'
1443      */
1444     public void setCompactPath(boolean compactPath)
1445     {
1446         _compactPath = compactPath;
1447     }
1448 
1449     /* ------------------------------------------------------------ */
1450     @Override
1451     public String toString()
1452     {
1453         String[] vhosts = getVirtualHosts();
1454 
1455         StringBuilder b = new StringBuilder();
1456 
1457         Package pkg = getClass().getPackage();
1458         if (pkg != null)
1459         {
1460             String p = pkg.getName();
1461             if (p != null && p.length() > 0)
1462             {
1463                 String[] ss = p.split("\\.");
1464                 for (String s : ss)
1465                     b.append(s.charAt(0)).append('.');
1466             }
1467         }
1468         b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(),16));
1469         b.append('{').append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability);
1470 
1471         if (vhosts != null && vhosts.length > 0)
1472             b.append(',').append(vhosts[0]);
1473         b.append('}');
1474 
1475         return b.toString();
1476     }
1477 
1478     /* ------------------------------------------------------------ */
1479     public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
1480     {
1481         if (className == null)
1482             return null;
1483 
1484         if (_classLoader == null)
1485             return Loader.loadClass(this.getClass(),className);
1486 
1487         return _classLoader.loadClass(className);
1488     }
1489 
1490     /* ------------------------------------------------------------ */
1491     public void addLocaleEncoding(String locale, String encoding)
1492     {
1493         if (_localeEncodingMap == null)
1494             _localeEncodingMap = new HashMap<String, String>();
1495         _localeEncodingMap.put(locale,encoding);
1496     }
1497 
1498     /* ------------------------------------------------------------ */
1499     public String getLocaleEncoding(String locale)
1500     {
1501         if (_localeEncodingMap == null)
1502             return null;
1503         String encoding = _localeEncodingMap.get(locale);
1504         return encoding;
1505     }
1506 
1507     /* ------------------------------------------------------------ */
1508     /**
1509      * 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
1510      * language is looked up.
1511      *
1512      * @param locale
1513      *            a <code>Locale</code> value
1514      * @return a <code>String</code> representing the character encoding for the locale or null if none found.
1515      */
1516     public String getLocaleEncoding(Locale locale)
1517     {
1518         if (_localeEncodingMap == null)
1519             return null;
1520         String encoding = _localeEncodingMap.get(locale.toString());
1521         if (encoding == null)
1522             encoding = _localeEncodingMap.get(locale.getLanguage());
1523         return encoding;
1524     }
1525 
1526     /* ------------------------------------------------------------ */
1527     /*
1528      */
1529     public Resource getResource(String path) throws MalformedURLException
1530     {
1531         if (path == null || !path.startsWith(URIUtil.SLASH))
1532             throw new MalformedURLException(path);
1533 
1534         if (_baseResource == null)
1535             return null;
1536 
1537         try
1538         {
1539             path = URIUtil.canonicalPath(path);
1540             Resource resource = _baseResource.addPath(path);
1541 
1542             if (!_aliases && resource.getAlias() != null)
1543             {
1544                 if (resource.exists())
1545                     LOG.warn("Aliased resource: " + resource + "~=" + resource.getAlias());
1546                 else if (path.endsWith("/") && resource.getAlias().toString().endsWith(path))
1547                     return resource;
1548                 else if (LOG.isDebugEnabled())
1549                     LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
1550                 return null;
1551             }
1552 
1553             return resource;
1554         }
1555         catch (Exception e)
1556         {
1557             LOG.ignore(e);
1558         }
1559 
1560         return null;
1561     }
1562 
1563     /* ------------------------------------------------------------ */
1564     /**
1565      * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
1566      */
1567     public Resource newResource(URL url) throws IOException
1568     {
1569         return Resource.newResource(url);
1570     }
1571 
1572     /* ------------------------------------------------------------ */
1573     /**
1574      * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
1575      *
1576      * @param urlOrPath
1577      *            The URL or path to convert
1578      * @return The Resource for the URL/path
1579      * @throws IOException
1580      *             The Resource could not be created.
1581      */
1582     public Resource newResource(String urlOrPath) throws IOException
1583     {
1584         return Resource.newResource(urlOrPath);
1585     }
1586 
1587     /* ------------------------------------------------------------ */
1588     /*
1589      */
1590     public Set<String> getResourcePaths(String path)
1591     {
1592         try
1593         {
1594             path = URIUtil.canonicalPath(path);
1595             Resource resource = getResource(path);
1596 
1597             if (resource != null && resource.exists())
1598             {
1599                 if (!path.endsWith(URIUtil.SLASH))
1600                     path = path + URIUtil.SLASH;
1601 
1602                 String[] l = resource.list();
1603                 if (l != null)
1604                 {
1605                     HashSet<String> set = new HashSet<String>();
1606                     for (int i = 0; i < l.length; i++)
1607                         set.add(path + l[i]);
1608                     return set;
1609                 }
1610             }
1611         }
1612         catch (Exception e)
1613         {
1614             LOG.ignore(e);
1615         }
1616         return Collections.emptySet();
1617     }
1618 
1619     /* ------------------------------------------------------------ */
1620     private String normalizeHostname(String host)
1621     {
1622         if (host == null)
1623             return null;
1624 
1625         if (host.endsWith("."))
1626             return host.substring(0,host.length() - 1);
1627 
1628         return host;
1629     }
1630 
1631     /* ------------------------------------------------------------ */
1632     /**
1633      * Context.
1634      * <p>
1635      * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
1636      * </p>
1637      *
1638      *
1639      */
1640     public class Context implements ServletContext
1641     {
1642         protected int _majorVersion = 3;
1643         protected int _minorVersion = 0;
1644         protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
1645 
1646         /* ------------------------------------------------------------ */
1647         protected Context()
1648         {
1649         }
1650 
1651         /* ------------------------------------------------------------ */
1652         public ContextHandler getContextHandler()
1653         {
1654             // TODO reduce visibility of this method
1655             return ContextHandler.this;
1656         }
1657 
1658         /* ------------------------------------------------------------ */
1659         /*
1660          * @see javax.servlet.ServletContext#getContext(java.lang.String)
1661          */
1662         @Override
1663         public ServletContext getContext(String uripath)
1664         {
1665             List<ContextHandler> contexts = new ArrayList<ContextHandler>();
1666             Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
1667             String matched_path = null;
1668 
1669             for (Handler handler : handlers)
1670             {
1671                 if (handler == null)
1672                     continue;
1673                 ContextHandler ch = (ContextHandler)handler;
1674                 String context_path = ch.getContextPath();
1675 
1676                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1677                         || "/".equals(context_path))
1678                 {
1679                     // look first for vhost matching context only
1680                     if (getVirtualHosts() != null && getVirtualHosts().length > 0)
1681                     {
1682                         if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
1683                         {
1684                             for (String h1 : getVirtualHosts())
1685                                 for (String h2 : ch.getVirtualHosts())
1686                                     if (h1.equals(h2))
1687                                     {
1688                                         if (matched_path == null || context_path.length() > matched_path.length())
1689                                         {
1690                                             contexts.clear();
1691                                             matched_path = context_path;
1692                                         }
1693 
1694                                         if (matched_path.equals(context_path))
1695                                             contexts.add(ch);
1696                                     }
1697                         }
1698                     }
1699                     else
1700                     {
1701                         if (matched_path == null || context_path.length() > matched_path.length())
1702                         {
1703                             contexts.clear();
1704                             matched_path = context_path;
1705                         }
1706 
1707                         if (matched_path.equals(context_path))
1708                             contexts.add(ch);
1709                     }
1710                 }
1711             }
1712 
1713             if (contexts.size() > 0)
1714                 return contexts.get(0)._scontext;
1715 
1716             // try again ignoring virtual hosts
1717             matched_path = null;
1718             for (Handler handler : handlers)
1719             {
1720                 if (handler == null)
1721                     continue;
1722                 ContextHandler ch = (ContextHandler)handler;
1723                 String context_path = ch.getContextPath();
1724 
1725                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1726                         || "/".equals(context_path))
1727                 {
1728                     if (matched_path == null || context_path.length() > matched_path.length())
1729                     {
1730                         contexts.clear();
1731                         matched_path = context_path;
1732                     }
1733 
1734                     if (matched_path.equals(context_path))
1735                         contexts.add(ch);
1736                 }
1737             }
1738 
1739             if (contexts.size() > 0)
1740                 return contexts.get(0)._scontext;
1741             return null;
1742         }
1743 
1744         /* ------------------------------------------------------------ */
1745         /*
1746          * @see javax.servlet.ServletContext#getMajorVersion()
1747          */
1748         @Override
1749         public int getMajorVersion()
1750         {
1751             return 3;
1752         }
1753 
1754 
1755         /* ------------------------------------------------------------ */
1756         /*
1757          * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
1758          */
1759         @Override
1760         public String getMimeType(String file)
1761         {
1762             if (_mimeTypes == null)
1763                 return null;
1764             return _mimeTypes.getMimeByExtension(file);
1765         }
1766 
1767         /* ------------------------------------------------------------ */
1768         /*
1769          * @see javax.servlet.ServletContext#getMinorVersion()
1770          */
1771         @Override
1772         public int getMinorVersion()
1773         {
1774             return 0;
1775         }
1776 
1777         /* ------------------------------------------------------------ */
1778         /*
1779          * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
1780          */
1781         @Override
1782         public RequestDispatcher getNamedDispatcher(String name)
1783         {
1784             return null;
1785         }
1786 
1787         /* ------------------------------------------------------------ */
1788         /*
1789          * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
1790          */
1791         @Override
1792         public RequestDispatcher getRequestDispatcher(String uriInContext)
1793         {
1794             if (uriInContext == null)
1795                 return null;
1796 
1797             if (!uriInContext.startsWith("/"))
1798                 return null;
1799 
1800             try
1801             {
1802                 String query = null;
1803                 int q = 0;
1804                 if ((q = uriInContext.indexOf('?')) > 0)
1805                 {
1806                     query = uriInContext.substring(q + 1);
1807                     uriInContext = uriInContext.substring(0,q);
1808                 }
1809                 // if ((q = uriInContext.indexOf(';')) > 0)
1810                 //     uriInContext = uriInContext.substring(0,q);
1811 
1812                 String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
1813                 String uri = URIUtil.addPaths(getContextPath(),uriInContext);
1814                 ContextHandler context = ContextHandler.this;
1815                 return new Dispatcher(context,uri,pathInContext,query);
1816             }
1817             catch (Exception e)
1818             {
1819                 LOG.ignore(e);
1820             }
1821             return null;
1822         }
1823 
1824         /* ------------------------------------------------------------ */
1825         /*
1826          * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
1827          */
1828         @Override
1829         public String getRealPath(String path)
1830         {
1831             if (path == null)
1832                 return null;
1833             if (path.length() == 0)
1834                 path = URIUtil.SLASH;
1835             else if (path.charAt(0) != '/')
1836                 path = URIUtil.SLASH + path;
1837 
1838             try
1839             {
1840                 Resource resource = ContextHandler.this.getResource(path);
1841                 if (resource != null)
1842                 {
1843                     File file = resource.getFile();
1844                     if (file != null)
1845                         return file.getCanonicalPath();
1846                 }
1847             }
1848             catch (Exception e)
1849             {
1850                 LOG.ignore(e);
1851             }
1852 
1853             return null;
1854         }
1855 
1856         /* ------------------------------------------------------------ */
1857         @Override
1858         public URL getResource(String path) throws MalformedURLException
1859         {
1860             Resource resource = ContextHandler.this.getResource(path);
1861             if (resource != null && resource.exists())
1862                 return resource.getURL();
1863             return null;
1864         }
1865 
1866         /* ------------------------------------------------------------ */
1867         /*
1868          * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
1869          */
1870         @Override
1871         public InputStream getResourceAsStream(String path)
1872         {
1873             try
1874             {
1875                 URL url = getResource(path);
1876                 if (url == null)
1877                     return null;
1878                 Resource r = Resource.newResource(url);
1879                 return r.getInputStream();
1880             }
1881             catch (Exception e)
1882             {
1883                 LOG.ignore(e);
1884                 return null;
1885             }
1886         }
1887 
1888         /* ------------------------------------------------------------ */
1889         /*
1890          * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
1891          */
1892         @Override
1893         public Set getResourcePaths(String path)
1894         {
1895             return ContextHandler.this.getResourcePaths(path);
1896         }
1897 
1898         /* ------------------------------------------------------------ */
1899         /*
1900          * @see javax.servlet.ServletContext#getServerInfo()
1901          */
1902         @Override
1903         public String getServerInfo()
1904         {
1905             return "jetty/" + Server.getVersion();
1906         }
1907 
1908         /* ------------------------------------------------------------ */
1909         /*
1910          * @see javax.servlet.ServletContext#getServlet(java.lang.String)
1911          */
1912         @Override
1913         @Deprecated
1914         public Servlet getServlet(String name) throws ServletException
1915         {
1916             return null;
1917         }
1918 
1919         /* ------------------------------------------------------------ */
1920         /*
1921          * @see javax.servlet.ServletContext#getServletNames()
1922          */
1923         @SuppressWarnings("unchecked")
1924         @Override
1925         @Deprecated
1926         public Enumeration getServletNames()
1927         {
1928             return Collections.enumeration(Collections.EMPTY_LIST);
1929         }
1930 
1931         /* ------------------------------------------------------------ */
1932         /*
1933          * @see javax.servlet.ServletContext#getServlets()
1934          */
1935         @SuppressWarnings("unchecked")
1936         @Override
1937         @Deprecated
1938         public Enumeration getServlets()
1939         {
1940             return Collections.enumeration(Collections.EMPTY_LIST);
1941         }
1942 
1943         /* ------------------------------------------------------------ */
1944         /*
1945          * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
1946          */
1947         @Override
1948         public void log(Exception exception, String msg)
1949         {
1950             _logger.warn(msg,exception);
1951         }
1952 
1953         /* ------------------------------------------------------------ */
1954         /*
1955          * @see javax.servlet.ServletContext#log(java.lang.String)
1956          */
1957         @Override
1958         public void log(String msg)
1959         {
1960             _logger.info(msg);
1961         }
1962 
1963         /* ------------------------------------------------------------ */
1964         /*
1965          * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
1966          */
1967         @Override
1968         public void log(String message, Throwable throwable)
1969         {
1970             _logger.warn(message,throwable);
1971         }
1972 
1973         /* ------------------------------------------------------------ */
1974         /*
1975          * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
1976          */
1977         @Override
1978         public String getInitParameter(String name)
1979         {
1980             return ContextHandler.this.getInitParameter(name);
1981         }
1982 
1983         /* ------------------------------------------------------------ */
1984         /*
1985          * @see javax.servlet.ServletContext#getInitParameterNames()
1986          */
1987         @SuppressWarnings("unchecked")
1988         @Override
1989         public Enumeration getInitParameterNames()
1990         {
1991             return ContextHandler.this.getInitParameterNames();
1992         }
1993 
1994         /* ------------------------------------------------------------ */
1995         /*
1996          * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
1997          */
1998         @Override
1999         public synchronized Object getAttribute(String name)
2000         {
2001             Object o = ContextHandler.this.getAttribute(name);
2002             if (o == null && _contextAttributes != null)
2003                 o = _contextAttributes.getAttribute(name);
2004             return o;
2005         }
2006 
2007         /* ------------------------------------------------------------ */
2008         /*
2009          * @see javax.servlet.ServletContext#getAttributeNames()
2010          */
2011         @SuppressWarnings("unchecked")
2012         @Override
2013         public synchronized Enumeration getAttributeNames()
2014         {
2015             HashSet<String> set = new HashSet<String>();
2016             if (_contextAttributes != null)
2017             {
2018                 Enumeration<String> e = _contextAttributes.getAttributeNames();
2019                 while (e.hasMoreElements())
2020                     set.add(e.nextElement());
2021             }
2022             Enumeration<String> e = _attributes.getAttributeNames();
2023             while (e.hasMoreElements())
2024                 set.add(e.nextElement());
2025 
2026             return Collections.enumeration(set);
2027         }
2028 
2029         /* ------------------------------------------------------------ */
2030         /*
2031          * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
2032          */
2033         @Override
2034         public synchronized void setAttribute(String name, Object value)
2035         {
2036             checkManagedAttribute(name,value);
2037             Object old_value = _contextAttributes.getAttribute(name);
2038 
2039             if (value == null)
2040                 _contextAttributes.removeAttribute(name);
2041             else
2042                 _contextAttributes.setAttribute(name,value);
2043 
2044             if (_contextAttributeListeners != null)
2045             {
2046                 ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
2047 
2048                 for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2049                 {
2050                     ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
2051 
2052                     if (old_value == null)
2053                         l.attributeAdded(event);
2054                     else if (value == null)
2055                         l.attributeRemoved(event);
2056                     else
2057                         l.attributeReplaced(event);
2058                 }
2059             }
2060         }
2061 
2062         /* ------------------------------------------------------------ */
2063         /*
2064          * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
2065          */
2066         @Override
2067         public synchronized void removeAttribute(String name)
2068         {
2069             checkManagedAttribute(name,null);
2070 
2071             if (_contextAttributes == null)
2072             {
2073                 // Set it on the handler
2074                 _attributes.removeAttribute(name);
2075                 return;
2076             }
2077 
2078             Object old_value = _contextAttributes.getAttribute(name);
2079             _contextAttributes.removeAttribute(name);
2080             if (old_value != null)
2081             {
2082                 if (_contextAttributeListeners != null)
2083                 {
2084                     ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
2085 
2086                     for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2087                         ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
2088                 }
2089             }
2090         }
2091 
2092         /* ------------------------------------------------------------ */
2093         /*
2094          * @see javax.servlet.ServletContext#getServletContextName()
2095          */
2096         @Override
2097         public String getServletContextName()
2098         {
2099             String name = ContextHandler.this.getDisplayName();
2100             if (name == null)
2101                 name = ContextHandler.this.getContextPath();
2102             return name;
2103         }
2104 
2105         /* ------------------------------------------------------------ */
2106         @Override
2107         public String getContextPath()
2108         {
2109             if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
2110                 return "";
2111 
2112             return _contextPath;
2113         }
2114 
2115         /* ------------------------------------------------------------ */
2116         @Override
2117         public String toString()
2118         {
2119             return "ServletContext@" + ContextHandler.this.toString();
2120         }
2121 
2122         /* ------------------------------------------------------------ */
2123         @Override
2124         public boolean setInitParameter(String name, String value)
2125         {
2126             if (ContextHandler.this.getInitParameter(name) != null)
2127                 return false;
2128             ContextHandler.this.getInitParams().put(name,value);
2129             return true;
2130         }
2131 
2132         /* ------------------------------------------------------------ */
2133         final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
2134 
2135         @Override
2136         public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
2137         {
2138             LOG.warn(__unimplmented);
2139             return null;
2140         }
2141 
2142         @Override
2143         public Dynamic addFilter(String filterName, Filter filter)
2144         {
2145             LOG.warn(__unimplmented);
2146             return null;
2147         }
2148 
2149         @Override
2150         public Dynamic addFilter(String filterName, String className)
2151         {
2152             LOG.warn(__unimplmented);
2153             return null;
2154         }
2155 
2156         @Override
2157         public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
2158         {
2159             LOG.warn(__unimplmented);
2160             return null;
2161         }
2162 
2163         @Override
2164         public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
2165         {
2166             LOG.warn(__unimplmented);
2167             return null;
2168         }
2169 
2170         @Override
2171         public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
2172         {
2173             LOG.warn(__unimplmented);
2174             return null;
2175         }
2176 
2177         @Override
2178         public <T extends Filter> T createFilter(Class<T> c) throws ServletException
2179         {
2180             LOG.warn(__unimplmented);
2181             return null;
2182         }
2183 
2184         @Override
2185         public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
2186         {
2187             LOG.warn(__unimplmented);
2188             return null;
2189         }
2190 
2191         @Override
2192         public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
2193         {
2194             LOG.warn(__unimplmented);
2195             return null;
2196         }
2197 
2198         @Override
2199         public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
2200         {
2201             LOG.warn(__unimplmented);
2202             return null;
2203         }
2204 
2205         @Override
2206         public FilterRegistration getFilterRegistration(String filterName)
2207         {
2208             LOG.warn(__unimplmented);
2209             return null;
2210         }
2211 
2212         @Override
2213         public Map<String, ? extends FilterRegistration> getFilterRegistrations()
2214         {
2215             LOG.warn(__unimplmented);
2216             return null;
2217         }
2218 
2219         @Override
2220         public ServletRegistration getServletRegistration(String servletName)
2221         {
2222             LOG.warn(__unimplmented);
2223             return null;
2224         }
2225 
2226         @Override
2227         public Map<String, ? extends ServletRegistration> getServletRegistrations()
2228         {
2229             LOG.warn(__unimplmented);
2230             return null;
2231         }
2232 
2233         @Override
2234         public SessionCookieConfig getSessionCookieConfig()
2235         {
2236             LOG.warn(__unimplmented);
2237             return null;
2238         }
2239 
2240         @Override
2241         public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
2242         {
2243             LOG.warn(__unimplmented);
2244         }
2245 
2246         @Override
2247         public void addListener(String className)
2248         {
2249             if (!_enabled)
2250                 throw new UnsupportedOperationException();
2251 
2252             try
2253             {
2254                 Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
2255                 addListener(clazz);
2256             }
2257             catch (ClassNotFoundException e)
2258             {
2259                 throw new IllegalArgumentException(e);
2260             }
2261         }
2262 
2263         @Override
2264         public <T extends EventListener> void addListener(T t)
2265         {
2266             if (!_enabled)
2267                 throw new UnsupportedOperationException();
2268             ContextHandler.this.addEventListener(t);
2269         }
2270 
2271         @Override
2272         public void addListener(Class<? extends EventListener> listenerClass)
2273         {
2274             if (!_enabled)
2275                 throw new UnsupportedOperationException();
2276 
2277             try
2278             {
2279                 EventListener e = createListener(listenerClass);
2280                 ContextHandler.this.addEventListener(e);
2281                 ContextHandler.this.restrictEventListener(e);
2282             }
2283             catch (ServletException e)
2284             {
2285                 throw new IllegalArgumentException(e);
2286             }
2287         }
2288 
2289         @Override
2290         public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
2291         {
2292             try
2293             {
2294                 return clazz.newInstance();
2295             }
2296             catch (InstantiationException e)
2297             {
2298                 throw new ServletException(e);
2299             }
2300             catch (IllegalAccessException e)
2301             {
2302                 throw new ServletException(e);
2303             }
2304         }
2305 
2306         @Override
2307         public ClassLoader getClassLoader()
2308         {
2309             AccessController.checkPermission(new RuntimePermission("getClassLoader"));
2310             return _classLoader;
2311         }
2312 
2313         @Override
2314         public int getEffectiveMajorVersion()
2315         {
2316             return _majorVersion;
2317         }
2318 
2319         @Override
2320         public int getEffectiveMinorVersion()
2321         {
2322             return _minorVersion;
2323         }
2324 
2325         public void setEffectiveMajorVersion (int v)
2326         {
2327             _majorVersion = v;
2328         }
2329 
2330         public void setEffectiveMinorVersion (int v)
2331         {
2332             _minorVersion = v;
2333         }
2334 
2335         @Override
2336         public JspConfigDescriptor getJspConfigDescriptor()
2337         {
2338             LOG.warn(__unimplmented);
2339             return null;
2340         }
2341 
2342         public void setJspConfigDescriptor(JspConfigDescriptor d)
2343         {
2344 
2345         }
2346 
2347         @Override
2348         public void declareRoles(String... roleNames)
2349         {
2350             if (!isStarting())
2351                 throw new IllegalStateException ();
2352             if (!_enabled)
2353                 throw new UnsupportedOperationException();
2354 
2355             // TODO Auto-generated method stub
2356 
2357         }
2358 
2359         public void setEnabled(boolean enabled)
2360         {
2361             _enabled = enabled;
2362         }
2363 
2364         public boolean isEnabled()
2365         {
2366             return _enabled;
2367         }
2368     }
2369 }