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}