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