View Javadoc

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