Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/mortbay/http/HttpContext.java


1   // ========================================================================
2   // Copyright (c) 2000 Mort Bay Consulting (Australia) Pty. Ltd.
3   // $Id: HttpContext.java,v 1.105 2003/11/22 16:06:01 gregwilkins Exp $
4   // ========================================================================
5   
6   package org.mortbay.http;
7   
8   import java.io.File;
9   import java.io.IOException;
10  import java.io.Serializable;
11  import java.net.InetAddress;
12  import java.net.MalformedURLException;
13  import java.net.Socket;
14  import java.net.URL;
15  import java.net.URLClassLoader;
16  import java.net.UnknownHostException;
17  import java.security.Permission;
18  import java.security.PermissionCollection;
19  import java.security.Permissions;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.ResourceBundle;
31  import java.util.StringTokenizer;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.mortbay.http.handler.ErrorPageHandler;
36  import org.mortbay.util.CachedResource;
37  import org.mortbay.util.IO;
38  import org.mortbay.util.LazyList;
39  import org.mortbay.util.LifeCycle;
40  import org.mortbay.util.LogSupport;
41  import org.mortbay.util.MultiException;
42  import org.mortbay.util.Resource;
43  import org.mortbay.util.StringUtil;
44  import org.mortbay.util.URI;
45  
46  
47  /* ------------------------------------------------------------ */
48  /** Context for a collection of HttpHandlers.
49   * HTTP Context provides an ordered container for HttpHandlers
50   * that share the same path prefix, filebase, resourcebase and/or
51   * classpath.
52   * <p>
53   * A HttpContext is analagous to a ServletContext in the
54   * Servlet API, except that it may contain other types of handler
55   * other than servlets.
56   * <p>
57   * A ClassLoader is created for the context and it uses
58   * Thread.currentThread().getContextClassLoader(); as it's parent loader.
59   * The class loader is initialized during start(), when a derived
60   * context calls initClassLoader() or on the first call to loadClass()
61   * <p>
62   *
63   * <B>Note. that order is important when configuring a HttpContext.
64   * For example, if resource serving is enabled before servlets, then resources
65   * take priority.</B>
66   *
67   * @see HttpServer
68   * @see HttpHandler
69   * @see org.mortbay.jetty.servlet.ServletHttpContext
70   * @version $Id: HttpContext.java,v 1.105 2003/11/22 16:06:01 gregwilkins Exp $
71   * @author Greg Wilkins (gregw)
72   */
73  public class HttpContext implements LifeCycle,
74                                      HttpHandler,
75                                      Serializable
76  {
77      private static Log log = LogFactory.getLog(HttpContext.class);
78  
79      /* ------------------------------------------------------------ */
80      /** File class path attribute.
81       * If this name is set as a context init parameter, then the attribute
82       * name given will be used to set the file classpath for the context as a
83       * context attribute.
84       */
85      public final static String __fileClassPathAttr=
86          "org.mortbay.http.HttpContext.FileClassPathAttribute";
87  
88      public final static String __ErrorHandler=
89          "org.mortbay.http.ErrorHandler";
90  
91      /* ------------------------------------------------------------ */
92      /* ------------------------------------------------------------ */
93      private final static Map __dftMimeMap = new HashMap();
94      private final static Map __encodings = new HashMap();
95      static
96      {
97          ResourceBundle mime = ResourceBundle.getBundle("org/mortbay/http/mime");
98          Enumeration i = mime.getKeys();
99          while(i.hasMoreElements())
100         {
101             String ext = (String)i.nextElement();
102             __dftMimeMap.put(ext,mime.getString(ext));
103         }
104         ResourceBundle encoding = ResourceBundle.getBundle("org/mortbay/http/encoding");
105         i = encoding.getKeys();
106         while(i.hasMoreElements())
107         {
108             String type = (String)i.nextElement();
109             __encodings.put(type,encoding.getString(type));
110         }
111     }
112 
113     /* ------------------------------------------------------------ */
114     /* ------------------------------------------------------------ */
115     // These attributes are serialized by WebApplicationContext, which needs
116     // to be updated if you add to these
117     private String _contextPath;
118     private List _vhosts=new ArrayList(2);
119     private List _hosts=new ArrayList(2);
120     private List _handlers=new ArrayList(3);
121     private Map _attributes = new HashMap(3);
122     private boolean _redirectNullPath=true;
123     private int _maxCachedFileSize =100*1024;
124     private int _maxCacheSize =1024*1024;
125     private boolean _statsOn=false;
126     private PermissionCollection _permissions;
127     private boolean _classLoaderJava2Compliant;
128 
129     /* ------------------------------------------------------------ */
130     private String _contextName;
131     private String _classPath;
132     private Map _initParams = new HashMap(11);
133     private UserRealm _userRealm;
134     private String _realmName;
135     private PathMap _constraintMap=new PathMap();
136     private Authenticator _authenticator;
137     private RequestLog _requestLog;
138 
139     private Resource _resourceBase;
140     private Map _mimeMap;
141     private Map _encodingMap;
142 
143     private String[] _welcomes=
144     {
145         "welcome.html",
146         "index.html",
147         "index.htm",
148         "index.jsp"
149     };
150 
151 
152     /* ------------------------------------------------------------ */
153     private transient boolean _started;
154     private transient ClassLoader _parent;
155     private transient ClassLoader _loader;
156     private transient HttpServer _httpServer;
157     private transient File _tmpDir;
158     private transient HttpHandler[] _handlersArray;
159     private transient String[] _vhostsArray;
160 
161     protected transient Map _cache=new HashMap();
162     protected transient int _cacheSize;
163     protected transient CachedMetaData _mostRecentlyUsed;
164     protected transient CachedMetaData _leastRecentlyUsed;
165 
166     /* ------------------------------------------------------------ */
167     transient Object _statsLock=new Object[0];
168     transient long _statsStartedAt;
169     transient int _requests;
170     transient int _requestsActive;
171     transient int _requestsActiveMax;
172     transient int _responses1xx; // Informal
173     transient int _responses2xx; // Success
174     transient int _responses3xx; // Redirection
175     transient int _responses4xx; // Client Error
176     transient int _responses5xx; // Server Error
177 
178 
179     /* ------------------------------------------------------------ */
180     /** Constructor.
181      */
182     public HttpContext()
183     {
184         setAttribute(__ErrorHandler,  new ErrorPageHandler()); 
185     }
186 
187     /* ------------------------------------------------------------ */
188     /** Constructor.
189      * @param httpServer
190      * @param contextPathSpec
191      */
192     public HttpContext(HttpServer httpServer,String contextPathSpec)
193     {
194         this();
195         setHttpServer(httpServer);
196         setContextPath(contextPathSpec);
197     }
198 
199     /* ------------------------------------------------------------ */
200     private void readObject(java.io.ObjectInputStream in)
201         throws IOException, ClassNotFoundException
202     {
203         in.defaultReadObject();
204         _statsLock=new Object[0];
205         _cache=new HashMap();
206         getHandlers();
207         for (int i=0;i<_handlersArray.length;i++)
208             _handlersArray[i].initialize(this);
209     }
210 
211     /* ------------------------------------------------------------ */
212     /** Get the ThreadLocal HttpConnection.
213      * Get the HttpConnection for current thread, if any.  This method is
214      * not static in order to control access.
215      * @return HttpConnection for this thread.
216      */
217     public HttpConnection getHttpConnection()
218     {
219         return HttpConnection.getHttpConnection();
220     }
221 
222     /* ------------------------------------------------------------ */
223     void setHttpServer(HttpServer httpServer)
224     {
225         _httpServer=httpServer;
226         _contextName=null;
227     }
228 
229     /* ------------------------------------------------------------ */
230     public HttpServer getHttpServer()
231     {
232         return _httpServer;
233     }
234 
235     /* ------------------------------------------------------------ */
236     public static String canonicalContextPathSpec(String contextPathSpec)
237     {
238         // check context path
239         if (contextPathSpec==null ||
240             contextPathSpec.indexOf(',')>=0 ||
241             contextPathSpec.startsWith("*"))
242             throw new IllegalArgumentException ("Illegal context spec:"+contextPathSpec);
243 
244         if(!contextPathSpec.startsWith("/"))
245       contextPathSpec='/'+contextPathSpec;
246 
247         if (contextPathSpec.length()>1)
248         {
249             if (contextPathSpec.endsWith("/"))
250                 contextPathSpec+="*";
251             else if (!contextPathSpec.endsWith("/*"))
252                 contextPathSpec+="/*";
253         }
254 
255         return contextPathSpec;
256     }
257 
258     /* ------------------------------------------------------------ */
259     public void setContextPath(String contextPathSpec)
260     {
261         if (_httpServer!=null)
262             _httpServer.removeMappings(this);
263 
264         contextPathSpec=canonicalContextPathSpec(contextPathSpec);
265 
266         if (contextPathSpec.length()>1)
267             _contextPath=contextPathSpec.substring(0,contextPathSpec.length()-2);
268         else
269             _contextPath="/";
270 
271         _contextName=null;
272 
273         if (_httpServer!=null)
274             _httpServer.addMappings(this);
275     }
276 
277 
278     /* ------------------------------------------------------------ */
279     /**
280      * @return The context prefix
281      */
282     public String getContextPath()
283     {
284         return _contextPath;
285     }
286 
287 
288     /* ------------------------------------------------------------ */
289     /** Add a virtual host alias to this context.
290      * @see #setVirtualHosts
291      * @param hostname A hostname. A null host name means any hostname is
292      * acceptable. Host names may String representation of IP addresses.
293      */
294     public void addVirtualHost(String hostname)
295     {
296         // Note that null hosts are also added.
297         if (!_vhosts.contains(hostname))
298         {
299             _vhosts.add(hostname);
300             _contextName=null;
301 
302             if (_httpServer!=null)
303             {
304                 if (_vhosts.size()==1)
305                     _httpServer.removeMapping(null,this);
306                 _httpServer.addMapping(hostname,this);
307             }
308             _vhostsArray=null;
309         }
310     }
311 
312     /* ------------------------------------------------------------ */
313     /** remove a virtual host alias to this context.
314      * @see #setVirtualHosts
315      * @param hostname A hostname. A null host name means any hostname is
316      * acceptable. Host names may String representation of IP addresses.
317      */
318     public void removeVirtualHost(String hostname)
319     {
320         // Note that null hosts are also added.
321         if (_vhosts.remove(hostname))
322         {
323             _contextName=null;
324             if (_httpServer!=null)
325             {
326                 _httpServer.removeMapping(hostname,this);
327                 if (_vhosts.size()==0)
328                     _httpServer.addMapping(null,this);
329             }
330             _vhostsArray=null;
331         }
332     }
333 
334     /* ------------------------------------------------------------ */
335     /** Set the virtual hosts for the context.
336      * Only requests that have a matching host header or fully qualified
337      * URL will be passed to that context with a virtual host name.
338      * A context with no virtual host names or a null virtual host name is
339      * available to all requests that are not served by a context with a
340      * matching virtual host name.
341      * @param hosts Array of virtual hosts that this context responds to. A
342      * null host name or null/empty array means any hostname is acceptable.
343      * Host names may String representation of IP addresses.
344      */
345     public void setVirtualHosts(String[] hosts)
346     {
347         List old = new ArrayList(_vhosts);
348 
349         for (int i=0;i<hosts.length;i++)
350         {
351             boolean existing=old.remove(hosts[i]);
352             if (!existing)
353                 addVirtualHost(hosts[i]);
354         }
355 
356         for (int i=0;i<old.size();i++)
357             removeVirtualHost((String)old.get(i));
358     }
359 
360     /* ------------------------------------------------------------ */
361     /** Set the hosts for the context.
362      * Set the real hosts that this context will accept requests for.
363      * If not null or empty, then only requests from HttpListeners for hosts
364      * in this array are accepted by this context. 
365      * Unlike virutal hosts, this value is not used by HttpServer for
366      * matching a request to a context.
367      */
368     public void setHosts(String[] hosts)
369         throws UnknownHostException
370     {
371         if (hosts==null || hosts.length==0)
372             _hosts=null;
373         else
374         {
375             _hosts=new ArrayList();
376             for (int i=0;i<hosts.length;i++)
377                 if (hosts[i]!=null)
378                     _hosts.add(InetAddress.getByName(hosts[i]));
379         }
380         
381     }
382 
383     /* ------------------------------------------------------------ */
384     /** Get the hosts for the context.
385      */
386     public String[] getHosts()
387     {
388         if (_hosts==null || _hosts.size()==0)
389             return null;
390         String[] hosts=new String[_hosts.size()];
391         for (int i=0;i<hosts.length;i++)
392         {
393             InetAddress a = (InetAddress)_hosts.get(i);
394             if (a!=null)
395                 hosts[i]=a.getHostName();
396         }
397         return hosts;
398     }
399 
400 
401     /* ------------------------------------------------------------ */
402     /** Get the virtual hosts for the context.
403      * Only requests that have a matching host header or fully qualified
404      * URL will be passed to that context with a virtual host name.
405      * A context with no virtual host names or a null virtual host name is
406      * available to all requests that are not served by a context with a
407      * matching virtual host name.
408      * @return Array of virtual hosts that this context responds to. A
409      * null host name or empty array means any hostname is acceptable.
410      * Host names may be String representation of IP addresses.
411      */
412     public String[] getVirtualHosts()
413     {
414         if (_vhostsArray!=null)
415             return _vhostsArray;
416         if (_vhosts==null)
417             _vhostsArray=new String[0];
418         else
419         {
420             _vhostsArray=new String[_vhosts.size()];
421             _vhostsArray=(String[])_vhosts.toArray(_vhostsArray);
422         }
423         return _vhostsArray;
424     }
425 
426 
427     /* ------------------------------------------------------------ */
428     public void setHandlers(HttpHandler[] handlers)
429     {
430         List old = new ArrayList(_handlers);
431 
432         for (int i=0;i<handlers.length;i++)
433         {
434             boolean existing=old.remove(handlers[i]);
435             if (!existing)
436                 addHandler(handlers[i]);
437         }
438 
439         for (int i=0;i<old.size();i++)
440             removeHandler((HttpHandler)old.get(i));
441     }
442 
443     /* ------------------------------------------------------------ */
444     /** Get all handlers.
445      * @return List of all HttpHandlers
446      */
447     public HttpHandler[] getHandlers()
448     {
449         if (_handlersArray!=null)
450             return _handlersArray;
451         if (_handlers==null)
452             _handlersArray=new HttpHandler[0];
453         else
454         {
455             _handlersArray=new HttpHandler[_handlers.size()];
456             _handlersArray=(HttpHandler[])_handlers.toArray(_handlersArray);
457         }
458         return _handlersArray;
459     }
460 
461 
462     /* ------------------------------------------------------------ */
463     /** Add a handler.
464      * @param i The position in the handler list
465      * @param handler The handler.
466      */
467     public synchronized void addHandler(int i,HttpHandler handler)
468     {
469         _handlers.add(i,handler);
470         _handlersArray=null;
471 
472         HttpContext context = handler.getHttpContext();
473         if (context==null)
474             handler.initialize(this);
475         else if (context!=this)
476             throw new IllegalArgumentException("Handler already initialized in another HttpContext");
477     }
478 
479     /* ------------------------------------------------------------ */
480     /** Add a HttpHandler to the context.
481      * @param handler
482      */
483     public synchronized void addHandler(HttpHandler handler)
484     {
485         addHandler(_handlers.size(),handler);
486     }
487 
488     /* ------------------------------------------------------------ */
489     /** Get handler index.
490      * @param handler instance
491      * @return Index of handler in context or -1 if not found.
492      */
493     public int getHandlerIndex(HttpHandler handler)
494     {
495         for (int h=0;h<_handlers.size();h++)
496         {
497             if ( handler == _handlers.get(h))
498                 return h;
499         }
500         return -1;
501     }
502 
503     /* ------------------------------------------------------------ */
504     /** Get a handler by class.
505      * @param handlerClass
506      * @return The first handler that is an instance of the handlerClass
507      */
508     public synchronized HttpHandler getHandler(Class handlerClass)
509     {
510         for (int h=0;h<_handlers.size();h++)
511         {
512             HttpHandler handler = (HttpHandler)_handlers.get(h);
513             if (handlerClass.isInstance(handler))
514                 return handler;
515         }
516         return null;
517     }
518 
519     /* ------------------------------------------------------------ */
520     /** Remove a handler.
521      * The handler must be stopped before being removed.
522      * @param i index of handler
523      */
524     public synchronized HttpHandler removeHandler(int i)
525     {
526         HttpHandler handler = _handlersArray[i];
527         if (handler.isStarted())
528             try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
529         _handlers.remove(i);
530         _handlersArray=null;
531         return handler;
532     }
533 
534     /* ------------------------------------------------------------ */
535     /** Remove a handler.
536      * The handler must be stopped before being removed.
537      */
538     public synchronized void removeHandler(HttpHandler handler)
539     {
540         if (handler.isStarted())
541             try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
542         _handlers.remove(handler);
543         _handlersArray=null;
544     }
545 
546 
547     /* ------------------------------------------------------------ */
548     /** Set context init parameter.
549      * Init Parameters differ from attributes as they can only
550      * have string values, servlets cannot set them and they do
551      * not have a package scoped name space.
552      * @param param param name
553      * @param value param value or null
554      */
555     public void setInitParameter(String param, String value)
556     {
557         _initParams.put(param,value);
558     }
559 
560     /* ------------------------------------------------------------ */
561     /** Get context init parameter.
562      * @param param param name
563      * @return param value or null
564      */
565     public String getInitParameter(String param)
566     {
567         return (String)_initParams.get(param);
568     }
569 
570     /* ------------------------------------------------------------ */
571     /** Get context init parameter.
572      * @return Enumeration of names
573      */
574     public Enumeration getInitParameterNames()
575     {
576         return Collections.enumeration(_initParams.keySet());
577     }
578 
579     /* ------------------------------------------------------------ */
580     /** Set a context attribute.
581      * Attributes are cleared when the context is stopped.
582      * @param name attribute name
583      * @param value attribute value
584      */
585     public synchronized void setAttribute(String name, Object value)
586     {
587         _attributes.put(name,value);
588     }
589 
590     /* ------------------------------------------------------------ */
591     /**
592      * @param name attribute name
593      * @return attribute value or null
594      */
595     public Object getAttribute(String name)
596     {
597         return _attributes.get(name);
598     }
599 
600     /* ------------------------------------------------------------ */
601     /**
602      */
603     public Map getAttributes()
604     {
605         return _attributes;
606     }
607 
608     /* ------------------------------------------------------------ */
609     /**
610      */
611     public void setAttributes(Map attributes)
612     {
613         _attributes=attributes;
614     }
615 
616     /* ------------------------------------------------------------ */
617     /**
618      * @return enumaration of names.
619      */
620     public Enumeration getAttributeNames()
621     {
622         return Collections.enumeration(_attributes.keySet());
623     }
624 
625     /* ------------------------------------------------------------ */
626     /**
627      * @param name attribute name
628      */
629     public synchronized void removeAttribute(String name)
630     {
631         _attributes.remove(name);
632     }
633 
634 
635 
636     /* ------------------------------------------------------------ */
637     /** Set the Resource Base.
638      * The base resource is the Resource to use as a relative base
639      * for all context resources. The ResourceBase attribute is a
640      * string version of the baseResource.
641      * If a relative file is passed, it is converted to a file
642      * URL based on the current working directory.
643      * @return The file or URL to use as the base for all resources
644      * within the context.
645      */
646     public String getResourceBase()
647     {
648         if (_resourceBase==null)
649             return null;
650         return _resourceBase.toString();
651     }
652 
653     /* ------------------------------------------------------------ */
654     /** Set the Resource Base.
655      * The base resource is the Resource to use as a relative base
656      * for all context resources. The ResourceBase attribute is a
657      * string version of the baseResource.
658      * If a relative file is passed, it is converted to a file
659      * URL based on the current working directory.
660      * @param resourceBase A URL prefix or directory name.
661      */
662     public void setResourceBase(String resourceBase)
663     {
664         try{
665             _resourceBase=Resource.newResource(resourceBase);
666             if(log.isDebugEnabled())log.debug("resourceBase="+_resourceBase+" for "+this);
667         }
668         catch(IOException e)
669         {
670             log.debug(LogSupport.EXCEPTION,e);
671             throw new IllegalArgumentException(resourceBase+":"+e.toString());
672         }
673     }
674 
675 
676     /* ------------------------------------------------------------ */
677     /** Get the base resource.
678      * The base resource is the Resource to use as a relative base
679      * for all context resources. The ResourceBase attribute is a
680      * string version of the baseResource.
681      * @return The resourceBase as a Resource instance
682      */
683     public Resource getBaseResource()
684     {
685         return _resourceBase;
686     }
687 
688     /* ------------------------------------------------------------ */
689     /** Set the base resource.
690      * The base resource is the Resource to use as a relative base
691      * for all context resources. The ResourceBase attribute is a
692      * string version of the baseResource.
693      * @param base The resourceBase as a Resource instance
694      */
695     public void setBaseResource(Resource base)
696     {
697         _resourceBase=base;
698     }
699 
700 
701     /* ------------------------------------------------------------ */
702     public int getMaxCachedFileSize()
703     {
704         return _maxCachedFileSize;
705     }
706 
707     /* ------------------------------------------------------------ */
708     public void setMaxCachedFileSize(int maxCachedFileSize)
709     {
710         _maxCachedFileSize = maxCachedFileSize;
711         _cache.clear();
712     }
713 
714     /* ------------------------------------------------------------ */
715     public int getMaxCacheSize()
716     {
717         return _maxCacheSize;
718     }
719 
720     /* ------------------------------------------------------------ */
721     public void setMaxCacheSize(int maxCacheSize)
722     {
723         _maxCacheSize = maxCacheSize;
724         _cache.clear();
725     }
726 
727     /* ------------------------------------------------------------ */
728     public void flushCache()
729     {
730         _cache.clear();
731         System.gc();
732     }
733 
734     /* ------------------------------------------------------------ */
735     public String[] getWelcomeFiles()
736     {
737         return _welcomes;
738     }
739 
740     /* ------------------------------------------------------------ */
741     public void setWelcomeFiles(String[] welcomes)
742     {
743         if (welcomes==null)
744             _welcomes=new String[0];
745         else
746             _welcomes=welcomes;
747     }
748 
749     /* ------------------------------------------------------------ */
750     public void addWelcomeFile(String welcomeFile)
751     {
752         if (welcomeFile.startsWith("/") ||
753             welcomeFile.startsWith(java.io.File.separator) ||
754             welcomeFile.endsWith("/") ||
755             welcomeFile.endsWith(java.io.File.separator))
756             log.warn("Invalid welcome file: "+welcomeFile);
757         List list = new ArrayList(Arrays.asList(_welcomes));
758         list.add(welcomeFile);
759         _welcomes=(String[])list.toArray(_welcomes);
760     }
761 
762     /* ------------------------------------------------------------ */
763     public void removeWelcomeFile(String welcomeFile)
764     {
765         List list = new ArrayList(Arrays.asList(_welcomes));
766         list.remove(welcomeFile);
767         _welcomes=(String[])list.toArray(_welcomes);
768     }
769 
770     /* ------------------------------------------------------------ */
771     /** Get a resource from the context.
772      * Cached Resources are returned if the resource fits within the LRU
773      * cache.  Directories may have CachedResources returned, but the
774      * caller must use the CachedResource.setCachedData method to set the
775      * formatted directory content.
776      *
777      * @param pathInContext
778      * @return Resource
779      * @exception IOException
780      */
781     public Resource getResource(String pathInContext)
782         throws IOException
783     {
784         if (_resourceBase==null)
785             return null;
786 
787         Resource resource=null;
788 
789         // Cache operations
790         synchronized(_cache)
791         {
792             // Look for it in the cache
793             CachedResource cached = (CachedResource)_cache.get(pathInContext);
794             if (cached!=null)
795             {
796                 if(LogSupport.isTraceEnabled(log))log.trace("CACHE HIT: "+cached);
797                 CachedMetaData cmd = (CachedMetaData)cached.getAssociate();
798                 if (cmd!=null && cmd.isValid())
799                     return cached;
800             }
801 
802             // Make the resource
803             resource=_resourceBase.addPath(_resourceBase.encode(pathInContext));
804             if(LogSupport.isTraceEnabled(log))log.trace("CACHE MISS: "+resource);
805             if (resource==null)
806                 return null;
807 
808             
809             // Check for file aliasing
810             if (resource.getAlias()!=null)
811             {
812                 log.warn("Alias request of '"+resource.getAlias()+
813                              "' for '"+resource+"'");
814                 return null;
815             }
816 
817             // Is it an existing file?
818             long len = resource.length();
819             if (resource.exists())
820             {
821                 // Is it badly named?
822                 if (!resource.isDirectory() && pathInContext.endsWith("/"))
823                     return null;
824 
825                 // Guess directory length.
826                 if (resource.isDirectory())
827                 {
828                     if (resource.list()!=null)
829                         len=resource.list().length*100;
830                     else
831                         len=0;
832                 }
833 
834                 // Is it cacheable?
835                 if (len>0 && len<_maxCachedFileSize && len<_maxCacheSize)
836                 {
837                     int needed=_maxCacheSize-(int)len;
838                     while(_cacheSize>needed)
839                         _leastRecentlyUsed.invalidate();
840 
841                     cached=resource.cache();
842                     if(LogSupport.isTraceEnabled(log))log.trace("CACHED: "+resource);
843                     new CachedMetaData(cached,pathInContext);
844                     return cached;
845                 }
846             }
847         }
848 
849         // Non cached response
850         new ResourceMetaData(resource);
851         return resource;
852     }
853 
854     /* ------------------------------------------------------------ */
855     public String getWelcomeFile(Resource resource)
856         throws IOException
857     {
858         if (!resource.isDirectory())
859             return null;
860 
861         for (int i=0;i<_welcomes.length;i++)
862         {
863             Resource welcome=resource.addPath(_welcomes[i]);
864             if (welcome.exists())
865                 return _welcomes[i];
866         }
867 
868         return null;
869     }
870 
871 
872     /* ------------------------------------------------------------ */
873     public synchronized Map getMimeMap()
874     {
875         return _mimeMap;
876     }
877 
878     /* ------------------------------------------------------------ */
879     /**
880      * Also sets the org.mortbay.http.mimeMap context attribute
881      * @param mimeMap
882      */
883     public void setMimeMap(Map mimeMap)
884     {
885         _mimeMap = mimeMap;
886     }
887 
888     /* ------------------------------------------------------------ */
889     /** Get the MIME type by filename extension.
890      * @param filename A file name
891      * @return MIME type matching the longest dot extension of the
892      * file name.
893      */
894     public String getMimeByExtension(String filename)
895     {
896         String type=null;
897 
898         if (filename!=null)
899         {
900             int i=-1;
901             while(type==null)
902             {
903                 i=filename.indexOf(".",i+1);
904 
905                 if (i<0 || i>=filename.length())
906                     break;
907 
908                 String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
909                 if (_mimeMap!=null)
910                     type = (String)_mimeMap.get(ext);
911                 if (type==null)
912                     type=(String)__dftMimeMap.get(ext);
913             }
914         }
915 
916         if (type==null)
917         {
918             if (_mimeMap!=null)
919                 type=(String)_mimeMap.get("*");
920              if (type==null)
921                  type=(String)__dftMimeMap.get("*");
922         }
923 
924         return type;
925     }
926 
927     /* ------------------------------------------------------------ */
928     /** Set a mime mapping
929      * @param extension
930      * @param type
931      */
932     public void setMimeMapping(String extension,String type)
933     {
934         if (_mimeMap==null)
935             _mimeMap=new HashMap();
936         _mimeMap.put(extension,type);
937     }
938 
939 
940     /* ------------------------------------------------------------ */
941     /** Get the context classpath.
942      * This method only returns the paths that have been set for this
943      * context and does not include any paths from a parent or the
944      * system classloader.
945      * Note that this may not be a legal javac classpath.
946      * @return a comma or ';' separated list of class
947      * resources. These may be jar files, directories or URLs to jars
948      * or directories.
949      * @see #getFileClassPath()
950      */
951     public String getClassPath()
952     {
953         return _classPath;
954     }
955 
956     /* ------------------------------------------------------------ */
957     /** Get the file classpath of the context.
958      * This method makes a best effort to return a complete file
959      * classpath for the context.
960      * It is obtained by walking the classloader hierarchy and looking for
961      * URLClassLoaders.  The system property java.class.path is also checked for
962      * file elements not already found in the loader hierarchy.
963      * @return Path of files and directories for loading classes.
964      * @exception IllegalStateException HttpContext.initClassLoader
965      * has not been called.
966      */
967     public String getFileClassPath()
968         throws IllegalStateException
969     {
970   
971         ClassLoader loader = getClassLoader();
972         if (loader==null)
973             throw new IllegalStateException("Context classloader not initialized");
974             
975         LinkedList paths =new LinkedList();
976         LinkedList loaders=new LinkedList();
977         
978         // Walk the loader hierarchy
979          while (loader !=null)
980          {
981             loaders.add(0,loader);
982             loader = loader.getParent();
983          }
984          
985          // Try to handle java2compliant modes
986          loader=getClassLoader();
987          if (loader instanceof ContextLoader && !((ContextLoader)loader).isJava2Compliant())
988          {
989             loaders.remove(loader);
990             loaders.add(0,loader);
991          }
992         
993         for (int i=0;i<loaders.size();i++)
994         {
995             loader=(ClassLoader)loaders.get(i);
996             
997             if (log.isDebugEnabled()) log.debug("extract paths from "+loader);
998             if (loader instanceof URLClassLoader)
999             {
1000                URL[] urls = ((URLClassLoader)loader).getURLs();
1001                for (int j=0;j<urls.length;j++)
1002                {
1003                    try
1004                    {
1005                        Resource path = Resource.newResource(urls[j]);
1006                        if (LogSupport.isTraceEnabled(log)) log.trace("path "+path);
1007                        File file = path.getFile();
1008                        if (file!=null)
1009                            paths.add(file.getAbsolutePath());
1010                    }
1011                    catch(Exception e)
1012                    {
1013                        LogSupport.ignore(log,e);
1014                    }
1015                }  
1016            }  
1017         }
1018         
1019        // Add the system classpath elements from property.
1020        String jcp=System.getProperty("java.class.path");
1021        if (jcp!=null)
1022        {
1023            StringTokenizer tok=new StringTokenizer(jcp,File.pathSeparator);
1024            while (tok.hasMoreTokens())
1025            {
1026                String path=tok.nextToken();
1027                if (!paths.contains(path))
1028                {
1029                    if(LogSupport.isTraceEnabled(log))log.trace("PATH="+path);
1030                    paths.add(path);
1031                }
1032                else
1033                    if(LogSupport.isTraceEnabled(log))log.trace("done="+path);      
1034            }
1035        }
1036        
1037        StringBuffer buf = new StringBuffer();
1038        Iterator iter = paths.iterator();
1039        while(iter.hasNext())
1040        {
1041            if (buf.length()>0)
1042                buf.append(File.pathSeparator);
1043            buf.append(iter.next().toString());
1044        }
1045        
1046        if (log.isDebugEnabled()) log.debug("fileClassPath="+buf);
1047        return buf.toString();
1048    }
1049
1050    /* ------------------------------------------------------------ */
1051    /** Sets the class path for the context.
1052     * A class path is only required for a context if it uses classes
1053     * that are not in the system class path.
1054     * @param classPath a comma or ';' separated list of class
1055     * resources. These may be jar files, directories or URLs to jars
1056     * or directories.
1057     */
1058    public void setClassPath(String classPath)
1059    {
1060        _classPath=classPath;
1061        if (isStarted())
1062            log.warn("classpath set while started");
1063    }
1064
1065    /* ------------------------------------------------------------ */
1066    /** Sets the class path for the context from the jar and zip files found
1067     *  in the specified resource.
1068     * @param lib the resource that contains the jar and/or zip files.
1069     * @param append true if the classpath entries are to be appended to any
1070     * existing classpath, or false if they replace the existing classpath.
1071     * @see #setClassPath(String)
1072     */
1073    public void setClassPaths(Resource lib, boolean append)
1074    {
1075        if (isStarted())
1076            log.warn("classpaths set while started");
1077
1078        if (lib.exists() && lib.isDirectory())
1079        {
1080            StringBuffer classPath=new StringBuffer();
1081
1082            if (append && this.getClassPath()!=null)
1083                classPath.append(_classPath);
1084
1085            String[] files=lib.list();
1086            for (int f=0;files!=null && f<files.length;f++)
1087            {
1088                try {
1089                    Resource fn=lib.addPath(files[f]);
1090                    String fnlc=fn.getName().toLowerCase();
1091                    if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip"))
1092                    {
1093                        classPath.append(classPath.length()>0?",":"");
1094                        classPath.append(fn.toString());
1095                    }
1096                }
1097                catch (Exception ex)
1098                {
1099                    log.warn(LogSupport.EXCEPTION,ex);
1100                }
1101            }
1102
1103            if (classPath.length()>0)
1104                _classPath=classPath.toString();
1105        }
1106    }
1107
1108    /* ------------------------------------------------------------ */
1109    /** Sets the class path for the context from the jar and zip files found
1110     *  in the specified resource.
1111     * @param lib the resource that contains the jar and/or zip files.
1112     * @param append true if the classpath entries are to be appended to any
1113     * existing classpath, or false if they are to be prepended.
1114     * @exception IOException
1115     */
1116    public void setClassPaths(String lib, boolean append) throws IOException
1117    {
1118        if (_loader!=null)
1119            throw new IllegalStateException("ClassLoader already initialized");
1120        this.setClassPaths(Resource.newResource(lib), append);
1121    }
1122
1123    /* ------------------------------------------------------------ */
1124    /** Get Java2 compliant classloading.
1125     * @return If true, the class loader will conform to the java 2
1126     * specification and delegate all loads to the parent classloader. If
1127     * false, the context classloader only delegate loads for system classes
1128     * or classes that it can't find itself.
1129     */
1130    public boolean isClassLoaderJava2Compliant()
1131    {
1132        return _classLoaderJava2Compliant;
1133    }
1134
1135    /* ------------------------------------------------------------ */
1136    /** Set Java2 compliant classloading.
1137     * @param compliant If true, the class loader will conform to the java 2
1138     * specification and delegate all loads to the parent classloader. If
1139     * false, the context classloader only delegate loads for system classes
1140     * or classes that it can't find itself.
1141     */
1142    public void setClassLoaderJava2Compliant(boolean compliant)
1143    {
1144        _classLoaderJava2Compliant = compliant;
1145        if (_loader!=null && (_loader instanceof ContextLoader))
1146            ((ContextLoader)_loader).setJava2Compliant(compliant);
1147    }
1148
1149    /* ------------------------------------------------------------ */
1150    /** Set temporary directory for context.
1151     * The javax.servlet.context.tempdir attribute is also set.
1152     * @param dir Writable temporary directory.
1153     */
1154    public void setTempDirectory(File dir)
1155    {
1156        if (isStarted())
1157            throw new IllegalStateException("Started");
1158
1159        if (dir!=null)
1160        {
1161            try{dir=new File(dir.getCanonicalPath());}
1162            catch (IOException e){log.warn(LogSupport.EXCEPTION,e);}
1163        }
1164
1165        if (dir!=null && !dir.exists())
1166        {
1167            dir.mkdir();
1168            dir.deleteOnExit();
1169        }
1170
1171        if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
1172            throw new IllegalArgumentException("Bad temp directory: "+dir);
1173
1174        _tmpDir=dir;
1175        setAttribute("javax.servlet.context.tempdir",_tmpDir);
1176    }
1177
1178    /* ------------------------------------------------------------ */
1179    /** Get Context temporary directory.
1180     * A tempory directory is generated if it has not been set.  The
1181     * "javax.servlet.context.tempdir" attribute is consulted and if
1182     * not set, the host, port and context are used to generate a
1183     * directory within the JVMs temporary directory.
1184     * @return Temporary directory as a File.
1185     */
1186    public File getTempDirectory()
1187    {
1188        if (_tmpDir!=null)
1189            return _tmpDir;
1190
1191        // Initialize temporary directory
1192        //
1193        // I'm afraid that this is very much black magic.
1194        // but if you can think of better....
1195        Object t = getAttribute("javax.servlet.context.tempdir");
1196
1197        if (t!=null && (t instanceof File))
1198        {
1199            _tmpDir=(File)t;
1200            if (_tmpDir.isDirectory() && _tmpDir.canWrite())
1201                return _tmpDir;
1202        }
1203
1204        if (t!=null && (t instanceof String))
1205        {
1206            try
1207            {
1208                _tmpDir=new File((String)t);
1209
1210                if (_tmpDir.isDirectory() && _tmpDir.canWrite())
1211                {
1212                    if(log.isDebugEnabled())log.debug("Converted to File "+_tmpDir+" for "+this);
1213                    setAttribute("javax.servlet.context.tempdir",_tmpDir);
1214                    return _tmpDir;
1215                }
1216            }
1217            catch(Exception e)
1218            {
1219                log.warn(LogSupport.EXCEPTION,e);
1220            }
1221        }
1222
1223        // No tempdir so look for a WEB-INF/work directory to use as tempDir base
1224        File work=null;
1225        try
1226        {
1227            work=new File(System.getProperty("jetty.home"),"work");
1228            if (!work.exists() || !work.canWrite() || !work.isDirectory())
1229                work=null;
1230        }
1231        catch(Exception e)
1232        {
1233            LogSupport.ignore(log,e);
1234        }
1235
1236        // No tempdir set so make one!
1237        try
1238        {
1239            HttpListener httpListener=_httpServer.getListeners()[0];
1240
1241            String vhost = null;
1242            for (int h=0;vhost==null && _vhosts!=null && h<_vhosts.size();h++)
1243                vhost=(String)_vhosts.get(h);
1244            String host=httpListener.getHost();
1245            String temp="Jetty_"+
1246                (host==null?"":host)+
1247                "_"+
1248                httpListener.getPort()+
1249                "_"+
1250                (vhost==null?"":vhost)+
1251                getContextPath();
1252
1253            temp=temp.replace('/','_');
1254            temp=temp.replace('.','_');
1255            temp=temp.replace('\\','_');
1256
1257            _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
1258            if (_tmpDir.exists())
1259            {
1260                if(log.isDebugEnabled())log.debug("Delete existing temp dir "+_tmpDir+" for "+this);
1261                if (!IO.delete(_tmpDir))
1262                {
1263                    if(log.isDebugEnabled())log.debug("Failed to delete temp dir "+_tmpDir);
1264                }
1265                
1266                if (_tmpDir.exists())
1267                {
1268                    String old=_tmpDir.toString();
1269                    _tmpDir=File.createTempFile(temp+"_","");
1270                    if (_tmpDir.exists())
1271                        _tmpDir.delete();
1272                    log.warn("Can't reuse "+old+", using "+_tmpDir);
1273                }
1274            }
1275
1276            _tmpDir.mkdir();
1277            _tmpDir.deleteOnExit();
1278            if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this);
1279        }
1280        catch(Exception e)
1281        {
1282            _tmpDir=null;
1283            LogSupport.ignore(log,e);
1284        }
1285
1286        if (_tmpDir==null)
1287        {
1288            try{
1289                // that didn't work, so try something simpler (ish)
1290                _tmpDir=File.createTempFile("JettyContext","");
1291                if (_tmpDir.exists())
1292                    _tmpDir.delete();
1293                _tmpDir.mkdir();
1294                _tmpDir.deleteOnExit();
1295                if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this);
1296            }
1297            catch(IOException e)
1298            {
1299                log.fatal(e); System.exit(1);
1300            }
1301        }
1302
1303        setAttribute("javax.servlet.context.tempdir",_tmpDir);
1304        return _tmpDir;
1305    }
1306
1307
1308
1309    /* ------------------------------------------------------------ */
1310    /** Set ClassLoader.
1311     * @param loader The loader to be used by this context.
1312     */
1313    public synchronized void setClassLoader(ClassLoader loader)
1314    {
1315        if (isStarted())
1316            throw new IllegalStateException("Started");
1317        _loader=loader;
1318    }
1319
1320
1321    /* ------------------------------------------------------------ */
1322    /** Get the classloader.
1323     * If no classloader has been set and the context has been loaded
1324     * normally, then null is returned.
1325     * If no classloader has been set and the context was loaded from
1326     * a classloader, that loader is returned.
1327     * If a classloader has been set and no classpath has been set then
1328     * the set classloader is returned.
1329     * If a classloader and a classpath has been set, then a new
1330     * URLClassloader initialized on the classpath with the set loader as a
1331     * partent is return.
1332     * @return Classloader or null.
1333     */
1334    public synchronized ClassLoader getClassLoader()
1335    {
1336        return _loader;
1337    }
1338
1339    /* ------------------------------------------------------------ */
1340    /** Set Parent ClassLoader.
1341     * By default the parent loader is the thread context classloader
1342     * of the thread that calls initClassLoader.  If setClassLoader is
1343     * called, then the parent is ignored.
1344     * @param loader The class loader to use for the parent loader of
1345     * the context classloader.
1346     */
1347    public synchronized void setParentClassLoader(ClassLoader loader)
1348    {
1349        if (isStarted())
1350            throw new IllegalStateException("Started");
1351        _parent=loader;
1352    }
1353
1354    /* ------------------------------------------------------------ */
1355    public ClassLoader getParentClassLoader()
1356    {
1357        return _parent;
1358    }
1359
1360    /* ------------------------------------------------------------ */
1361    /** Initialize the context classloader.
1362     * Initialize the context classloader with the current parameters.
1363     * Any attempts to change the classpath after this call will
1364     * result in a IllegalStateException
1365     * @param forceContextLoader If true, a ContextLoader is always if
1366     * no loader has been set.
1367     */
1368    protected void initClassLoader(boolean forceContextLoader)
1369        throws MalformedURLException, IOException
1370    {
1371        if (_loader==null)
1372        {
1373            // If no parent, then try this threads classes loader as parent
1374            if (_parent==null)
1375                _parent=Thread.currentThread().getContextClassLoader();
1376
1377            // If no parent, then try this classes loader as parent
1378            if (_parent==null)
1379                _parent=this.getClass().getClassLoader();
1380
1381            if(log.isDebugEnabled())log.debug("Init classloader from "+_classPath+
1382                       ", "+_parent+" for "+this);
1383
1384            if (forceContextLoader || _classPath!=null || _permissions!=null)
1385            {
1386                ContextLoader loader=new ContextLoader(this,_classPath,_parent,_permissions);
1387                loader.setJava2Compliant(_classLoaderJava2Compliant);
1388                _loader=loader;
1389            }
1390            else
1391                _loader=_parent;
1392        }
1393    }
1394
1395    /* ------------------------------------------------------------ */
1396    public synchronized Class loadClass(String className)
1397        throws ClassNotFoundException
1398    {
1399        if (_loader==null)
1400        {
1401            try{initClassLoader(false);}
1402            catch(Exception e)
1403            {
1404                log.warn(LogSupport.EXCEPTION,e);
1405                return null;
1406            }
1407        }
1408
1409        if (className==null)
1410            return null;
1411
1412        return _loader.loadClass(className);
1413    }
1414
1415    /* ------------------------------------------------------------ */
1416    /** Set the realm name.
1417     * @param realmName The name to use to retrieve the actual realm
1418     * from the HttpServer
1419     */
1420    public void setRealmName(String realmName)
1421    {
1422        _realmName=realmName;
1423    }
1424
1425    /* ------------------------------------------------------------ */
1426    public String getRealmName()
1427    {
1428        return _realmName;
1429    }
1430
1431    /* ------------------------------------------------------------ */
1432    /** Set the  realm.
1433     */
1434    public void setRealm(UserRealm realm)
1435    {
1436        _userRealm=realm;
1437    }
1438
1439    /* ------------------------------------------------------------ */
1440    public UserRealm getRealm()
1441    {
1442        return _userRealm;
1443    }
1444
1445    /* ------------------------------------------------------------ */
1446    public Authenticator getAuthenticator()
1447    {
1448        return _authenticator;
1449    }
1450
1451    /* ------------------------------------------------------------ */
1452    public void setAuthenticator(Authenticator authenticator)
1453    {
1454        _authenticator=authenticator;
1455    }
1456
1457    /* ------------------------------------------------------------ */
1458    public void addSecurityConstraint(String pathSpec, SecurityConstraint sc)
1459    {
1460        Object scs = _constraintMap.get(pathSpec);
1461        scs = LazyList.add(scs,sc);
1462        _constraintMap.put(pathSpec,scs);
1463        
1464        if(log.isDebugEnabled())log.debug("added "+sc+" at "+pathSpec);
1465    }
1466
1467    /* ------------------------------------------------------------ */
1468    public boolean checkSecurityConstraints(
1469        String pathInContext,
1470        HttpRequest request,
1471        HttpResponse response)
1472        throws HttpException, IOException
1473    {
1474        UserRealm realm= getRealm();
1475
1476        List scss= _constraintMap.getMatches(pathInContext);
1477        String pattern=null;
1478        if (scss != null && scss.size() > 0)
1479        {
1480            Object constraints= null;
1481
1482            // for each path match
1483            // Add only constraints that have the correct method
1484            // break if the matching pattern changes.  This allows only
1485            // constraints with matching pattern and method to be combined.
1486            loop:
1487            for (int m= 0; m < scss.size(); m++)
1488            {
1489                Map.Entry entry= (Map.Entry)scss.get(m);
1490                Object scs= entry.getValue();
1491                String p=(String)entry.getKey();
1492                for (int c=0;c<LazyList.size(scs);c++)
1493                {
1494                  SecurityConstraint sc=(SecurityConstraint)LazyList.get(scs,c);
1495          if (!sc.forMethod(request.getMethod()))
1496            continue;
1497            
1498          if (pattern!=null && !pattern.equals(p))
1499            break loop;
1500          pattern=p;  
1501                  constraints= LazyList.add(constraints, sc);
1502                }
1503            }
1504            
1505            return SecurityConstraint.check(
1506                LazyList.getList(constraints),
1507                _authenticator,
1508                realm,
1509                pathInContext,
1510                request,
1511                response);
1512        }
1513        return true;
1514    }
1515
1516    /* ------------------------------------------------------------ */
1517    /** Get the map of mime type to char encoding.
1518     * @return Map of mime type to character encodings.
1519     */
1520    public synchronized Map getEncodingMap()
1521    {
1522        if (_encodingMap==null)
1523            _encodingMap=Collections.unmodifiableMap(__encodings);
1524        return _encodingMap;
1525    }
1526
1527    /* ------------------------------------------------------------ */
1528    /** Set the map of mime type to char encoding.
1529     * Also sets the org.mortbay.http.encodingMap context attribute
1530     * @param encodingMap Map of mime type to character encodings.
1531     */
1532    public void setEncodingMap(Map encodingMap)
1533    {
1534        _encodingMap = encodingMap;
1535    }
1536
1537    /* ------------------------------------------------------------ */
1538    /** Get char encoding by mime type.
1539     * @param type A mime type.
1540     * @return The prefered character encoding for that type if known.
1541     */
1542    public String getEncodingByMimeType(String type)
1543    {
1544        String encoding =null;
1545
1546        if (type!=null)
1547            encoding=(String)_encodingMap.get(type);
1548
1549        return encoding;
1550    }
1551
1552    /* ------------------------------------------------------------ */
1553    /** Set the encoding that should be used for a mimeType.
1554     * @param mimeType
1555     * @param encoding
1556     */
1557    public void setTypeEncoding(String mimeType,String encoding)
1558    {
1559        getEncodingMap().put(mimeType,encoding);
1560    }
1561
1562    /* ------------------------------------------------------------ */
1563    /** Set null path redirection.
1564     * @param b if true a /context request will be redirected to
1565     * /context/ if there is not path in the context.
1566     */
1567    public void setRedirectNullPath(boolean b)
1568    {
1569        _redirectNullPath=b;
1570    }
1571
1572    /* ------------------------------------------------------------ */
1573    /**
1574     * @return True if a /context request is redirected to /context/ if
1575     * there is not path in the context.
1576     */
1577    public boolean isRedirectNullPath()
1578    {
1579        return _redirectNullPath;
1580    }
1581
1582
1583
1584    /* ------------------------------------------------------------ */
1585    /** Set the permissions to be used for this context.
1586     * The collection of permissions set here are used for all classes
1587     * loaded by this context.  This is simpler that creating a
1588     * security policy file, as not all code sources may be statically
1589     * known.
1590     * @param permissions
1591     */
1592    public void setPermissions(PermissionCollection permissions)
1593    {
1594        _permissions=permissions;
1595    }
1596
1597    /* ------------------------------------------------------------ */
1598    /** Get the permissions to be used for this context.
1599     */
1600    public PermissionCollection getPermissions()
1601    {
1602        return _permissions;
1603    }
1604
1605    /* ------------------------------------------------------------ */
1606    /** Add a permission to this context.
1607     * The collection of permissions set here are used for all classes
1608     * loaded by this context.  This is simpler that creating a
1609     * security policy file, as not all code sources may be statically
1610     * known.
1611     * @param permission
1612     */
1613    public void addPermission(Permission permission)
1614    {
1615        if (_permissions==null)
1616            _permissions=new Permissions();
1617        _permissions.add(permission);
1618    }
1619
1620    /* ------------------------------------------------------------ */
1621    /** Handler request.
1622     * Determine the path within the context and then call
1623     * handle(pathInContext,request,response).
1624     * @param request
1625     * @param response
1626     * @return True if the request has been handled.
1627     * @exception HttpException
1628     * @exception IOException
1629     */
1630    public void handle(HttpRequest request,
1631                       HttpResponse response)
1632        throws HttpException, IOException
1633    {
1634        if (!_started)
1635            return;
1636
1637        // reject requests by real host
1638        if (_hosts!=null && _hosts.size()>0)
1639        {
1640            Object o = request.getHttpConnection().getConnection();
1641            if (o instanceof Socket)
1642            {
1643                Socket s=(Socket)o;
1644                if (!_hosts.contains(s.getLocalAddress()))
1645                {
1646                    if(log.isDebugEnabled())log.debug(s.getLocalAddress()+" not in "+_hosts);
1647                    return;
1648                }
1649            }
1650        }
1651        
1652        // handle stats
1653        if (_statsOn)
1654        {
1655            synchronized(_statsLock)
1656            {
1657                _requests++;
1658                _requestsActive++;
1659                if (_requestsActive>_requestsActiveMax)
1660                    _requestsActiveMax=_requestsActive;
1661            }
1662        }
1663
1664        String pathInContext = URI.canonicalPath(request.getPath());
1665        if (pathInContext==null)
1666        {
1667            // Must be a bad request.
1668            throw new HttpException(HttpResponse.__400_Bad_Request);
1669        }
1670
1671        if (_contextPath.length()>1)
1672            pathInContext=pathInContext.substring(_contextPath.length());
1673
1674        if (_redirectNullPath && (pathInContext==null ||
1675                                  pathInContext.length()==0))
1676        {
1677            StringBuffer buf=request.getRequestURL();
1678            buf.append("/");
1679            String q=request.getQuery();
1680            if (q!=null&&q.length()!=0)
1681                buf.append("?"+q);
1682                
1683            response.sendRedirect(buf.toString());
1684            if (log.isDebugEnabled())
1685                log.debug(this+" consumed all of path "+
1686                             request.getPath()+
1687                             ", redirect to "+buf.toString());
1688            return;
1689        }
1690
1691        String pathParams=null;
1692        int semi = pathInContext.lastIndexOf(';');
1693        if (semi>=0)
1694        {
1695            int pl = pathInContext.length()-semi;
1696            String ep=request.getEncodedPath();
1697            if(';'==ep.charAt(ep.length()-pl))
1698            {
1699                pathParams=pathInContext.substring(semi+1);
1700                pathInContext=pathInContext.substring(0,semi);
1701            }
1702        }
1703
1704        try
1705        {
1706            handle(pathInContext,pathParams,request,response);
1707        }
1708        finally
1709        {
1710            if (_userRealm!=null && request.hasUserPrincipal())
1711                _userRealm.disassociate(request.getUserPrincipal());
1712        }
1713    }
1714
1715    /* ------------------------------------------------------------ */
1716    /** Handler request.
1717     * Call each HttpHandler until request is handled.
1718     * @param pathInContext Path in context
1719     * @param pathParams Path parameters such as encoded Session ID
1720     * @param request
1721     * @param response
1722     * @return True if the request has been handled.
1723     * @exception HttpException
1724     * @exception IOException
1725     */
1726    public void handle(String pathInContext,
1727                       String pathParams,
1728                       HttpRequest request,
1729                       HttpResponse response)
1730        throws HttpException, IOException
1731    {
1732        Object old_scope= null;
1733        try
1734        {
1735            old_scope= enterContextScope(request, response);
1736            HttpHandler[] handlers= getHandlers();
1737            for (int k= 0; k < handlers.length; k++)
1738            {
1739                HttpHandler handler= handlers[k];
1740                if (handler == null)
1741                {
1742                    handlers= getHandlers();
1743                    k= -1;
1744                    continue;
1745                }
1746                if (!handler.isStarted())
1747                {
1748                    if (log.isDebugEnabled())
1749                        log.debug(handler + " not started in " + this);
1750                    continue;
1751                }
1752                if (log.isDebugEnabled())
1753                    log.debug("Handler " + handler);
1754                handler.handle(pathInContext, pathParams, request, response);
1755                if (request.isHandled())
1756                {
1757                    if (log.isDebugEnabled())
1758                        log.debug("Handled by " + handler);
1759                    return;
1760                }
1761            }
1762            return;
1763        }
1764        finally
1765        {
1766            leaveContextScope(request, response, old_scope);
1767        }
1768    }
1769
1770    /* ------------------------------------------------------------ */
1771    /** Enter the context scope.
1772     * This method is called (by handle or servlet dispatchers) to indicate that
1773     * request handling is entering the scope of this context.  The opaque scope object
1774     * returned, should be passed to the leaveContextScope method.
1775     */
1776    public Object enterContextScope(HttpRequest request, HttpResponse response)
1777    {
1778        // Save the thread context loader
1779        Thread thread = Thread.currentThread();
1780        ClassLoader cl=thread.getContextClassLoader();
1781        HttpContext c=response.getHttpContext();
1782
1783        Scope scope=null;
1784        if (cl!=HttpContext.class.getClassLoader() || c!=null)
1785        {
1786            scope=new Scope();
1787            scope._classLoader=cl;
1788            scope._httpContext=c;
1789        }
1790        
1791        if (_loader!=null)
1792            thread.setContextClassLoader(_loader);
1793        response.setHttpContext(this);
1794            
1795        return scope;
1796    }
1797    
1798    /* ------------------------------------------------------------ */
1799    /** Leave the context scope.
1800     * This method is called (by handle or servlet dispatchers) to indicate that
1801     * request handling is leaveing the scope of this context.  The opaque scope object
1802     * returned by enterContextScope should be passed in.
1803     */
1804    public void leaveContextScope(HttpRequest request, HttpResponse response,Object oldScope)
1805    {
1806        if (oldScope==null)
1807        {
1808            Thread.currentThread()
1809                .setContextClassLoader(HttpContext.class.getClassLoader());
1810            response.setHttpContext(null);
1811        }
1812        else
1813        {
1814            Scope old = (Scope)oldScope;
1815            Thread.currentThread().setContextClassLoader(old._classLoader);
1816            response.setHttpContext(old._httpContext);
1817        }
1818    }
1819    
1820
1821    /* ------------------------------------------------------------ */
1822    public String getHttpContextName()
1823    {
1824        if (_contextName==null)
1825            _contextName = (_vhosts.size()>1?(_vhosts.toString()+":"):"")+_contextPath;
1826        return _contextName;
1827    }
1828
1829    /* ------------------------------------------------------------ */
1830    public String toString()
1831    {
1832        return "HttpContext["+getHttpContextName()+"]";
1833    }
1834
1835    /* ------------------------------------------------------------ */
1836    public String toString(boolean detail)
1837    {
1838        return "HttpContext["+getHttpContextName()+"]" +
1839            (detail?("="+_handlers):"");
1840    }
1841
1842    /* ------------------------------------------------------------ */
1843    public synchronized void start()
1844        throws Exception
1845    {
1846        if (isStarted())
1847            return;
1848
1849        statsReset();
1850
1851        if (_httpServer==null)
1852            throw new IllegalStateException("No server for "+this);
1853
1854        // start the context itself
1855        getMimeMap();
1856        getEncodingMap();
1857
1858        // Setup realm
1859        if (_userRealm==null && _authenticator!=null)
1860        {
1861            _userRealm=_httpServer.getRealm(_realmName);
1862            if (_userRealm==null)
1863                log.warn("No Realm: "+_realmName);
1864        }
1865
1866        // setup the context loader
1867        initClassLoader(false);
1868
1869        // Set attribute if needed
1870        String attr = getInitParameter(__fileClassPathAttr);
1871        if (attr!=null && attr.length()>0)
1872            setAttribute(attr,getFileClassPath());
1873
1874        // Start the handlers
1875        Thread thread = Thread.currentThread();
1876        ClassLoader lastContextLoader=thread.getContextClassLoader();
1877        try
1878        {
1879            if (_loader!=null)
1880                thread.setContextClassLoader(_loader);
1881
1882            if (_requestLog!=null)
1883                _requestLog.start();
1884            
1885            startHandlers();
1886        }
1887        finally
1888        {
1889            thread.setContextClassLoader(lastContextLoader);
1890            _started=true;
1891      getHandlers();
1892        }
1893
1894        log.info("Started "+this);
1895    }
1896
1897    /* ------------------------------------------------------------ */
1898    /** Start the handlers.
1899     * This is called by start after the classloader has been
1900     * initialized and set as the thread context loader.
1901     * It may be specialized to provide custom handling
1902     * before any handlers are started.
1903     * @exception Exception
1904     */
1905    protected void startHandlers()
1906        throws Exception
1907    {
1908        // Prepare a multi exception
1909        MultiException mx = new MultiException();
1910
1911        Iterator handlers = _handlers.iterator();
1912        while(handlers.hasNext())
1913        {
1914            HttpHandler handler=(HttpHandler)handlers.next();
1915            if (!handler.isStarted())
1916                try{handler.start();}catch(Exception e){mx.add(e);}
1917        }
1918        mx.ifExceptionThrow();
1919    }
1920
1921    /* ------------------------------------------------------------ */
1922    public synchronized boolean isStarted()
1923    {
1924        return _started;
1925    }
1926
1927    /* ------------------------------------------------------------ */
1928    /** Stop the context.
1929     * @param graceful If true and statistics are on, then this method will wait
1930     * for requestsActive to go to zero before calling stop()
1931     */
1932    public void stop(boolean graceful)
1933        throws InterruptedException
1934    {
1935        _started=false;
1936
1937        // wait for all requests to complete.
1938        while (graceful && _statsOn && _requestsActive>0 && _httpServer!=null)
1939            try {Thread.sleep(100);}
1940            catch (InterruptedException e){throw e;}
1941            catch (Exception e){LogSupport.ignore(log,e);}
1942
1943        stop();
1944    }
1945
1946    /* ------------------------------------------------------------ */
1947    /** Stop the context.
1948     */
1949    public void stop()
1950        throws InterruptedException
1951    {
1952        _started=false;
1953
1954        if (_httpServer==null)
1955            throw new InterruptedException("Destroy called");
1956
1957        synchronized(this)
1958        {
1959            // Notify the container for the stop
1960            Thread thread = Thread.currentThread();
1961            ClassLoader lastContextLoader=thread.getContextClassLoader();
1962            try
1963            {
1964                if (_loader!=null)
1965                    thread.setContextClassLoader(_loader);
1966                Iterator handlers = _handlers.iterator();
1967                while(handlers.hasNext())
1968                {
1969                    HttpHandler handler=(HttpHandler)handlers.next();
1970                    if (handler.isStarted())
1971                    {
1972                        try{handler.stop();}
1973                        catch(Exception e){log.warn(LogSupport.EXCEPTION,e);}
1974                    }
1975                }
1976                
1977                if (_requestLog!=null)
1978                    _requestLog.stop();
1979            }
1980            finally
1981            {
1982                thread.setContextClassLoader(lastContextLoader);
1983            }
1984            _loader=null;
1985        }
1986        _cache.clear();
1987  _constraintMap.clear();
1988        if (_attributes!=null)
1989            _attributes.clear();
1990        log.info("Stopped "+this);
1991    }
1992
1993
1994    /* ------------------------------------------------------------ */
1995    /** Destroy a context.
1996     * Destroy a context and remove it from the HttpServer. The
1997     * HttpContext must be stopped before it can be destroyed.
1998     */
1999    public void destroy()
2000    {
2001        if (isStarted())
2002            throw new IllegalStateException("Started");
2003
2004        if (_httpServer!=null)
2005            _httpServer.removeContext(this);
2006
2007        _httpServer=null;
2008        if (_handlers!=null)
2009            _handlers.clear();
2010        _handlers=null;
2011        _parent=null;
2012        _loader=null;
2013        _resourceBase=null;
2014        _attributes=null;
2015        if (_initParams!=null)
2016            _initParams.clear();
2017        _initParams=null;
2018        if (_vhosts!=null)
2019            _vhosts.clear();
2020        _vhosts=null;
2021        _hosts=null;
2022        _tmpDir=null;
2023
2024        setMimeMap(null);
2025        _encodingMap=null;
2026        _permissions=null;
2027    }
2028
2029
2030    /* ------------------------------------------------------------ */
2031    /** Set the request log.
2032     * @param log RequestLog to use.
2033     */
2034    public void setRequestLog(RequestLog log)
2035    {
2036        _requestLog=log;
2037    }
2038    
2039    /* ------------------------------------------------------------ */
2040    public RequestLog getRequestLog()
2041    {
2042        return _requestLog;
2043    }
2044
2045    /* ------------------------------------------------------------ */
2046    /** True set statistics recording on for this context.
2047     * @param on If true, statistics will be recorded for this context.
2048     */
2049    public void setStatsOn(boolean on)
2050    {
2051        log.info("setStatsOn "+on+" for "+this);
2052        _statsOn=on;
2053        statsReset();
2054    }
2055
2056    /* ------------------------------------------------------------ */
2057    public boolean getStatsOn() {return _statsOn;}
2058
2059    /* ------------------------------------------------------------ */
2060    public long getStatsOnMs()
2061    {return _statsOn?(System.currentTimeMillis()-_statsStartedAt):0;}
2062
2063    /* ------------------------------------------------------------ */
2064    public void statsReset()
2065    {
2066        synchronized(_statsLock)
2067        {
2068            if (_statsOn)
2069                _statsStartedAt=System.currentTimeMillis();
2070            _requests=0;
2071            _requestsActive=0;
2072            _requestsActiveMax=0;
2073            _responses1xx=0;
2074            _responses2xx=0;
2075            _responses3xx=0;
2076            _responses4xx=0;
2077            _responses5xx=0;
2078        }
2079    }
2080
2081    /* ------------------------------------------------------------ */
2082    /**
2083     * @return Get the number of requests handled by this context
2084     * since last call of statsReset(). If setStatsOn(false) then this
2085     * is undefined.
2086     */
2087    public int getRequests() {return _requests;}
2088
2089    /* ------------------------------------------------------------ */
2090    /**
2091     * @return Number of requests currently active.
2092     * Undefined if setStatsOn(false).
2093     */
2094    public int getRequestsActive() {return _requestsActive;}
2095
2096    /* ------------------------------------------------------------ */
2097    /**
2098     * @return Maximum number of active requests
2099     * since statsReset() called. Undefined if setStatsOn(false).
2100     */
2101    public int getRequestsActiveMax() {return _requestsActiveMax;}
2102
2103    /* ------------------------------------------------------------ */
2104    /**
2105     * @return Get the number of responses with a 2xx status returned
2106     * by this context since last call of statsReset(). Undefined if
2107     * if setStatsOn(false).
2108     */
2109    public int getResponses1xx() {return _responses1xx;}
2110
2111    /* ------------------------------------------------------------ */
2112    /**
2113     * @return Get the number of responses with a 100 status returned
2114     * by this context since last call of statsReset(). Undefined if
2115     * if setStatsOn(false).
2116     */
2117    public int getResponses2xx() {return _responses2xx;}
2118
2119    /* ------------------------------------------------------------ */
2120    /**
2121     * @return Get the number of responses with a 3xx status returned
2122     * by this context since last call of statsReset(). Undefined if
2123     * if setStatsOn(false).
2124     */
2125    public int getResponses3xx() {return _responses3xx;}
2126
2127    /* ------------------------------------------------------------ */
2128    /**
2129     * @return Get the number of responses with a 4xx status returned
2130     * by this context since last call of statsReset(). Undefined if
2131     * if setStatsOn(false).
2132     */
2133    public int getResponses4xx() {return _responses4xx;}
2134
2135    /* ------------------------------------------------------------ */
2136    /**
2137     * @return Get the number of responses with a 5xx status returned
2138     * by this context since last call of statsReset(). Undefined if
2139     * if setStatsOn(false).
2140     */
2141    public int getResponses5xx() {return _responses5xx;}
2142
2143
2144    /* ------------------------------------------------------------ */
2145    /** Log a request and response.
2146     * Statistics are also collected by this method.
2147     * @param request
2148     * @param response
2149     */
2150    public void log(HttpRequest request,
2151                    HttpResponse response,
2152                    int length)
2153    {
2154        if (_statsOn)
2155        {
2156            synchronized(_statsLock)
2157            {
2158                if (--_requestsActive<0)
2159                    _requestsActive=0;
2160
2161                if (response!=null)
2162                {
2163                    switch(response.getStatus()/100)
2164                    {
2165                      case 1: _responses1xx++;break;
2166                      case 2: _responses2xx++;break;
2167                      case 3: _responses3xx++;break;
2168                      case 4: _responses4xx++;break;
2169                      case 5: _responses5xx++;break;
2170                    }
2171                }
2172            }
2173        }
2174
2175        if (_requestLog!=null &&
2176            request!=null &&
2177            response!=null)
2178            _requestLog.log(request,response,length);
2179        else if (_httpServer!=null)
2180            _httpServer.log(request,response,length);
2181    }
2182
2183    /* ------------------------------------------------------------ */
2184    /** Get Resource MetaData.
2185     * This is a temp method until the resource cache is split out from the HttpContext.
2186     * @param resource 
2187     * @return Meta data for the resource.
2188     */
2189    public ResourceMetaData getResourceMetaData(Resource resource)
2190    {
2191        Object o=resource.getAssociate();
2192        if (o instanceof ResourceMetaData)
2193            return (ResourceMetaData)o;
2194        return new ResourceMetaData(resource);
2195    }
2196    
2197    /* ------------------------------------------------------------ */
2198    /* ------------------------------------------------------------ */
2199    /** MetaData associated with a context Resource.
2200     */
2201    public class ResourceMetaData
2202    {
2203        protected String _name;
2204        protected Resource _resource;
2205
2206        ResourceMetaData(Resource resource)
2207        {
2208            _resource=resource;
2209            _name=_resource.toString();
2210            _resource.setAssociate(this);
2211        }
2212
2213        public String getLength()
2214        {
2215            return Long.toString(_resource.length());
2216        }
2217
2218        public String getLastModified()
2219        {
2220            return HttpFields.__dateSend.format(new Date(_resource.lastModified()));
2221        }
2222
2223        public String getEncoding()
2224        {
2225            return getMimeByExtension(_name);
2226        }
2227    }
2228
2229    /* ------------------------------------------------------------ */
2230    /* ------------------------------------------------------------ */
2231    private class CachedMetaData extends ResourceMetaData
2232    {
2233        String _lastModified;
2234        String _encoding;
2235        String _length;
2236        String _key;
2237
2238        CachedResource _cached;
2239        CachedMetaData _prev;
2240        CachedMetaData _next;
2241
2242        CachedMetaData(CachedResource resource, String pathInContext)
2243        {
2244            super(resource);
2245            _cached=resource;
2246            _length=super.getLength();
2247            _lastModified=super.getLastModified();
2248            _encoding=super.getEncoding();
2249            _key=pathInContext;
2250
2251            _next=_mostRecentlyUsed;
2252            _mostRecentlyUsed=this;
2253            if (_next!=null)
2254                _next._prev=this;
2255            _prev=null;
2256            if (_leastRecentlyUsed==null)
2257                _leastRecentlyUsed=this;
2258
2259            _cache.put(_key,resource);
2260
2261            _cacheSize+=_cached.length();
2262
2263        }
2264
2265        public String getLength()
2266        {
2267            return _length;
2268        }
2269
2270        public String getLastModified()
2271        {
2272            return _lastModified;
2273        }
2274
2275        public String getEncoding()
2276        {
2277            return _encoding;
2278        }
2279
2280        /* ------------------------------------------------------------ */
2281        boolean isValid()
2282            throws IOException
2283        {
2284            if (_cached.isUptoDate())
2285            {
2286                if (_mostRecentlyUsed!=this)
2287                {
2288                    CachedMetaData tp = _prev;
2289                    CachedMetaData tn = _next;
2290
2291                    _next=_mostRecentlyUsed;
2292                    _mostRecentlyUsed=this;
2293                    if (_next!=null)
2294                        _next._prev=this;
2295                    _prev=null;
2296
2297                    if (tp!=null)
2298                        tp._next=tn;
2299                    if (tn!=null)
2300                        tn._prev=tp;
2301
2302                    if (_leastRecentlyUsed==this && tp!=null)
2303                        _leastRecentlyUsed=tp;
2304                }
2305                return true;
2306            }
2307
2308            invalidate();
2309            return false;
2310        }
2311
2312        public void invalidate()
2313        {
2314            // Invalidate it
2315            _cache.remove(_key);
2316            _cacheSize=_cacheSize-(int)_cached.length();
2317
2318
2319            if (_mostRecentlyUsed==this)
2320                _mostRecentlyUsed=_next;
2321            else
2322                _prev._next=_next;
2323
2324            if (_leastRecentlyUsed==this)
2325                _leastRecentlyUsed=_prev;
2326            else
2327                _next._prev=_prev;
2328
2329            _prev=null;
2330            _next=null;
2331        }
2332    }
2333    
2334    /* ------------------------------------------------------------ */
2335    /* Class to save scope of nested context calls
2336     */
2337    private static class Scope 
2338    {
2339        ClassLoader _classLoader;
2340        HttpContext _httpContext;
2341    }
2342
2343    /* 
2344     * @see org.mortbay.http.HttpHandler#getName()
2345     */
2346    public String getName()
2347    {
2348        return this.getContextPath();
2349    }
2350
2351    /* 
2352     * @see org.mortbay.http.HttpHandler#getHttpContext()
2353     */
2354    public HttpContext getHttpContext()
2355    {
2356        return this;
2357    }
2358
2359    /* 
2360     * @see org.mortbay.http.HttpHandler#initialize(org.mortbay.http.HttpContext)
2361     */
2362    public void initialize(HttpContext context)
2363    {
2364        throw new UnsupportedOperationException();
2365    }    
2366    
2367}