1   //========================================================================
2   //$Id: ContextHandler.java,v 1.16 2005/11/17 11:19:45 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty.handler;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.EnumSet;
27  import java.util.Enumeration;
28  import java.util.EventListener;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import javax.servlet.DispatcherType;
36  import javax.servlet.RequestDispatcher;
37  import javax.servlet.Servlet;
38  import javax.servlet.ServletContext;
39  import javax.servlet.ServletContextAttributeEvent;
40  import javax.servlet.ServletContextAttributeListener;
41  import javax.servlet.ServletContextEvent;
42  import javax.servlet.ServletContextListener;
43  import javax.servlet.ServletException;
44  import javax.servlet.ServletRequestAttributeListener;
45  import javax.servlet.ServletRequestEvent;
46  import javax.servlet.ServletRequestListener;
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletResponse;
49  
50  import org.mortbay.io.Buffer;
51  import org.mortbay.jetty.Handler;
52  import org.mortbay.jetty.HandlerContainer;
53  import org.mortbay.jetty.HttpConnection;
54  import org.mortbay.jetty.HttpException;
55  import org.mortbay.jetty.MimeTypes;
56  import org.mortbay.jetty.Request;
57  import org.mortbay.jetty.Server;
58  import org.mortbay.jetty.webapp.WebAppClassLoader;
59  import org.mortbay.log.Log;
60  import org.mortbay.log.Logger;
61  import org.mortbay.resource.Resource;
62  import org.mortbay.util.Attributes;
63  import org.mortbay.util.AttributesMap;
64  import org.mortbay.util.LazyList;
65  import org.mortbay.util.Loader;
66  import org.mortbay.util.URIUtil;
67  
68  /* ------------------------------------------------------------ */
69  /** ContextHandler.
70   * 
71   * This handler wraps a call to handle by setting the context and
72   * servlet path, plus setting the context classloader.
73   * 
74   * Note. Because of http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4425695
75   * directly replacing war or jar files in a context is not supported.
76   * You should use classes instead of jars if they will change, or deploy
77   * a packed war file that gets extracted on deployment.
78   * 
79   * 
80   * @org.apache.xbean.XBean description="Creates a basic HTTP context"
81   *
82   * @author gregw
83   *
84   */
85  public class ContextHandler extends HandlerWrapper implements Attributes, Server.Graceful
86  {
87      private static ThreadLocal __context=new ThreadLocal();
88      
89      /* ------------------------------------------------------------ */
90      /** Get the current ServletContext implementation.
91       * This call is only valid during a call to doStart and is available to
92       * nested handlers to access the context.
93       * 
94       * @return ServletContext implementation
95       */
96      public static SContext getCurrentContext()
97      {
98          SContext context = (SContext)__context.get();
99          return context;
100     }
101 
102     protected SContext _scontext;
103     
104     private Attributes _attributes;
105     private Attributes _contextAttributes;
106     private ClassLoader _classLoader;
107     private String _contextPath="/";
108     private Map _initParams;
109     private String _displayName;
110     private Resource _baseResource;  
111     private MimeTypes _mimeTypes;
112     private Map _localeEncodingMap;
113     private String[] _welcomeFiles;
114     private ErrorHandler _errorHandler;
115     private String[] _vhosts;
116     private Set _connectors;
117     private EventListener[] _eventListeners;
118     private Logger _logger;
119     private boolean _shutdown;
120     private boolean _allowNullPathInfo;
121     private int _maxFormContentSize=Integer.getInteger("org.mortbay.jetty.Request.maxFormContentSize",200000).intValue();
122     private boolean _compactPath=false;
123     private boolean _aliases=true;
124 
125     private Object _contextListeners;
126     private Object _contextAttributeListeners;
127     private Object _requestListeners;
128     private Object _requestAttributeListeners;
129     
130     /* ------------------------------------------------------------ */
131     /**
132      * 
133      */
134     public ContextHandler()
135     {
136         super();
137         _scontext=new SContext();
138         _attributes=new AttributesMap();
139         _initParams=new HashMap();
140     }
141     
142     /* ------------------------------------------------------------ */
143     /**
144      * 
145      */
146     protected ContextHandler(SContext context)
147     {
148         super();
149         _scontext=context;
150         _attributes=new AttributesMap();
151         _initParams=new HashMap();
152     }
153     
154     /* ------------------------------------------------------------ */
155     /**
156      * 
157      */
158     public ContextHandler(String contextPath)
159     {
160         this();
161         setContextPath(contextPath);
162     }
163     
164     /* ------------------------------------------------------------ */
165     /**
166      * 
167      */
168     public ContextHandler(HandlerContainer parent, String contextPath)
169     {
170         this();
171         setContextPath(contextPath);
172         parent.addHandler(this);
173     }
174 
175     /* ------------------------------------------------------------ */
176     public SContext getServletContext()
177     {
178         return _scontext;
179     }
180     
181     /* ------------------------------------------------------------ */
182     /**
183      * @return the allowNullPathInfo true if /context is not redirected to /context/
184      */
185     public boolean getAllowNullPathInfo()
186     {
187         return _allowNullPathInfo;
188     }
189 
190     /* ------------------------------------------------------------ */
191     /**
192      * @param allowNullPathInfo  true if /context is not redirected to /context/
193      */
194     public void setAllowNullPathInfo(boolean allowNullPathInfo)
195     {
196         _allowNullPathInfo=allowNullPathInfo;
197     }
198 
199     /* ------------------------------------------------------------ */
200     public void setServer(Server server)
201     {
202         if (_errorHandler!=null)
203         {
204             Server old_server=getServer();
205             if (old_server!=null && old_server!=server)
206                 old_server.getContainer().update(this, _errorHandler, null, "error",true);
207             super.setServer(server); 
208             if (server!=null && server!=old_server)
209                 server.getContainer().update(this, null, _errorHandler, "error",true);
210             _errorHandler.setServer(server); 
211         }
212         else
213             super.setServer(server); 
214         
215     }
216 
217     /* ------------------------------------------------------------ */
218     /** Set the virtual hosts for the context.
219      * Only requests that have a matching host header or fully qualified
220      * URL will be passed to that context with a virtual host name.
221      * A context with no virtual host names or a null virtual host name is
222      * available to all requests that are not served by a context with a
223      * matching virtual host name.
224      * @param vhosts Array of virtual hosts that this context responds to. A
225      * null host name or null/empty array means any hostname is acceptable.
226      * Host names may String representation of IP addresses.
227      */
228     public void setVirtualHosts( String[] vhosts )
229     {
230         if ( vhosts == null )
231         {
232             _vhosts = vhosts;
233         } 
234         else 
235         {
236             _vhosts = new String[vhosts.length];
237             for ( int i = 0; i < vhosts.length; i++ )
238                 _vhosts[i] = normalizeHostname( vhosts[i]);
239         }
240     }
241 
242     /* ------------------------------------------------------------ */
243     /** Get the virtual hosts for the context.
244      * Only requests that have a matching host header or fully qualified
245      * URL will be passed to that context with a virtual host name.
246      * A context with no virtual host names or a null virtual host name is
247      * available to all requests that are not served by a context with a
248      * matching virtual host name.
249      * @return Array of virtual hosts that this context responds to. A
250      * null host name or empty array means any hostname is acceptable.
251      * Host names may be String representation of IP addresses.
252      */
253     public String[] getVirtualHosts()
254     {
255         return _vhosts;
256     }
257 
258     /* ------------------------------------------------------------ */
259     /** 
260      * @deprecated use {@link #setConnectorNames(String[])} 
261      */
262     public void setHosts(String[] hosts)
263     {
264         setConnectorNames(hosts);
265     }
266 
267     /* ------------------------------------------------------------ */
268     /** Get the hosts for the context.
269      * @deprecated
270      */
271     public String[] getHosts()
272     {
273         return getConnectorNames();
274     }
275 
276     /* ------------------------------------------------------------ */
277     /**
278      * @return an array of connector names that this context
279      * will accept a request from.
280      */
281     public String[] getConnectorNames()
282     {
283         if (_connectors==null || _connectors.size()==0)
284             return null;
285             
286         return (String[])_connectors.toArray(new String[_connectors.size()]);
287     }
288 
289     /* ------------------------------------------------------------ */
290     /** Set the names of accepted connectors.
291      * 
292      * Names are either "host:port" or a specific configured name for a connector.
293      * 
294      * @param connectors If non null, an array of connector names that this context
295      * will accept a request from.
296      */
297     public void setConnectorNames(String[] connectors)
298     {
299         if (connectors==null || connectors.length==0)
300             _connectors=null;
301         else
302             _connectors= new HashSet(Arrays.asList(connectors));
303     }
304     
305     /* ------------------------------------------------------------ */
306     /* 
307      * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
308      */
309     public Object getAttribute(String name)
310     {
311         return _attributes.getAttribute(name);
312     }
313 
314     /* ------------------------------------------------------------ */
315     /* 
316      * @see javax.servlet.ServletContext#getAttributeNames()
317      */
318     public Enumeration getAttributeNames()
319     {
320         return AttributesMap.getAttributeNamesCopy(_attributes);
321     }
322     
323     /* ------------------------------------------------------------ */
324     /**
325      * @return Returns the attributes.
326      */
327     public Attributes getAttributes()
328     {
329         return _attributes;
330     }
331     
332     /* ------------------------------------------------------------ */
333     /**
334      * @return Returns the classLoader.
335      */
336     public ClassLoader getClassLoader()
337     {
338         return _classLoader;
339     }
340 
341     /* ------------------------------------------------------------ */
342     /**
343      * Make best effort to extract a file classpath from the context classloader
344      * @return Returns the classLoader.
345      */
346     public String getClassPath()
347     {
348         if ( _classLoader==null || !(_classLoader instanceof URLClassLoader))
349             return null;
350         URLClassLoader loader = (URLClassLoader)_classLoader;
351         URL[] urls =loader.getURLs();
352         StringBuilder classpath=new StringBuilder();
353         for (int i=0;i<urls.length;i++)
354         {
355             try
356             {
357                 Resource resource = newResource(urls[i]);
358                 File file=resource.getFile();
359                 if (file.exists())
360                 {
361                     if (classpath.length()>0)
362                         classpath.append(File.pathSeparatorChar);
363                     classpath.append(file.getAbsolutePath());
364                 }
365             }
366             catch (IOException e)
367             {
368                 Log.debug(e);
369             }
370         }
371         if (classpath.length()==0)
372             return null;
373         return classpath.toString();
374     }
375 
376     /* ------------------------------------------------------------ */
377     /**
378      * @return Returns the _contextPath.
379      */
380     public String getContextPath()
381     {
382         return _contextPath;
383     }
384    
385     /* ------------------------------------------------------------ */
386     /* 
387      * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
388      */
389     public String getInitParameter(String name)
390     {
391         return (String)_initParams.get(name);
392     }
393 
394     /* ------------------------------------------------------------ */
395     /* 
396      * @see javax.servlet.ServletContext#getInitParameterNames()
397      */
398     public Enumeration getInitParameterNames()
399     {
400         return Collections.enumeration(_initParams.keySet());
401     }
402     
403     /* ------------------------------------------------------------ */
404     /**
405      * @return Returns the initParams.
406      */
407     public Map getInitParams()
408     {
409         return _initParams;
410     }
411 
412     /* ------------------------------------------------------------ */
413     /* 
414      * @see javax.servlet.ServletContext#getServletContextName()
415      */
416     public String getDisplayName()
417     {
418         return _displayName;
419     }
420 
421     /* ------------------------------------------------------------ */
422     public EventListener[] getEventListeners()
423     {
424         return _eventListeners;
425     }
426     
427     /* ------------------------------------------------------------ */
428     public void setEventListeners(EventListener[] eventListeners)
429     {
430         _contextListeners=null;
431         _contextAttributeListeners=null;
432         _requestListeners=null;
433         _requestAttributeListeners=null;
434         
435         _eventListeners=eventListeners;
436         
437         for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
438         {
439             EventListener listener = _eventListeners[i];
440             
441             if (listener instanceof ServletContextListener)
442                 _contextListeners= LazyList.add(_contextListeners, listener);
443             
444             if (listener instanceof ServletContextAttributeListener)
445                 _contextAttributeListeners= LazyList.add(_contextAttributeListeners, listener);
446             
447             if (listener instanceof ServletRequestListener)
448                 _requestListeners= LazyList.add(_requestListeners, listener);
449             
450             if (listener instanceof ServletRequestAttributeListener)
451                 _requestAttributeListeners= LazyList.add(_requestAttributeListeners, listener);
452         }
453     }     
454 
455     /* ------------------------------------------------------------ */
456     public void addEventListener(EventListener listener) 
457     {
458         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));
459     }
460 
461     /* ------------------------------------------------------------ */
462     /**
463      * @return true if this context is accepting new requests
464      */
465     public boolean isShutdown()
466     {
467         return !_shutdown;
468     }
469 
470     /* ------------------------------------------------------------ */
471     /** Set shutdown status.
472      * This field allows for graceful shutdown of a context. A started context may be put into non accepting state so
473      * that existing requests can complete, but no new requests are accepted.
474      * @param accepting true if this context is accepting new requests
475      */
476     public void setShutdown(boolean shutdown)
477     {
478         _shutdown = shutdown;
479     }
480     
481     /* ------------------------------------------------------------ */
482     /* 
483      * @see org.mortbay.thread.AbstractLifeCycle#doStart()
484      */
485     protected void doStart() throws Exception
486     {
487         if (_contextPath==null)
488             throw new IllegalStateException("Null contextPath");
489         
490         _logger=Log.getLogger(getDisplayName()==null?getContextPath():getDisplayName());
491         ClassLoader old_classloader=null;
492         Thread current_thread=null;
493         Object old_context=null;
494         _contextAttributes=new AttributesMap();
495         try
496         {
497             // Set the classloader
498             if (_classLoader!=null)
499             {
500                 current_thread=Thread.currentThread();
501                 old_classloader=current_thread.getContextClassLoader();
502                 current_thread.setContextClassLoader(_classLoader);
503             }
504             
505 
506             if (_mimeTypes==null)
507                 _mimeTypes=new MimeTypes();
508             
509             old_context=__context.get();
510             __context.set(_scontext);
511             
512             if (_errorHandler==null)
513                 setErrorHandler(new ErrorHandler());
514             
515             startContext();
516             
517         }
518         finally
519         {
520             __context.set(old_context);
521             
522             // reset the classloader
523             if (_classLoader!=null)
524             {
525                 current_thread.setContextClassLoader(old_classloader);
526             }
527         }
528     }
529 
530     /* ------------------------------------------------------------ */
531     protected void startContext()
532     	throws Exception
533     {
534         super.doStart();
535 
536         if (_errorHandler!=null)
537             _errorHandler.start();
538         
539         // Context listeners
540         if (_contextListeners != null )
541         {
542             ServletContextEvent event= new ServletContextEvent(_scontext);
543             for (int i= 0; i < LazyList.size(_contextListeners); i++)
544             {
545                 ((ServletContextListener)LazyList.get(_contextListeners, i)).contextInitialized(event);
546             }
547         }
548     }
549     
550     /* ------------------------------------------------------------ */
551     /* 
552      * @see org.mortbay.thread.AbstractLifeCycle#doStop()
553      */
554     protected void doStop() throws Exception
555     {
556         ClassLoader old_classloader=null;
557         Thread current_thread=null;
558 
559         Object old_context=__context.get();
560         __context.set(_scontext);
561         try
562         {
563             // Set the classloader
564             if (_classLoader!=null)
565             {
566                 current_thread=Thread.currentThread();
567                 old_classloader=current_thread.getContextClassLoader();
568                 current_thread.setContextClassLoader(_classLoader);
569             }
570             
571             super.doStop();
572             
573             // Context listeners
574             if (_contextListeners != null )
575             {
576                 ServletContextEvent event= new ServletContextEvent(_scontext);
577                 for (int i=LazyList.size(_contextListeners); i-->0;)
578                 {
579                     ((ServletContextListener)LazyList.get(_contextListeners, i)).contextDestroyed(event);
580                 }
581             }
582 
583             if (_errorHandler!=null)
584                 _errorHandler.stop();
585         }
586         finally
587         {
588             __context.set(old_context);
589             // reset the classloader
590             if (_classLoader!=null)
591                 current_thread.setContextClassLoader(old_classloader);
592         }
593 
594         if (_contextAttributes!=null)
595             _contextAttributes.clearAttributes();
596         _contextAttributes=null;
597     }
598     
599     /* ------------------------------------------------------------ */
600     /* 
601      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
602      */
603     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
604             throws IOException, ServletException
605     {   
606         boolean new_context=false;
607         SContext old_context=null;
608         String old_context_path=null;
609         String old_servlet_path=null;
610         String old_path_info=null;
611         ClassLoader old_classloader=null;
612         Thread current_thread=null;
613         String pathInfo=null;
614         
615         Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
616         if( !isStarted() || _shutdown || (dispatch==REQUEST && base_request.isHandled()))
617             return;
618         
619         old_context=base_request.getContext();
620         
621         // Are we already in this context?
622         if (old_context!=_scontext)
623         {
624             new_context=true;
625             
626             // Check the vhosts
627             if (_vhosts!=null && _vhosts.length>0)
628             {
629                 String vhost = normalizeHostname( request.getServerName());
630 
631                 boolean match=false;
632                 
633                 // TODO non-linear lookup
634                 for (int i=0;!match && i<_vhosts.length;i++)
635                     match=_vhosts[i]!=null && _vhosts[i].equalsIgnoreCase(vhost);
636                 if (!match)
637                     return;
638             }
639             
640             // Check the connector
641             if (_connectors!=null && _connectors.size()>0)
642             {
643                 String connector=HttpConnection.getCurrentConnection().getConnector().getName();
644                 if (connector==null || !_connectors.contains(connector))
645                     return;
646             }
647             
648             // Nope - so check the target.
649             if (dispatch==REQUEST)
650             {
651                 if (_compactPath)
652                     target=URIUtil.compactPath(target);
653                 
654                 if (target.equals(_contextPath))
655                 {
656                     if (_contextPath.length()==1)
657                     {
658                         target=URIUtil.SLASH;
659                         pathInfo=URIUtil.SLASH;
660                     }
661                     else if (_allowNullPathInfo)
662                     {
663                         target=URIUtil.SLASH;
664                         pathInfo=null;
665                         request.setAttribute("org.mortbay.jetty.nullPathInfo",target);
666                     }
667                     else
668                     {
669                         base_request.setHandled(true);
670                         if (request.getQueryString()!=null)
671                             response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)+"?"+request.getQueryString());
672                         else 
673                             response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
674                         return;
675                     }
676                 }
677                 else if (target.startsWith(_contextPath) && (_contextPath.length()==1 || target.charAt(_contextPath.length())=='/'))
678                 {
679                     if (_contextPath.length()>1)
680                         target=target.substring(_contextPath.length());
681                     pathInfo=target;
682                 }
683                 else 
684                 {
685                     // Not for this context!
686                     return;
687                 }
688             }
689         }
690         
691         try
692         {
693             old_context_path=base_request.getContextPath();
694             old_servlet_path=base_request.getServletPath();
695             old_path_info=base_request.getPathInfo();
696             
697             // Update the paths
698             base_request.setContext(_scontext);
699             if (dispatch!=INCLUDE && target.startsWith("/"))
700             {
701                 if (_contextPath.length()==1)
702                     base_request.setContextPath("");
703                 else
704                     base_request.setContextPath(_contextPath);
705                 base_request.setServletPath(null);
706                 base_request.setPathInfo(pathInfo);
707             }
708 
709             ServletRequestEvent event=null;
710             if (new_context)
711             {
712                 // Set the classloader
713                 if (_classLoader!=null)
714                 {
715                     current_thread=Thread.currentThread();
716                     old_classloader=current_thread.getContextClassLoader();
717                     current_thread.setContextClassLoader(_classLoader);
718                 }
719                 
720                 // Handle the REALLY SILLY request events!
721                 // TODO, this really should be done AFTER security and session handlers
722                 // Perhaps this can be moved to the servlet handler?
723                 if (_requestListeners!=null)
724                 {
725                     event = new ServletRequestEvent(_scontext,request);
726                     for(int i=0;i<LazyList.size(_requestListeners);i++)
727                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(event);
728                 }
729                 for(int i=0;i<LazyList.size(_requestAttributeListeners);i++)
730                     base_request.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
731             }
732             
733             // Handle the request
734             try
735             {
736                 if (dispatch==REQUEST && isProtectedTarget(target))
737                     throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
738                 
739                 Handler handler = getHandler();
740                 if (handler!=null)
741                     handler.handle(target, request, response, dispatch);
742             }
743             catch(HttpException e)
744             {
745                 Log.debug(e);
746                 response.sendError(e.getStatus(), e.getReason());
747             }
748             finally
749             {
750                 // Handle more REALLY SILLY request events!
751                 if (new_context)
752                 {
753                     for(int i=LazyList.size(_requestListeners);i-->0;)
754                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(event);
755                     
756                     for(int i=0;i<LazyList.size(_requestAttributeListeners);i++)
757                         base_request.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
758                 }
759             }
760         }
761         finally
762         {
763             if (old_context!=_scontext)
764             {
765                 // reset the classloader
766                 if (_classLoader!=null)
767                 {
768                     current_thread.setContextClassLoader(old_classloader);
769                 }
770                 
771                 // reset the context and servlet path.
772                 base_request.setContext(old_context);
773                 base_request.setContextPath(old_context_path);
774                 base_request.setServletPath(old_servlet_path);
775                 base_request.setPathInfo(old_path_info); 
776             }
777         }
778     }
779 
780     /* ------------------------------------------------------------ */
781     /** Check the target.
782      * Called by {@link #handle(String, HttpServletRequest, HttpServletResponse, int)} when a
783      * target within a context is determined.  If the target is protected, 404 is returned.
784      * The default implementation always returns false.
785      * @see org.mortbay.jetty.webapp.WebAppContext#isProtectedTarget(String)
786      */
787     /* ------------------------------------------------------------ */
788     protected boolean isProtectedTarget(String target)
789     { 
790         return false;
791     }
792 
793     /* ------------------------------------------------------------ */
794     /* 
795      * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
796      */
797     public void removeAttribute(String name)
798     {
799         _attributes.removeAttribute(name);
800     }
801 
802     /* ------------------------------------------------------------ */
803     /* Set a context attribute.
804      * Attributes set via this API cannot be overriden by the ServletContext.setAttribute API.
805      * Their lifecycle spans the stop/start of a context.  No attribute listener events are 
806      * triggered by this API.
807      * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
808      */
809     public void setAttribute(String name, Object value)
810     {
811         _attributes.setAttribute(name,value);
812     }
813     
814     /* ------------------------------------------------------------ */
815     /**
816      * @param attributes The attributes to set.
817      */
818     public void setAttributes(Attributes attributes)
819     {
820         _attributes = attributes;
821     }
822 
823     /* ------------------------------------------------------------ */
824     public void clearAttributes()
825     {
826         _attributes.clearAttributes();
827     }
828     
829     /* ------------------------------------------------------------ */
830     /**
831      * @param classLoader The classLoader to set.
832      */
833     public void setClassLoader(ClassLoader classLoader)
834     {
835         _classLoader = classLoader;
836     }
837     
838     /* ------------------------------------------------------------ */
839     /**
840      * @param contextPath The _contextPath to set.
841      */
842     public void setContextPath(String contextPath)
843     {
844         if (contextPath!=null && contextPath.length()>1 && contextPath.endsWith("/"))
845             throw new IllegalArgumentException("ends with /");
846         _contextPath = contextPath;
847         
848         if (getServer()!=null && (getServer().isStarting() || getServer().isStarted()))
849         {
850             Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
851             for (int h=0;contextCollections!=null&& h<contextCollections.length;h++)
852                 ((ContextHandlerCollection)contextCollections[h]).mapContexts();
853         }
854     }
855     
856     /* ------------------------------------------------------------ */
857     /**
858      * @param initParams The initParams to set.
859      */
860     public void setInitParams(Map initParams)
861     {
862         if (initParams == null)
863             return;
864         _initParams = new HashMap(initParams);
865     }
866     
867     /* ------------------------------------------------------------ */
868     /**
869      * @param servletContextName The servletContextName to set.
870      */
871     public void setDisplayName(String servletContextName)
872     {
873         _displayName = servletContextName;
874         if (_classLoader!=null && _classLoader instanceof WebAppClassLoader)
875             ((WebAppClassLoader)_classLoader).setName(servletContextName);
876     }
877     
878     /* ------------------------------------------------------------ */
879     /**
880      * @return Returns the resourceBase.
881      */
882     public Resource getBaseResource()
883     {
884         if (_baseResource==null)
885             return null;
886         return _baseResource;
887     }
888 
889     /* ------------------------------------------------------------ */
890     /**
891      * @return Returns the base resource as a string.
892      */
893     public String getResourceBase()
894     {
895         if (_baseResource==null)
896             return null;
897         return _baseResource.toString();
898     }
899     
900     /* ------------------------------------------------------------ */
901     /**
902      * @param base The resourceBase to set.
903      */
904     public void setBaseResource(Resource base) 
905     {
906         _baseResource=base;
907     }
908 
909     /* ------------------------------------------------------------ */
910     /**
911      * @param resourceBase The base resource as a string.
912      */
913     public void setResourceBase(String resourceBase) 
914     {
915         try
916         {
917             setBaseResource(newResource(resourceBase));
918         }
919         catch (Exception e)
920         {
921             Log.warn(e);
922             throw new IllegalArgumentException(resourceBase);
923         }
924     }
925     /* ------------------------------------------------------------ */
926     /**
927      * @return True if alias checking is performed on resources.
928      */
929     public boolean isAliases()
930     {
931         return _aliases;
932     }
933 
934     /* ------------------------------------------------------------ */
935     /**
936      * @param aliases  alias checking performed on resources.
937      */
938     public void setAliases(boolean aliases)
939     {
940         _aliases = aliases;
941     }
942 
943     /* ------------------------------------------------------------ */
944     /**
945      * @return Returns the mimeTypes.
946      */
947     public MimeTypes getMimeTypes()
948     {
949         return _mimeTypes;
950     }
951     
952     /* ------------------------------------------------------------ */
953     /**
954      * @param mimeTypes The mimeTypes to set.
955      */
956     public void setMimeTypes(MimeTypes mimeTypes)
957     {
958         _mimeTypes = mimeTypes;
959     }
960 
961     /* ------------------------------------------------------------ */
962     /**
963      */
964     public void setWelcomeFiles(String[] files) 
965     {
966         _welcomeFiles=files;
967     }
968 
969     /* ------------------------------------------------------------ */
970     /**
971      * @return The names of the files which the server should consider to be welcome files in this context.
972      * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
973      * @see #setWelcomeFiles
974      */
975     public String[] getWelcomeFiles() 
976     {
977         return _welcomeFiles;
978     }
979 
980     /* ------------------------------------------------------------ */
981     /**
982      * @return Returns the errorHandler.
983      */
984     public ErrorHandler getErrorHandler()
985     {
986         return _errorHandler;
987     }
988 
989     /* ------------------------------------------------------------ */
990     /**
991      * @param errorHandler The errorHandler to set.
992      */
993     public void setErrorHandler(ErrorHandler errorHandler)
994     {
995         if (errorHandler!=null)
996             errorHandler.setServer(getServer());
997         if (getServer()!=null)
998             getServer().getContainer().update(this, _errorHandler, errorHandler, "errorHandler",true);
999         _errorHandler = errorHandler;
1000     }
1001     
1002     /* ------------------------------------------------------------ */
1003     public int getMaxFormContentSize()
1004     {
1005         return _maxFormContentSize;
1006     }
1007     
1008     /* ------------------------------------------------------------ */
1009     public void setMaxFormContentSize(int maxSize)
1010     {
1011         _maxFormContentSize=maxSize;
1012     }
1013 
1014 
1015     /* ------------------------------------------------------------ */
1016     /**
1017      * @return True if URLs are compacted to replace multiple '/'s with a single '/'
1018      */
1019     public boolean isCompactPath()
1020     {
1021         return _compactPath;
1022     }
1023 
1024     /* ------------------------------------------------------------ */
1025     /**
1026      * @param compactPath True if URLs are compacted to replace multiple '/'s with a single '/'
1027      */
1028     public void setCompactPath(boolean compactPath)
1029     {
1030         _compactPath=compactPath;
1031     }
1032 
1033     /* ------------------------------------------------------------ */
1034     public String toString()
1035     {
1036         
1037         return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+getBaseResource()+"}";
1038     }
1039 
1040     /* ------------------------------------------------------------ */
1041     public synchronized Class loadClass(String className)
1042         throws ClassNotFoundException
1043     {
1044         if (className==null)
1045             return null;
1046         
1047         if (_classLoader==null)
1048             return Loader.loadClass(this.getClass(), className);
1049 
1050         return _classLoader.loadClass(className);
1051     }
1052     
1053 
1054     /* ------------------------------------------------------------ */
1055     public void addLocaleEncoding(String locale,String encoding)
1056     {
1057         if (_localeEncodingMap==null)
1058             _localeEncodingMap=new HashMap();
1059         _localeEncodingMap.put(locale, encoding);
1060     }
1061     
1062     /* ------------------------------------------------------------ */
1063     /**
1064      * Get the character encoding for a locale. The full locale name is first
1065      * looked up in the map of encodings. If no encoding is found, then the
1066      * locale language is looked up. 
1067      *
1068      * @param locale a <code>Locale</code> value
1069      * @return a <code>String</code> representing the character encoding for
1070      * the locale or null if none found.
1071      */
1072     public String getLocaleEncoding(Locale locale)
1073     {
1074         if (_localeEncodingMap==null)
1075             return null;
1076         String encoding = (String)_localeEncodingMap.get(locale.toString());
1077         if (encoding==null)
1078             encoding = (String)_localeEncodingMap.get(locale.getLanguage());
1079         return encoding;
1080     }
1081     
1082     /* ------------------------------------------------------------ */
1083     /* 
1084      */
1085     public Resource getResource(String path) throws MalformedURLException
1086     {
1087         if (path==null || !path.startsWith(URIUtil.SLASH))
1088             throw new MalformedURLException(path);
1089         
1090         if (_baseResource==null)
1091             return null;
1092 
1093         try
1094         {
1095             path=URIUtil.canonicalPath(path);
1096             Resource resource=_baseResource.addPath(path);
1097             
1098             if (_aliases && resource.getAlias()!=null)
1099             {
1100                 if (resource.exists())
1101                     Log.warn("Aliased resource: "+resource+"~="+resource.getAlias());
1102                 else if (Log.isDebugEnabled())
1103                     Log.debug("Aliased resource: "+resource+"~="+resource.getAlias());
1104                 return null;
1105             }
1106             
1107             return resource;
1108         }
1109         catch(Exception e)
1110         {
1111             Log.ignore(e);
1112         }
1113                     
1114         return null;
1115     }
1116 
1117     /* ------------------------------------------------------------ */
1118     /** Convert URL to Resource
1119      * wrapper for {@link Resource#newResource(URL)} enables extensions to 
1120      * provide alternate resource implementations.
1121      */
1122     public Resource newResource(URL url) throws IOException
1123     {
1124         return Resource.newResource(url);
1125     }
1126 
1127     /* ------------------------------------------------------------ */
1128     /** Convert URL to Resource
1129      * wrapper for {@link Resource#newResource(String)} enables extensions to 
1130      * provide alternate resource implementations.
1131      */
1132     public Resource newResource(String url) throws IOException
1133     {
1134         return Resource.newResource(url);
1135     }
1136 
1137     /* ------------------------------------------------------------ */
1138     /* 
1139      */
1140     public Set getResourcePaths(String path)
1141     {           
1142         try
1143         {
1144             path=URIUtil.canonicalPath(path);
1145             Resource resource=getResource(path);
1146             
1147             if (resource!=null && resource.exists())
1148             {
1149                 if (!path.endsWith(URIUtil.SLASH))
1150                     path=path+URIUtil.SLASH;
1151                 
1152                 String[] l=resource.list();
1153                 if (l!=null)
1154                 {
1155                     HashSet set = new HashSet();
1156                     for(int i=0;i<l.length;i++)
1157                         set.add(path+l[i]);
1158                     return set;
1159                 }   
1160             }
1161         }
1162         catch(Exception e)
1163         {
1164             Log.ignore(e);
1165         }
1166         return Collections.EMPTY_SET;
1167     }
1168 
1169     
1170     /* ------------------------------------------------------------ */
1171     /** Context.
1172      * <p>
1173      * Implements {@link javax.servlet.ServletContext} from the {@link javax.servlet} package.   
1174      * </p>
1175      * @author gregw
1176      *
1177      */
1178     public class SContext implements ServletContext
1179     {
1180         /* ------------------------------------------------------------ */
1181         protected SContext()
1182         {
1183         }
1184 
1185         /* ------------------------------------------------------------ */
1186         public ContextHandler getContextHandler()
1187         {
1188             // TODO reduce visibility of this method
1189             return ContextHandler.this;
1190         }
1191 
1192         /* ------------------------------------------------------------ */
1193         /* 
1194          * @see javax.servlet.ServletContext#getContext(java.lang.String)
1195          */
1196         public ServletContext getContext(String uripath)
1197         {
1198             // TODO this is a very poor implementation!
1199             // TODO move this to Server
1200             ContextHandler context=null;
1201             Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
1202             for (int i=0;i<handlers.length;i++)
1203             {
1204                 if (handlers[i]==null || !handlers[i].isStarted())
1205                     continue;
1206                 ContextHandler ch = (ContextHandler)handlers[i];
1207                 String context_path=ch.getContextPath();
1208                 if (uripath.equals(context_path) || (uripath.startsWith(context_path)&&uripath.charAt(context_path.length())=='/'))
1209                 {
1210                     if (context==null || context_path.length()>context.getContextPath().length())
1211                         context=ch;
1212                 }
1213             }
1214             
1215             if (context!=null)
1216                 return context._scontext;
1217             return null;
1218         }
1219 
1220         /* ------------------------------------------------------------ */
1221         /* 
1222          * @see javax.servlet.ServletContext#getMajorVersion()
1223          */
1224         public int getMajorVersion()
1225         {
1226             return 3;
1227         }
1228 
1229         /* ------------------------------------------------------------ */
1230         /* 
1231          * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
1232          */
1233         public String getMimeType(String file)
1234         {
1235             if (_mimeTypes==null)
1236                 return null;
1237             Buffer mime = _mimeTypes.getMimeByExtension(file);
1238             if (mime!=null)
1239                 return mime.toString();
1240             return null;
1241         }
1242 
1243         /* ------------------------------------------------------------ */
1244         /* 
1245          * @see javax.servlet.ServletContext#getMinorVersion()
1246          */
1247         public int getMinorVersion()
1248         {
1249             return 0;
1250         }
1251 
1252         /* ------------------------------------------------------------ */
1253         /* 
1254          * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
1255          */
1256         public RequestDispatcher getNamedDispatcher(String name)
1257         {
1258             return null;
1259         }
1260 
1261         /* ------------------------------------------------------------ */
1262         /* 
1263          * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
1264          */
1265         public String getRealPath(String path)
1266         {
1267             try
1268             {
1269                 Resource resource=ContextHandler.this.getResource(path);
1270                 File file =resource.getFile();
1271                 if (file!=null)
1272                     return file.getCanonicalPath();
1273             }
1274             catch (Exception e)
1275             {
1276                 Log.ignore(e);
1277             }
1278             
1279             return null;
1280         }
1281 
1282         /* ------------------------------------------------------------ */
1283         /* 
1284          * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
1285          */
1286         public RequestDispatcher getRequestDispatcher(String uriInContext)
1287         {
1288             return null;
1289         }
1290 
1291         /* ------------------------------------------------------------ */
1292         public URL getResource(String path) throws MalformedURLException
1293         {
1294             Resource resource=ContextHandler.this.getResource(path);
1295             if (resource!=null && resource.exists())
1296                 return resource.getURL();
1297             return null;
1298         }
1299         
1300         /* ------------------------------------------------------------ */
1301         /* 
1302          * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
1303          */
1304         public InputStream getResourceAsStream(String path)
1305         {
1306             try
1307             {
1308                 URL url=getResource(path);
1309                 if (url==null)
1310                     return null;
1311                 return url.openStream();
1312             }
1313             catch(Exception e)
1314             {
1315                 Log.ignore(e);
1316                 return null;
1317             }
1318         }
1319 
1320         /* ------------------------------------------------------------ */
1321         /* 
1322          * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
1323          */
1324         public Set getResourcePaths(String path)
1325         {            
1326             return ContextHandler.this.getResourcePaths(path);
1327         }
1328 
1329         /* ------------------------------------------------------------ */
1330         /* 
1331          * @see javax.servlet.ServletContext#getServerInfo()
1332          */
1333         public String getServerInfo()
1334         {
1335             return "jetty/"+getServer().getVersion();
1336         }
1337 
1338         /* ------------------------------------------------------------ */
1339         /* 
1340          * @see javax.servlet.ServletContext#getServlet(java.lang.String)
1341          */
1342         public Servlet getServlet(String name) throws ServletException
1343         {
1344             return null;
1345         }
1346 
1347         /* ------------------------------------------------------------ */
1348         /* 
1349          * @see javax.servlet.ServletContext#getServletNames()
1350          */
1351         public Enumeration getServletNames()
1352         {
1353             return Collections.enumeration(Collections.EMPTY_LIST);
1354         }
1355 
1356         /* ------------------------------------------------------------ */
1357         /* 
1358          * @see javax.servlet.ServletContext#getServlets()
1359          */
1360         public Enumeration getServlets()
1361         {
1362             return Collections.enumeration(Collections.EMPTY_LIST);
1363         }
1364 
1365         /* ------------------------------------------------------------ */
1366         /* 
1367          * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
1368          */
1369         public void log(Exception exception, String msg)
1370         {
1371             _logger.warn(msg,exception);
1372         }
1373 
1374         /* ------------------------------------------------------------ */
1375         /* 
1376          * @see javax.servlet.ServletContext#log(java.lang.String)
1377          */
1378         public void log(String msg)
1379         {
1380             _logger.info(msg, null, null);
1381         }
1382 
1383         /* ------------------------------------------------------------ */
1384         /* 
1385          * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
1386          */
1387         public void log(String message, Throwable throwable)
1388         {
1389             _logger.warn(message,throwable);
1390         }
1391 
1392         /* ------------------------------------------------------------ */
1393         /* 
1394          * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
1395          */
1396         public String getInitParameter(String name)
1397         {
1398             return ContextHandler.this.getInitParameter(name);
1399         }
1400 
1401         /* ------------------------------------------------------------ */
1402         /* 
1403          * @see javax.servlet.ServletContext#getInitParameterNames()
1404          */
1405         public Enumeration getInitParameterNames()
1406         {
1407             return ContextHandler.this.getInitParameterNames();
1408         }
1409 
1410         /* ------------------------------------------------------------ */
1411         /* 
1412          * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
1413          */
1414         public synchronized Object getAttribute(String name)
1415         {
1416             Object o = ContextHandler.this.getAttribute(name);
1417             if (o==null && _contextAttributes!=null)
1418                 o=_contextAttributes.getAttribute(name);
1419             return o;
1420         }
1421 
1422         /* ------------------------------------------------------------ */
1423         /* 
1424          * @see javax.servlet.ServletContext#getAttributeNames()
1425          */
1426         public synchronized Enumeration getAttributeNames()
1427         {
1428             HashSet set = new HashSet();
1429             if (_contextAttributes!=null)
1430             {
1431             	Enumeration e = _contextAttributes.getAttributeNames();
1432             	while(e.hasMoreElements())
1433             		set.add(e.nextElement());
1434             }
1435             Enumeration e = ContextHandler.this.getAttributeNames();
1436             while(e.hasMoreElements())
1437                 set.add(e.nextElement());
1438             
1439             return Collections.enumeration(set);
1440         }
1441 
1442         /* ------------------------------------------------------------ */
1443         /* 
1444          * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
1445          */
1446         public synchronized void setAttribute(String name, Object value)
1447         {
1448             if (_contextAttributes==null)
1449             {
1450             	// Set it on the handler
1451             	ContextHandler.this.setAttribute(name, value);
1452                 return;
1453             }
1454             
1455             Object old_value=_contextAttributes==null?null:_contextAttributes.getAttribute(name);
1456             
1457             if (value==null)
1458                 _contextAttributes.removeAttribute(name);
1459             else
1460                 _contextAttributes.setAttribute(name,value);
1461             
1462             if (_contextAttributeListeners!=null)
1463             {
1464                 ServletContextAttributeEvent event =
1465                     new ServletContextAttributeEvent(_scontext,name, old_value==null?value:old_value);
1466 
1467                 for(int i=0;i<LazyList.size(_contextAttributeListeners);i++)
1468                 {
1469                     ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
1470                     
1471                     if (old_value==null)
1472                         l.attributeAdded(event);
1473                     else if (value==null)
1474                         l.attributeRemoved(event);
1475                     else
1476                         l.attributeReplaced(event);
1477                 }
1478             }
1479         }
1480 
1481         /* ------------------------------------------------------------ */
1482         /* 
1483          * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
1484          */
1485         public synchronized void removeAttribute(String name)
1486         {
1487             if (_contextAttributes==null)
1488             {
1489             	// Set it on the handler
1490             	ContextHandler.this.removeAttribute(name);
1491                 return;
1492             }
1493             
1494             Object old_value=_contextAttributes.getAttribute(name);
1495             _contextAttributes.removeAttribute(name);
1496             if (old_value!=null)
1497             {
1498                 if (_contextAttributeListeners!=null)
1499                 {
1500                     ServletContextAttributeEvent event =
1501                         new ServletContextAttributeEvent(_scontext,name, old_value);
1502 
1503                     for(int i=0;i<LazyList.size(_contextAttributeListeners);i++)
1504                         ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
1505                 }
1506             }
1507         }
1508 
1509         /* ------------------------------------------------------------ */
1510         /* 
1511          * @see javax.servlet.ServletContext#getServletContextName()
1512          */
1513         public String getServletContextName()
1514         {
1515             String name = ContextHandler.this.getDisplayName();
1516             if (name==null)
1517                 name=ContextHandler.this.getContextPath();
1518             return name;
1519         }
1520 
1521         /* ------------------------------------------------------------ */
1522         /**
1523          * @return Returns the _contextPath.
1524          */
1525         public String getContextPath()
1526         {
1527             if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
1528                 return "";
1529             
1530             return _contextPath;
1531         }
1532 
1533         /* ------------------------------------------------------------ */
1534         public String toString()
1535         {
1536             return "ServletContext@"+Integer.toHexString(hashCode())+"{"+(getContextPath().equals("")?URIUtil.SLASH:getContextPath())+","+getBaseResource()+"}";
1537         }
1538 
1539         /* ------------------------------------------------------------ */
1540         /* (non-Javadoc)
1541          * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.String, java.lang.String, java.util.Map)
1542          */
1543         public void addFilter(String filterName, String description, String className, Map<String, String> initParameters)
1544         {
1545         }
1546 
1547         /* ------------------------------------------------------------ */
1548         /* (non-Javadoc)
1549          * @see javax.servlet.ServletContext#addFilterMapping(java.lang.String, java.lang.String[], java.lang.String[], java.util.EnumSet, boolean)
1550          */
1551         public void addFilterMapping(String filterName, String[] urlPatterns, String[] servletNames, EnumSet<DispatcherType> dispatcherTypes,
1552                 boolean isMatchAfter)
1553         {
1554         }
1555 
1556         /* ------------------------------------------------------------ */
1557         /* (non-Javadoc)
1558          * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.String, java.lang.String, java.util.Map, int)
1559          */
1560         public void addServlet(String servletName, String description, String className, Map<String, String> initParameters, int loadOnStartup)
1561         {
1562         }
1563 
1564         /* ------------------------------------------------------------ */
1565         /* (non-Javadoc)
1566          * @see javax.servlet.ServletContext#addServletMapping(java.lang.String, java.lang.String[])
1567          */
1568         public void addServletMapping(String servletName, String[] urlPatterns)
1569         {
1570         }
1571         
1572     }
1573 
1574     /* ------------------------------------------------------------ */
1575     private String normalizeHostname( String host )
1576     {
1577         if ( host == null )
1578             return null;
1579         
1580         if ( host.endsWith( "." ) )
1581             return host.substring( 0, host.length() -1);
1582       
1583             return host;
1584     }
1585 
1586 
1587 }