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