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