View Javadoc

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