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