View Javadoc

1   //========================================================================
2   //$Id: WebAppContext.java,v 1.5 2005/11/16 22:02:45 gregwilkins Exp $
3   //Copyright 2004-2006 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty.webapp;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.net.MalformedURLException;
21  import java.security.PermissionCollection;
22  import java.util.EventListener;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  import javax.servlet.http.HttpSessionActivationListener;
30  import javax.servlet.http.HttpSessionAttributeListener;
31  import javax.servlet.http.HttpSessionBindingListener;
32  import javax.servlet.http.HttpSessionListener;
33  
34  import org.mortbay.component.AbstractLifeCycle;
35  import org.mortbay.jetty.Connector;
36  import org.mortbay.jetty.HandlerContainer;
37  import org.mortbay.jetty.HttpConnection;
38  import org.mortbay.jetty.Request;
39  import org.mortbay.jetty.Server;
40  import org.mortbay.jetty.deployer.ContextDeployer;
41  import org.mortbay.jetty.deployer.WebAppDeployer;
42  import org.mortbay.jetty.handler.ContextHandler;
43  import org.mortbay.jetty.handler.ContextHandlerCollection;
44  import org.mortbay.jetty.handler.ErrorHandler;
45  import org.mortbay.jetty.handler.HandlerCollection;
46  import org.mortbay.jetty.security.SecurityHandler;
47  import org.mortbay.jetty.servlet.Context;
48  import org.mortbay.jetty.servlet.ErrorPageErrorHandler;
49  import org.mortbay.jetty.servlet.ServletHandler;
50  import org.mortbay.jetty.servlet.SessionHandler;
51  import org.mortbay.log.Log;
52  import org.mortbay.resource.JarResource;
53  import org.mortbay.resource.Resource;
54  import org.mortbay.util.IO;
55  import org.mortbay.util.LazyList;
56  import org.mortbay.util.Loader;
57  import org.mortbay.util.StringUtil;
58  import org.mortbay.util.URIUtil;
59  import org.mortbay.util.UrlEncoded;
60  
61  /* ------------------------------------------------------------ */
62  /** Web Application Context Handler.
63   * The WebAppContext handler is an extension of ContextHandler that
64   * coordinates the construction and configuration of nested handlers:
65   * {@link org.mortbay.jetty.security.SecurityHandler}, {@link org.mortbay.jetty.servlet.SessionHandler}
66   * and {@link org.mortbay.jetty.servlet.ServletHandler}.
67   * The handlers are configured by pluggable configuration classes, with
68   * the default being  {@link org.mortbay.jetty.webapp.WebXmlConfiguration} and 
69   * {@link org.mortbay.jetty.webapp.JettyWebXmlConfiguration}.
70   *      
71   * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base"
72   * 
73   * @author gregw
74   *
75   */
76  public class WebAppContext extends Context
77  {   
78      public final static String WEB_DEFAULTS_XML="org/mortbay/jetty/webapp/webdefault.xml";
79      public final static String ERROR_PAGE="org.mortbay.jetty.error_page";
80      
81      private static String[] __dftConfigurationClasses =  
82      { 
83          "org.mortbay.jetty.webapp.WebInfConfiguration", 
84          "org.mortbay.jetty.webapp.WebXmlConfiguration", 
85          "org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
86          "org.mortbay.jetty.webapp.TagLibConfiguration" 
87      } ;
88      private String[] _configurationClasses=__dftConfigurationClasses;
89      private Configuration[] _configurations;
90      private String _defaultsDescriptor=WEB_DEFAULTS_XML;
91      private String _descriptor=null;
92      private String _overrideDescriptor=null;
93      private boolean _distributable=false;
94      private boolean _extractWAR=true;
95      private boolean _copyDir=false;
96      private boolean _logUrlOnStart =false;
97      private boolean _parentLoaderPriority= Boolean.getBoolean("org.mortbay.jetty.webapp.parentLoaderPriority");
98      private PermissionCollection _permissions;
99      
100     
101     private String[] _systemClasses = 
102     {
103         "java.",
104         "javax.",
105         "org.mortbay.",
106         "org.xml.",
107         "org.w3c.", 
108         "org.apache.commons.logging.", 
109         "org.apache.log4j."
110     };
111 
112     private String[] _serverClasses = 
113     {
114         "-org.mortbay.jetty.plus.annotation.",       // don't hide
115         "-org.mortbay.jetty.plus.jaas.",             // don't hide 
116         "-org.mortbay.jetty.plus.naming.",           // don't hide
117         "-org.mortbay.jetty.plus.jaas.",             // don't hide
118         "-org.mortbay.jetty.servlet.DefaultServlet", // don't hide
119         "org.mortbay.jetty.", 
120         "org.slf4j."
121     }; 
122     
123     private File _tmpDir;
124     private boolean _isExistingTmpDir;
125     private String _war;
126     private String _extraClasspath;
127     private Throwable _unavailableException;
128     
129     
130     private transient Map _resourceAliases;
131     private transient boolean _ownClassLoader=false;
132     private transient boolean _unavailable;
133 
134     public static ContextHandler getCurrentWebAppContext()
135     {
136         ContextHandler.SContext context=ContextHandler.getCurrentContext();
137         if (context!=null)
138         {
139             ContextHandler handler = context.getContextHandler();
140             if (handler instanceof WebAppContext)
141                 return (ContextHandler)handler;
142         }
143         return null;   
144     }
145     
146     /* ------------------------------------------------------------ */
147     /**  Add Web Applications.
148      * Add auto webapplications to the server.  The name of the
149      * webapp directory or war is used as the context name. If the
150      * webapp matches the rootWebApp it is added as the "/" context.
151      * @param server Must not be <code>null</code>
152      * @param webapps Directory file name or URL to look for auto
153      * webapplication.
154      * @param defaults The defaults xml filename or URL which is
155      * loaded before any in the web app. Must respect the web.dtd.
156      * If null the default defaults file is used. If the empty string, then
157      * no defaults file is used.
158      * @param extract If true, extract war files
159      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
160      * @exception IOException 
161      * @deprecated use {@link org.mortbay.jetty.deployer.WebAppDeployer} or {@link org.mortbay.jetty.deployer.ContextDeployer}
162      */
163     public static void addWebApplications(Server server,
164                                           String webapps,
165                                           String defaults,
166                                           boolean extract,
167                                           boolean java2CompliantClassLoader)
168         throws IOException
169     {
170         addWebApplications(server, webapps, defaults, __dftConfigurationClasses, extract, java2CompliantClassLoader);
171     }
172     
173     /* ------------------------------------------------------------ */
174     /**  Add Web Applications.
175      * Add auto webapplications to the server.  The name of the
176      * webapp directory or war is used as the context name. If the
177      * webapp matches the rootWebApp it is added as the "/" context.
178      * @param server Must not be <code>null</code>.
179      * @param webapps Directory file name or URL to look for auto
180      * webapplication.
181      * @param defaults The defaults xml filename or URL which is
182      * loaded before any in the web app. Must respect the web.dtd.
183      * If null the default defaults file is used. If the empty string, then
184      * no defaults file is used.
185      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
186      * @param extract If true, extract war files
187      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
188      * @exception IOException 
189      * @throws IllegalAccessException 
190      * @throws InstantiationException 
191      * @deprecated use {@link org.mortbay.jetty.deployer.WebAppDeployer} or {@link org.mortbay.jetty.deployer.ContextDeployer}
192      */
193     public static void addWebApplications(Server server,
194                                           String webapps,
195                                           String defaults,
196                                           String[] configurations,
197                                           boolean extract,
198                                           boolean java2CompliantClassLoader)
199         throws IOException
200     {
201         HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
202         if (contexts==null)
203             contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
204         
205         addWebApplications(contexts,webapps,defaults,configurations,extract,java2CompliantClassLoader);
206     }        
207 
208     /* ------------------------------------------------------------ */
209     /**  Add Web Applications.
210      * Add auto webapplications to the server.  The name of the
211      * webapp directory or war is used as the context name. If the
212      * webapp is called "root" it is added as the "/" context.
213      * @param contexts A HandlerContainer to which the contexts will be added
214      * @param webapps Directory file name or URL to look for auto
215      * webapplication.
216      * @param defaults The defaults xml filename or URL which is
217      * loaded before any in the web app. Must respect the web.dtd.
218      * If null the default defaults file is used. If the empty string, then
219      * no defaults file is used.
220      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
221      * @param extract If true, extract war files
222      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
223      * @exception IOException 
224      * @throws IllegalAccessException 
225      * @throws InstantiationException 
226      * @deprecated use {@link WebAppDeployer} or {@link ContextDeployer}
227      */
228     public static void addWebApplications(HandlerContainer contexts,
229                                           String webapps,
230                                           String defaults,
231                                           boolean extract,
232                                           boolean java2CompliantClassLoader)
233     throws IOException
234     {
235         addWebApplications(contexts, webapps, defaults, __dftConfigurationClasses, extract, java2CompliantClassLoader);
236     }
237     
238     /* ------------------------------------------------------------ */
239     /**  Add Web Applications.
240      * Add auto webapplications to the server.  The name of the
241      * webapp directory or war is used as the context name. If the
242      * webapp is called "root" it is added as the "/" context.
243      * @param contexts A HandlerContainer to which the contexts will be added
244      * @param webapps Directory file name or URL to look for auto
245      * webapplication.
246      * @param defaults The defaults xml filename or URL which is
247      * loaded before any in the web app. Must respect the web.dtd.
248      * If null the default defaults file is used. If the empty string, then
249      * no defaults file is used.
250      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
251      * @param extract If true, extract war files
252      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
253      * @exception IOException 
254      * @throws IllegalAccessException 
255      * @throws InstantiationException 
256      * @deprecated use {@link WebAppDeployer} or {@link ContextDeployer}
257      */
258     public static void addWebApplications(HandlerContainer contexts,
259                                           String webapps,
260                                           String defaults,
261                                           String[] configurations,
262                                           boolean extract,
263                                           boolean java2CompliantClassLoader)
264         throws IOException
265     {
266         Log.warn("Deprecated configuration used for "+webapps);
267         WebAppDeployer deployer = new WebAppDeployer();
268         deployer.setContexts(contexts);
269         deployer.setWebAppDir(webapps);
270         deployer.setConfigurationClasses(configurations);
271         deployer.setExtract(extract);
272         deployer.setParentLoaderPriority(java2CompliantClassLoader);
273         try
274         {
275             deployer.start();
276         }
277         catch(IOException e)
278         {
279             throw e;
280         }
281         catch(Exception e)
282         {
283             throw new RuntimeException(e);
284         }
285     }
286     
287     /* ------------------------------------------------------------ */
288     public WebAppContext()
289     {
290         this(null,null,null,null);
291     }
292     
293     /* ------------------------------------------------------------ */
294     /**
295      * @param contextPath The context path
296      * @param webApp The URL or filename of the webapp directory or war file.
297      */
298     public WebAppContext(String webApp,String contextPath)
299     {
300         super(null,contextPath,SESSIONS|SECURITY);
301         setContextPath(contextPath);
302         setWar(webApp);
303         setErrorHandler(new ErrorPageErrorHandler());
304     }
305     
306     /* ------------------------------------------------------------ */
307     /**
308      * @param parent The parent HandlerContainer.
309      * @param contextPath The context path
310      * @param webApp The URL or filename of the webapp directory or war file.
311      */
312     public WebAppContext(HandlerContainer parent, String webApp, String contextPath)
313     {
314         super(parent,contextPath,SESSIONS|SECURITY);
315         setWar(webApp);
316         setErrorHandler(new ErrorPageErrorHandler());
317     }
318 
319     /* ------------------------------------------------------------ */
320     /**
321      */
322     public WebAppContext(SecurityHandler securityHandler,SessionHandler sessionHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
323     {
324         super(null,
325               sessionHandler!=null?sessionHandler:new SessionHandler(),
326               securityHandler!=null?securityHandler:new SecurityHandler(),
327               servletHandler!=null?servletHandler:new ServletHandler(),
328               null);
329         
330         setErrorHandler(errorHandler!=null?errorHandler:new ErrorPageErrorHandler());
331     }    
332 
333     /* ------------------------------------------------------------ */
334     /** Get an exception that caused the webapp to be unavailable
335      * @return A throwable if the webapp is unavailable or null
336      */
337     public Throwable getUnavailableException()
338     {
339         return _unavailableException;
340     }
341 
342     
343     /* ------------------------------------------------------------ */
344     /** Set Resource Alias.
345      * Resource aliases map resource uri's within a context.
346      * They may optionally be used by a handler when looking for
347      * a resource.  
348      * @param alias 
349      * @param uri 
350      */
351     public void setResourceAlias(String alias, String uri)
352     {
353         if (_resourceAliases == null)
354             _resourceAliases= new HashMap(5);
355         _resourceAliases.put(alias, uri);
356     }
357 
358     /* ------------------------------------------------------------ */
359     public Map getResourceAliases()
360     {
361         if (_resourceAliases == null)
362             return null;
363         return _resourceAliases;
364     }
365     
366     /* ------------------------------------------------------------ */
367     public void setResourceAliases(Map map)
368     {
369         _resourceAliases = map;
370     }
371     
372     /* ------------------------------------------------------------ */
373     public String getResourceAlias(String alias)
374     {
375         if (_resourceAliases == null)
376             return null;
377         return (String)_resourceAliases.get(alias);
378     }
379 
380     /* ------------------------------------------------------------ */
381     public String removeResourceAlias(String alias)
382     {
383         if (_resourceAliases == null)
384             return null;
385         return (String)_resourceAliases.remove(alias);
386     }
387 
388     /* ------------------------------------------------------------ */
389     /* (non-Javadoc)
390      * @see org.mortbay.jetty.handler.ContextHandler#setClassLoader(java.lang.ClassLoader)
391      */
392     public void setClassLoader(ClassLoader classLoader)
393     {
394         super.setClassLoader(classLoader);
395         
396 //        if ( !(classLoader instanceof WebAppClassLoader) )
397 //        {
398 //            Log.info("NOTE: detected a classloader which is not an instance of WebAppClassLoader being set on WebAppContext, some typical class and resource locations may be missing on: " + toString() );
399 //        }
400         
401         if (classLoader!=null && classLoader instanceof WebAppClassLoader)
402             ((WebAppClassLoader)classLoader).setName(getDisplayName());
403     }
404     
405     /* ------------------------------------------------------------ */
406     public Resource getResource(String uriInContext) throws MalformedURLException
407     {
408         IOException ioe= null;
409         Resource resource= null;
410         int loop=0;
411         while (uriInContext!=null && loop++<100)
412         {
413             try
414             {
415                 resource= super.getResource(uriInContext);
416                 if (resource != null && resource.exists())
417                     return resource;
418                 
419                 uriInContext = getResourceAlias(uriInContext);
420             }
421             catch (IOException e)
422             {
423                 Log.ignore(e);
424                 if (ioe==null)
425                     ioe= e;
426             }
427         }
428 
429         if (ioe != null && ioe instanceof MalformedURLException)
430             throw (MalformedURLException)ioe;
431 
432         return resource;
433     }
434     
435 
436     /* ------------------------------------------------------------ */
437     /** 
438      * @see org.mortbay.jetty.handler.ContextHandler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
439      */
440     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
441     throws IOException, ServletException
442     {   
443         if (_unavailable)
444         {
445 	    Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
446 	    base_request.setHandled(true);
447             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
448         }
449         else
450             super.handle(target, request, response, dispatch);
451     }
452 
453     /* ------------------------------------------------------------ */
454     /* 
455      * @see org.mortbay.thread.AbstractLifeCycle#doStart()
456      */
457     protected void doStart() throws Exception
458     {
459         try
460         {
461             // Setup configurations 
462             loadConfigurations();
463 
464             for (int i=0;i<_configurations.length;i++)
465                 _configurations[i].setWebAppContext(this);
466 
467             // Configure classloader
468             _ownClassLoader=false;
469             if (getClassLoader()==null)
470             {
471                 WebAppClassLoader classLoader = new WebAppClassLoader(this);
472                 setClassLoader(classLoader);
473                 _ownClassLoader=true;
474             }
475 
476             if (Log.isDebugEnabled()) 
477             {
478                 ClassLoader loader = getClassLoader();
479                 Log.debug("Thread Context class loader is: " + loader);
480                 loader=loader.getParent();
481                 while(loader!=null)
482                 {
483                     Log.debug("Parent class loader is: " + loader); 
484                     loader=loader.getParent();
485                 }
486             }
487 
488             for (int i=0;i<_configurations.length;i++)
489                 _configurations[i].configureClassLoader();
490 
491             getTempDirectory();
492             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory())
493             {
494                 File sentinel = new File(_tmpDir, ".active");
495                 if(!sentinel.exists())
496                     sentinel.mkdir();
497             }
498 
499             super.doStart();
500 
501             if (isLogUrlOnStart()) 
502                 dumpUrl();
503         }
504         catch (Exception e)
505         {
506             //start up of the webapp context failed, make sure it is not started
507             Log.warn("Failed startup of context "+this, e);
508             _unavailableException=e;
509             _unavailable = true;
510         }
511     }
512 
513     /* ------------------------------------------------------------ */
514     /*
515      * Dumps the current web app name and URL to the log
516      */
517     public void dumpUrl() 
518     {
519         Connector[] connectors = getServer().getConnectors();
520         for (int i=0;i<connectors.length;i++) 
521         {
522             String connectorName = connectors[i].getName();
523             String displayName = getDisplayName();
524             if (displayName == null)
525                 displayName = "WebApp@"+connectors.hashCode();
526            
527             Log.info(displayName + " at http://" + connectorName + getContextPath());
528         }
529     }
530 
531     /* ------------------------------------------------------------ */
532     /* 
533      * @see org.mortbay.thread.AbstractLifeCycle#doStop()
534      */
535     protected void doStop() throws Exception
536     {
537         super.doStop();
538 
539         try
540         {
541             // Configure classloader
542             for (int i=_configurations.length;i-->0;)
543                 _configurations[i].deconfigureWebApp();
544             _configurations=null;
545             
546             // restore security handler
547             if (_securityHandler.getHandler()==null)
548             {
549                 _sessionHandler.setHandler(_securityHandler);
550                 _securityHandler.setHandler(_servletHandler);
551             }
552             
553             // delete temp directory if we had to create it or if it isn't called work
554             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory()) //_tmpDir!=null && !"work".equals(_tmpDir.getName()))
555             {
556                 IO.delete(_tmpDir);
557                 _tmpDir=null;
558             }
559         }
560         finally
561         {
562             if (_ownClassLoader)
563                 setClassLoader(null);
564             
565             _unavailable = false;
566             _unavailableException=null;
567         }
568     }
569     
570     /* ------------------------------------------------------------ */
571     /**
572      * @return Returns the configurations.
573      */
574     public String[] getConfigurationClasses()
575     {
576         return _configurationClasses;
577     }
578     
579     /* ------------------------------------------------------------ */
580     /**
581      * @return Returns the configurations.
582      */
583     public Configuration[] getConfigurations()
584     {
585         return _configurations;
586     }
587     
588     /* ------------------------------------------------------------ */
589     /**
590      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
591      * @return Returns the defaultsDescriptor.
592      */
593     public String getDefaultsDescriptor()
594     {
595         return _defaultsDescriptor;
596     }
597     
598     /* ------------------------------------------------------------ */
599     /**
600      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
601      * @return Returns the Override Descriptor.
602      */
603     public String getOverrideDescriptor()
604     {
605         return _overrideDescriptor;
606     }
607     
608     /* ------------------------------------------------------------ */
609     /**
610      * @return Returns the permissions.
611      */
612     public PermissionCollection getPermissions()
613     {
614         return _permissions;
615     }
616     
617 
618     /* ------------------------------------------------------------ */
619     /**
620      * @return Returns the serverClasses.
621      */
622     public String[] getServerClasses()
623     {
624         return _serverClasses;
625     }
626     
627     
628     /* ------------------------------------------------------------ */
629     /**
630      * @return Returns the systemClasses.
631      */
632     public String[] getSystemClasses()
633     {
634         return _systemClasses;
635     }
636     
637     /* ------------------------------------------------------------ */
638     /**
639      * Get a temporary directory in which to unpack the war etc etc.
640      * The algorithm for determining this is to check these alternatives
641      * in the order shown:
642      * 
643      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
644      * <ol>
645      * <li>
646      * Iff an explicit directory is set for this webapp, use it. Do NOT set
647      * delete on exit.
648      * </li>
649      * <li>
650      * Iff javax.servlet.context.tempdir context attribute is set for
651      * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
652      * </li>
653      * </ol>
654      * 
655      * <p>B. Create a directory based on global settings. The new directory 
656      * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
657      * Work out where to create this directory:
658      * <ol>
659      * <li>
660      * Iff $(jetty.home)/work exists create the directory there. Do NOT
661      * set delete on exit. Do NOT delete contents if dir already exists.
662      * </li>
663      * <li>
664      * Iff WEB-INF/work exists create the directory there. Do NOT set
665      * delete on exit. Do NOT delete contents if dir already exists.
666      * </li>
667      * <li>
668      * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
669      * contents if dir already exists.
670      * </li>
671      * </ol>
672      * 
673      * @return
674      */
675     public File getTempDirectory()
676     {
677         if (_tmpDir!=null && _tmpDir.isDirectory() && _tmpDir.canWrite())
678             return _tmpDir;
679 
680         // Initialize temporary directory
681         //
682         // I'm afraid that this is very much black magic.
683         // but if you can think of better....
684         Object t = getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR);
685 
686         if (t!=null && (t instanceof File))
687         {
688             _tmpDir=(File)t;
689             if (_tmpDir.isDirectory() && _tmpDir.canWrite())
690                 return _tmpDir;
691         }
692 
693         if (t!=null && (t instanceof String))
694         {
695             try
696             {
697                 _tmpDir=new File((String)t);
698 
699                 if (_tmpDir.isDirectory() && _tmpDir.canWrite())
700                 {
701                     if(Log.isDebugEnabled())Log.debug("Converted to File "+_tmpDir+" for "+this);
702                     setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
703                     return _tmpDir;
704                 }
705             }
706             catch(Exception e)
707             {
708                 Log.warn(Log.EXCEPTION,e);
709             }
710         }
711 
712         // No tempdir so look for a work directory to use as tempDir base
713         File work=null;
714         try
715         {
716             File w=new File(System.getProperty("jetty.home"),"work");
717             if (w.exists() && w.canWrite() && w.isDirectory())
718                 work=w;
719             else if (getBaseResource()!=null)
720             {
721                 Resource web_inf = getWebInf();
722                 if (web_inf !=null && web_inf.exists())
723                 {
724                     w=new File(web_inf.getFile(),"work");
725                     if (w.exists() && w.canWrite() && w.isDirectory())
726                         work=w;
727                 }
728             }
729         }
730         catch(Exception e)
731         {
732             Log.ignore(e);
733         }
734 
735         // No tempdir set so make one!
736         try
737         {
738            
739            String temp = getCanonicalNameForWebAppTmpDir();
740             
741             if (work!=null)
742                 _tmpDir=new File(work,temp);
743             else
744             {
745                 _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
746                 
747                 if (_tmpDir.exists())
748                 {
749                     if(Log.isDebugEnabled())Log.debug("Delete existing temp dir "+_tmpDir+" for "+this);
750                     if (!IO.delete(_tmpDir))
751                     {
752                         if(Log.isDebugEnabled())Log.debug("Failed to delete temp dir "+_tmpDir);
753                     }
754                 
755                     if (_tmpDir.exists())
756                     {
757                         String old=_tmpDir.toString();
758                         _tmpDir=File.createTempFile(temp+"_","");
759                         if (_tmpDir.exists())
760                             _tmpDir.delete();
761                         Log.warn("Can't reuse "+old+", using "+_tmpDir);
762                     }
763                 }
764             }
765 
766             if (!_tmpDir.exists())
767                 _tmpDir.mkdir();
768             
769             //if not in a dir called "work" then we want to delete it on jvm exit
770             if (!isTempWorkDirectory())
771                 _tmpDir.deleteOnExit();
772             if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
773         }
774         catch(Exception e)
775         {
776             _tmpDir=null;
777             Log.ignore(e);
778         }
779 
780         if (_tmpDir==null)
781         {
782             try{
783                 // that didn't work, so try something simpler (ish)
784                 _tmpDir=File.createTempFile("JettyContext","");
785                 if (_tmpDir.exists())
786                     _tmpDir.delete();
787                 _tmpDir.mkdir();
788                 _tmpDir.deleteOnExit();
789                 if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
790             }
791             catch(IOException e)
792             {
793                 Log.warn("tmpdir",e); System.exit(1);
794             }
795         }
796 
797         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
798         return _tmpDir;
799     }
800     
801     /**
802      * Check if the _tmpDir itself is called "work", or if the _tmpDir
803      * is in a directory called "work".
804      * @return
805      */
806     public boolean isTempWorkDirectory ()
807     {
808         if (_tmpDir == null)
809             return false;
810         if (_tmpDir.getName().equalsIgnoreCase("work"))
811             return true;
812         File t = _tmpDir.getParentFile();
813         if (t == null)
814             return false;
815         return (t.getName().equalsIgnoreCase("work"));
816     }
817     
818     /* ------------------------------------------------------------ */
819     /**
820      * @return Returns the war as a file or URL string (Resource)
821      */
822     public String getWar()
823     {
824         if (_war==null)
825             _war=getResourceBase();
826         return _war;
827     }
828 
829     /* ------------------------------------------------------------ */
830     public Resource getWebInf() throws IOException
831     {
832         resolveWebApp();
833 
834         // Iw there a WEB-INF directory?
835         Resource web_inf= super.getBaseResource().addPath("WEB-INF/");
836         if (!web_inf.exists() || !web_inf.isDirectory())
837             return null;
838         
839         return web_inf;
840     }
841     
842     /* ------------------------------------------------------------ */
843     /**
844      * @return Returns the distributable.
845      */
846     public boolean isDistributable()
847     {
848         return _distributable;
849     }
850 
851     /* ------------------------------------------------------------ */
852     /**
853      * @return Returns the extractWAR.
854      */
855     public boolean isExtractWAR()
856     {
857         return _extractWAR;
858     }
859 
860     /* ------------------------------------------------------------ */
861     /**
862      * @return True if the webdir is copied (to allow hot replacement of jars)
863      */
864     public boolean isCopyWebDir()
865     {
866         return _copyDir;
867     }
868     
869     /* ------------------------------------------------------------ */
870     /**
871      * @return Returns the java2compliant.
872      */
873     public boolean isParentLoaderPriority()
874     {
875         return _parentLoaderPriority;
876     }
877     
878     /* ------------------------------------------------------------ */
879     protected void loadConfigurations() 
880     	throws Exception
881     {
882         if (_configurations!=null)
883             return;
884         if (_configurationClasses==null)
885             _configurationClasses=__dftConfigurationClasses;
886         
887         _configurations = new Configuration[_configurationClasses.length];
888         for (int i=0;i<_configurations.length;i++)
889         {
890             _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
891         }
892     }
893     
894     /* ------------------------------------------------------------ */
895     protected boolean isProtectedTarget(String target)
896     {
897         while (target.startsWith("//"))
898             target=URIUtil.compactPath(target);
899          
900         return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf");
901     }
902     
903 
904     /* ------------------------------------------------------------ */
905     public String toString()
906     {
907         return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+(_war==null?getResourceBase():_war)+"}";
908     }
909     
910     /* ------------------------------------------------------------ */
911     /** Resolve Web App directory
912      * If the BaseResource has not been set, use the war resource to
913      * derive a webapp resource (expanding WAR if required).
914      */
915     protected void resolveWebApp() throws IOException
916     {
917         Resource web_app = super.getBaseResource();
918         if (web_app == null)
919         {
920             if (_war==null || _war.length()==0)
921                 _war=getResourceBase();
922             
923             // Set dir or WAR
924             web_app= Resource.newResource(_war);
925 
926             // Accept aliases for WAR files
927             if (web_app.getAlias() != null)
928             {
929                 Log.debug(web_app + " anti-aliased to " + web_app.getAlias());
930                 web_app= Resource.newResource(web_app.getAlias());
931             }
932 
933             if (Log.isDebugEnabled())
934                 Log.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory());
935 
936             // Is the WAR usable directly?
937             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
938             {
939                 // No - then lets see if it can be turned into a jar URL.
940                 Resource jarWebApp= Resource.newResource("jar:" + web_app + "!/");
941                 if (jarWebApp.exists() && jarWebApp.isDirectory())
942                 {
943                     web_app= jarWebApp;
944                 }
945             }
946 
947             // If we should extract or the URL is still not usable
948             if (web_app.exists()  && (
949                (_copyDir && web_app.getFile()!= null && web_app.getFile().isDirectory()) 
950                ||
951                (_extractWAR && web_app.getFile()!= null && !web_app.getFile().isDirectory())
952                ||
953                (_extractWAR && web_app.getFile() == null)
954                ||
955                !web_app.isDirectory()
956                ))
957             {
958                 // Then extract it if necessary.
959                 File extractedWebAppDir= new File(getTempDirectory(), "webapp");
960                 
961                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
962                 {
963                     // Copy directory
964                     Log.info("Copy " + web_app.getFile() + " to " + extractedWebAppDir);
965                     IO.copyDir(web_app.getFile(),extractedWebAppDir);
966                 }
967                 else
968                 {
969                     if (!extractedWebAppDir.exists())
970                     {
971                         //it hasn't been extracted before so extract it
972                         extractedWebAppDir.mkdir();
973                         Log.info("Extract " + _war + " to " + extractedWebAppDir);
974                         JarResource.extract(web_app, extractedWebAppDir, false);
975                     }
976                     else
977                     {
978                         //only extract if the war file is newer
979                         if (web_app.lastModified() > extractedWebAppDir.lastModified())
980                         {
981 			    IO.delete(extractedWebAppDir);
982                             extractedWebAppDir.mkdir();
983                             Log.info("Extract " + _war + " to " + extractedWebAppDir);
984                             JarResource.extract(web_app, extractedWebAppDir, false);
985                         }
986                     }
987                 }
988                 
989                 web_app= Resource.newResource(extractedWebAppDir.getCanonicalPath());
990 
991             }
992 
993             // Now do we have something usable?
994             if (!web_app.exists() || !web_app.isDirectory())
995             {
996                 Log.warn("Web application not found " + _war);
997                 throw new java.io.FileNotFoundException(_war);
998             }
999 
1000             if (Log.isDebugEnabled())
1001                 Log.debug("webapp=" + web_app);
1002 
1003             // ResourcePath
1004             super.setBaseResource(web_app);
1005         }
1006     }
1007     
1008 
1009     /* ------------------------------------------------------------ */
1010     /**
1011      * @param configurations The configuration class names.  If setConfigurations is not called
1012      * these classes are used to create a configurations array.
1013      */
1014     public void setConfigurationClasses(String[] configurations)
1015     {
1016         if (isRunning())
1017             throw new IllegalStateException("Running");
1018         _configurationClasses = configurations==null?null:(String[])configurations.clone();
1019     }
1020     
1021     /* ------------------------------------------------------------ */
1022     /**
1023      * @param configurations The configurations to set.
1024      */
1025     public void setConfigurations(Configuration[] configurations)
1026     {
1027         if (isRunning())
1028             throw new IllegalStateException("Running");
1029         _configurations = configurations==null?null:(Configuration[])configurations.clone();
1030     }
1031 
1032     /* ------------------------------------------------------------ */
1033     /** 
1034      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
1035      * @param defaultsDescriptor The defaultsDescriptor to set.
1036      */
1037     public void setDefaultsDescriptor(String defaultsDescriptor)
1038     {
1039         if (isRunning())
1040             throw new IllegalStateException("Running");
1041         _defaultsDescriptor = defaultsDescriptor;
1042     }
1043 
1044     /* ------------------------------------------------------------ */
1045     /**
1046      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
1047      * @param defaultsDescriptor The overrideDescritpor to set.
1048      */
1049     public void setOverrideDescriptor(String overrideDescriptor)
1050     {
1051         if (isRunning())
1052             throw new IllegalStateException("Running");
1053         _overrideDescriptor = overrideDescriptor;
1054     }
1055 
1056     /* ------------------------------------------------------------ */
1057     /**
1058      * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
1059      */
1060     public String getDescriptor()
1061     {
1062         return _descriptor;
1063     }
1064 
1065     /* ------------------------------------------------------------ */
1066     /**
1067      * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
1068      */
1069     public void setDescriptor(String descriptor)
1070     {
1071         if (isRunning())
1072             throw new IllegalStateException("Running");
1073         _descriptor=descriptor;
1074     }
1075     
1076     /* ------------------------------------------------------------ */
1077     /**
1078      * @param distributable The distributable to set.
1079      */
1080     public void setDistributable(boolean distributable)
1081     {
1082         this._distributable = distributable;
1083     }
1084 
1085     /* ------------------------------------------------------------ */
1086     public void setEventListeners(EventListener[] eventListeners)
1087     {
1088         if (_sessionHandler!=null)
1089             _sessionHandler.clearEventListeners();
1090             
1091         super.setEventListeners(eventListeners);
1092       
1093         for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
1094         {
1095             EventListener listener = eventListeners[i];
1096             
1097             if ((listener instanceof HttpSessionActivationListener)
1098                             || (listener instanceof HttpSessionAttributeListener)
1099                             || (listener instanceof HttpSessionBindingListener)
1100                             || (listener instanceof HttpSessionListener))
1101             {
1102                 if (_sessionHandler!=null)
1103                     _sessionHandler.addEventListener(listener);
1104             }
1105             
1106         }
1107     }
1108 
1109     /* ------------------------------------------------------------ */
1110     /** Add EventListener
1111      * Conveniance method that calls {@link #setEventListeners(EventListener[])}
1112      * @param listener
1113      */
1114     public void addEventListener(EventListener listener)
1115     {
1116         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));   
1117     }
1118 
1119     
1120     /* ------------------------------------------------------------ */
1121     /**
1122      * @param extractWAR True if war files are extracted
1123      */
1124     public void setExtractWAR(boolean extractWAR)
1125     {
1126         _extractWAR = extractWAR;
1127     }
1128     
1129     /* ------------------------------------------------------------ */
1130     /**
1131      * 
1132      * @param copy True if the webdir is copied (to allow hot replacement of jars)
1133      */
1134     public void setCopyWebDir(boolean copy)
1135     {
1136         _copyDir = copy;
1137     }
1138 
1139     /* ------------------------------------------------------------ */
1140     /**
1141      * @param java2compliant The java2compliant to set.
1142      */
1143     public void setParentLoaderPriority(boolean java2compliant)
1144     {
1145         _parentLoaderPriority = java2compliant;
1146     }
1147 
1148     /* ------------------------------------------------------------ */
1149     /**
1150      * @param permissions The permissions to set.
1151      */
1152     public void setPermissions(PermissionCollection permissions)
1153     {
1154         _permissions = permissions;
1155     }
1156 
1157     /* ------------------------------------------------------------ */
1158     /**
1159      * @param serverClasses The serverClasses to set.
1160      */
1161     public void setServerClasses(String[] serverClasses) 
1162     {
1163         _serverClasses = serverClasses==null?null:(String[])serverClasses.clone();
1164     }
1165     
1166     /* ------------------------------------------------------------ */
1167     /**
1168      * @param systemClasses The systemClasses to set.
1169      */
1170     public void setSystemClasses(String[] systemClasses)
1171     {
1172         _systemClasses = systemClasses==null?null:(String[])systemClasses.clone();
1173     }
1174     
1175 
1176     /* ------------------------------------------------------------ */
1177     /** Set temporary directory for context.
1178      * The javax.servlet.context.tempdir attribute is also set.
1179      * @param dir Writable temporary directory.
1180      */
1181     public void setTempDirectory(File dir)
1182     {
1183         if (isStarted())
1184             throw new IllegalStateException("Started");
1185 
1186         if (dir!=null)
1187         {
1188             try{dir=new File(dir.getCanonicalPath());}
1189             catch (IOException e){Log.warn(Log.EXCEPTION,e);}
1190         }
1191 
1192         if (dir!=null && !dir.exists())
1193         {
1194             dir.mkdir();
1195             dir.deleteOnExit();
1196         }
1197         else if (dir != null)
1198             _isExistingTmpDir = true;
1199 
1200         if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
1201             throw new IllegalArgumentException("Bad temp directory: "+dir);
1202 
1203         _tmpDir=dir;
1204         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
1205     }
1206     
1207     /* ------------------------------------------------------------ */
1208     /**
1209      * @param war The war to set as a file name or URL
1210      */
1211     public void setWar(String war)
1212     {
1213         _war = war;
1214     }
1215 
1216 
1217     /* ------------------------------------------------------------ */
1218     /**
1219      * @return Comma or semicolon separated path of filenames or URLs
1220      * pointing to directories or jar files. Directories should end
1221      * with '/'.
1222      */
1223     public String getExtraClasspath()
1224     {
1225         return _extraClasspath;
1226     }
1227 
1228     /* ------------------------------------------------------------ */
1229     /**
1230      * @param extraClasspath Comma or semicolon separated path of filenames or URLs
1231      * pointing to directories or jar files. Directories should end
1232      * with '/'.
1233      */
1234     public void setExtraClasspath(String extraClasspath)
1235     {
1236         _extraClasspath=extraClasspath;
1237     }
1238 
1239     /* ------------------------------------------------------------ */
1240     public boolean isLogUrlOnStart() 
1241     {
1242         return _logUrlOnStart;
1243     }
1244 
1245     /* ------------------------------------------------------------ */
1246     /**
1247      * Sets whether or not the web app name and URL is logged on startup
1248      *
1249      * @param logOnStart whether or not the log message is created
1250      */
1251     public void setLogUrlOnStart(boolean logOnStart) 
1252     {
1253         this._logUrlOnStart = logOnStart;
1254     }
1255 
1256     /* ------------------------------------------------------------ */
1257     protected void startContext()
1258         throws Exception
1259     {
1260         // Configure defaults
1261         for (int i=0;i<_configurations.length;i++)
1262             _configurations[i].configureDefaults();
1263         
1264         // Is there a WEB-INF work directory
1265         Resource web_inf=getWebInf();
1266         if (web_inf!=null)
1267         {
1268             Resource work= web_inf.addPath("work");
1269             if (work.exists()
1270                             && work.isDirectory()
1271                             && work.getFile() != null
1272                             && work.getFile().canWrite()
1273                             && getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR) == null)
1274                 setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR, work.getFile());
1275         }
1276         
1277         // Configure webapp
1278         for (int i=0;i<_configurations.length;i++)
1279             _configurations[i].configureWebApp();
1280 
1281         
1282         super.startContext();
1283     }
1284     
1285     /**
1286      * Create a canonical name for a webapp tmp directory.
1287      * The form of the name is:
1288      *  "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36 hashcode of whole string
1289      *  
1290      *  host and port uniquely identify the server
1291      *  context and virtual host uniquely identify the webapp
1292      * @return
1293      */
1294     private String getCanonicalNameForWebAppTmpDir ()
1295     {
1296         StringBuffer canonicalName = new StringBuffer();
1297         canonicalName.append("Jetty");
1298        
1299         //get the host and the port from the first connector 
1300         Connector[] connectors = getServer().getConnectors();
1301         
1302         
1303         //Get the host
1304         canonicalName.append("_");
1305         String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
1306         if (host == null)
1307             host = "0.0.0.0";
1308         canonicalName.append(host.replace('.', '_'));
1309         
1310         //Get the port
1311         canonicalName.append("_");
1312         //try getting the real port being listened on
1313         int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
1314         //if not available (eg no connectors or connector not started), 
1315         //try getting one that was configured.
1316         if (port < 0)
1317             port = connectors[0].getPort();
1318         canonicalName.append(port);
1319 
1320        
1321         //Resource  base
1322         canonicalName.append("_");
1323         try
1324         {
1325             Resource resource = super.getBaseResource();
1326             if (resource == null)
1327             {
1328                 if (_war==null || _war.length()==0)
1329                     resource=Resource.newResource(getResourceBase());
1330                 
1331                 // Set dir or WAR
1332                 resource= Resource.newResource(_war);
1333             }
1334                 
1335             String tmp = URIUtil.decodePath(resource.getURL().getPath());
1336             if (tmp.endsWith("/"))
1337                 tmp = tmp.substring(0, tmp.length()-1);
1338             if (tmp.endsWith("!"))
1339                 tmp = tmp.substring(0, tmp.length() -1);
1340             //get just the last part which is the filename
1341             int i = tmp.lastIndexOf("/");
1342             
1343             canonicalName.append(tmp.substring(i+1, tmp.length()));
1344         }
1345         catch (Exception e)
1346         {
1347             Log.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
1348         }
1349             
1350         //Context name
1351         canonicalName.append("_");
1352         String contextPath = getContextPath();
1353         contextPath=contextPath.replace('/','_');
1354         contextPath=contextPath.replace('\\','_');
1355         canonicalName.append(contextPath);
1356         
1357         //Virtual host (if there is one)
1358         canonicalName.append("_");
1359         String[] vhosts = getVirtualHosts();
1360         canonicalName.append((vhosts==null||vhosts[0]==null?"":vhosts[0]));
1361         
1362         //base36 hash of the whole string for uniqueness
1363         String hash = Integer.toString(canonicalName.toString().hashCode(),36);
1364         canonicalName.append("_");
1365         canonicalName.append(hash);
1366         
1367         // sanitize
1368         for (int i=0;i<canonicalName.length();i++)
1369         {
1370         	char c=canonicalName.charAt(i);
1371         	if (!Character.isJavaIdentifierPart(c))
1372         		canonicalName.setCharAt(i,'.');
1373         }
1374   
1375         return canonicalName.toString();
1376     }
1377 }