View Javadoc

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