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             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory())
460             {
461                 File sentinel = new File(_tmpDir, ".active");
462                 if(!sentinel.exists())
463                     sentinel.mkdir();
464             }
465 
466             super.doStart();
467 
468             if (isLogUrlOnStart()) 
469                 dumpUrl();
470         }
471         catch (Exception e)
472         {
473             //start up of the webapp context failed, make sure it is not started
474             Log.warn("Failed startup of context "+this, e);
475             _unavailableException=e;
476             _unavailable = true;
477         }
478     }
479 
480     /* ------------------------------------------------------------ */
481     /*
482      * Dumps the current web app name and URL to the log
483      */
484     public void dumpUrl() 
485     {
486         Connector[] connectors = getServer().getConnectors();
487         for (int i=0;i<connectors.length;i++) 
488         {
489             String connectorName = connectors[i].getName();
490             String displayName = getDisplayName();
491             if (displayName == null)
492                 displayName = "WebApp@"+connectors.hashCode();
493            
494             Log.info(displayName + " at http://" + connectorName + getContextPath());
495         }
496     }
497 
498     /* ------------------------------------------------------------ */
499     /* 
500      * @see org.mortbay.thread.AbstractLifeCycle#doStop()
501      */
502     protected void doStop() throws Exception
503     {
504         super.doStop();
505 
506         try
507         {
508             // Configure classloader
509             for (int i=_configurations.length;i-->0;)
510                 _configurations[i].deconfigureWebApp();
511             _configurations=null;
512             
513             // restore security handler
514             if (_securityHandler.getHandler()==null)
515             {
516                 _sessionHandler.setHandler(_securityHandler);
517                 _securityHandler.setHandler(_servletHandler);
518             }
519             
520             // delete temp directory if we had to create it or if it isn't called work
521             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory()) //_tmpDir!=null && !"work".equals(_tmpDir.getName()))
522             {
523                 IO.delete(_tmpDir);
524                 _tmpDir=null;
525             }
526         }
527         finally
528         {
529             if (_ownClassLoader)
530                 setClassLoader(null);
531             
532             _unavailable = false;
533             _unavailableException=null;
534         }
535     }
536     
537     /* ------------------------------------------------------------ */
538     /**
539      * @return Returns the configurations.
540      */
541     public String[] getConfigurationClasses()
542     {
543         return _configurationClasses;
544     }
545     
546     /* ------------------------------------------------------------ */
547     /**
548      * @return Returns the configurations.
549      */
550     public Configuration[] getConfigurations()
551     {
552         return _configurations;
553     }
554     
555     /* ------------------------------------------------------------ */
556     /**
557      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
558      * @return Returns the defaultsDescriptor.
559      */
560     public String getDefaultsDescriptor()
561     {
562         return _defaultsDescriptor;
563     }
564     
565     /* ------------------------------------------------------------ */
566     /**
567      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
568      * @return Returns the Override Descriptor.
569      */
570     public String getOverrideDescriptor()
571     {
572         return _overrideDescriptor;
573     }
574     
575     /* ------------------------------------------------------------ */
576     /**
577      * @return Returns the permissions.
578      */
579     public PermissionCollection getPermissions()
580     {
581         return _permissions;
582     }
583     
584 
585     /* ------------------------------------------------------------ */
586     /**
587      * @return Returns the serverClasses.
588      */
589     public String[] getServerClasses()
590     {
591         return _serverClasses;
592     }
593     
594     
595     /* ------------------------------------------------------------ */
596     /**
597      * @return Returns the systemClasses.
598      */
599     public String[] getSystemClasses()
600     {
601         return _systemClasses;
602     }
603     
604     /* ------------------------------------------------------------ */
605     /**
606      * Get a temporary directory in which to unpack the war etc etc.
607      * The algorithm for determining this is to check these alternatives
608      * in the order shown:
609      * 
610      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
611      * <ol>
612      * <li>
613      * Iff an explicit directory is set for this webapp, use it. Do NOT set
614      * delete on exit.
615      * </li>
616      * <li>
617      * Iff javax.servlet.context.tempdir context attribute is set for
618      * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
619      * </li>
620      * </ol>
621      * 
622      * <p>B. Create a directory based on global settings. The new directory 
623      * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
624      * Work out where to create this directory:
625      * <ol>
626      * <li>
627      * Iff $(jetty.home)/work exists create the directory there. Do NOT
628      * set delete on exit. Do NOT delete contents if dir already exists.
629      * </li>
630      * <li>
631      * Iff WEB-INF/work exists create the directory there. Do NOT set
632      * delete on exit. Do NOT delete contents if dir already exists.
633      * </li>
634      * <li>
635      * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
636      * contents if dir already exists.
637      * </li>
638      * </ol>
639      * 
640      * @return
641      */
642     public File getTempDirectory()
643     {
644         if (_tmpDir!=null && _tmpDir.isDirectory() && _tmpDir.canWrite())
645             return _tmpDir;
646 
647         // Initialize temporary directory
648         //
649         // I'm afraid that this is very much black magic.
650         // but if you can think of better....
651         Object t = getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR);
652 
653         if (t!=null && (t instanceof File))
654         {
655             _tmpDir=(File)t;
656             if (_tmpDir.isDirectory() && _tmpDir.canWrite())
657                 return _tmpDir;
658         }
659 
660         if (t!=null && (t instanceof String))
661         {
662             try
663             {
664                 _tmpDir=new File((String)t);
665 
666                 if (_tmpDir.isDirectory() && _tmpDir.canWrite())
667                 {
668                     if(Log.isDebugEnabled())Log.debug("Converted to File "+_tmpDir+" for "+this);
669                     setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
670                     return _tmpDir;
671                 }
672             }
673             catch(Exception e)
674             {
675                 Log.warn(Log.EXCEPTION,e);
676             }
677         }
678 
679         // No tempdir so look for a work directory to use as tempDir base
680         File work=null;
681         try
682         {
683             File w=new File(System.getProperty("jetty.home"),"work");
684             if (w.exists() && w.canWrite() && w.isDirectory())
685                 work=w;
686             else if (getBaseResource()!=null)
687             {
688                 Resource web_inf = getWebInf();
689                 if (web_inf !=null && web_inf.exists())
690                 {
691                     w=new File(web_inf.getFile(),"work");
692                     if (w.exists() && w.canWrite() && w.isDirectory())
693                         work=w;
694                 }
695             }
696         }
697         catch(Exception e)
698         {
699             Log.ignore(e);
700         }
701 
702         // No tempdir set so make one!
703         try
704         {
705            
706            String temp = getCanonicalNameForWebAppTmpDir();
707             
708             if (work!=null)
709                 _tmpDir=new File(work,temp);
710             else
711             {
712                 _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
713                 
714                 if (_tmpDir.exists())
715                 {
716                     if(Log.isDebugEnabled())Log.debug("Delete existing temp dir "+_tmpDir+" for "+this);
717                     if (!IO.delete(_tmpDir))
718                     {
719                         if(Log.isDebugEnabled())Log.debug("Failed to delete temp dir "+_tmpDir);
720                     }
721                 
722                     if (_tmpDir.exists())
723                     {
724                         String old=_tmpDir.toString();
725                         _tmpDir=File.createTempFile(temp+"_","");
726                         if (_tmpDir.exists())
727                             _tmpDir.delete();
728                         Log.warn("Can't reuse "+old+", using "+_tmpDir);
729                     }
730                 }
731             }
732 
733             if (!_tmpDir.exists())
734                 _tmpDir.mkdir();
735             
736             //if not in a dir called "work" then we want to delete it on jvm exit
737             if (!isTempWorkDirectory())
738                 _tmpDir.deleteOnExit();
739             if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
740         }
741         catch(Exception e)
742         {
743             _tmpDir=null;
744             Log.ignore(e);
745         }
746 
747         if (_tmpDir==null)
748         {
749             try{
750                 // that didn't work, so try something simpler (ish)
751                 _tmpDir=File.createTempFile("JettyContext","");
752                 if (_tmpDir.exists())
753                     _tmpDir.delete();
754                 _tmpDir.mkdir();
755                 _tmpDir.deleteOnExit();
756                 if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
757             }
758             catch(IOException e)
759             {
760                 Log.warn("tmpdir",e); System.exit(1);
761             }
762         }
763 
764         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
765         return _tmpDir;
766     }
767     
768     /**
769      * Check if the _tmpDir itself is called "work", or if the _tmpDir
770      * is in a directory called "work".
771      * @return
772      */
773     public boolean isTempWorkDirectory ()
774     {
775         if (_tmpDir == null)
776             return false;
777         if (_tmpDir.getName().equalsIgnoreCase("work"))
778             return true;
779         File t = _tmpDir.getParentFile();
780         if (t == null)
781             return false;
782         return (t.getName().equalsIgnoreCase("work"));
783     }
784     
785     /* ------------------------------------------------------------ */
786     /**
787      * @return Returns the war as a file or URL string (Resource)
788      */
789     public String getWar()
790     {
791         if (_war==null)
792             _war=getResourceBase();
793         return _war;
794     }
795 
796     /* ------------------------------------------------------------ */
797     public Resource getWebInf() throws IOException
798     {
799         resolveWebApp();
800 
801         // Iw there a WEB-INF directory?
802         Resource web_inf= super.getBaseResource().addPath("WEB-INF/");
803         if (!web_inf.exists() || !web_inf.isDirectory())
804             return null;
805         
806         return web_inf;
807     }
808     
809     /* ------------------------------------------------------------ */
810     /**
811      * @return Returns the distributable.
812      */
813     public boolean isDistributable()
814     {
815         return _distributable;
816     }
817 
818     /* ------------------------------------------------------------ */
819     /**
820      * @return Returns the extractWAR.
821      */
822     public boolean isExtractWAR()
823     {
824         return _extractWAR;
825     }
826 
827     /* ------------------------------------------------------------ */
828     /**
829      * @return True if the webdir is copied (to allow hot replacement of jars)
830      */
831     public boolean isCopyWebDir()
832     {
833         return _copyDir;
834     }
835     
836     /* ------------------------------------------------------------ */
837     /**
838      * @return Returns the java2compliant.
839      */
840     public boolean isParentLoaderPriority()
841     {
842         return _parentLoaderPriority;
843     }
844     
845     /* ------------------------------------------------------------ */
846     protected void loadConfigurations() 
847     	throws Exception
848     {
849         if (_configurations!=null)
850             return;
851         if (_configurationClasses==null)
852             _configurationClasses=__dftConfigurationClasses;
853         
854         _configurations = new Configuration[_configurationClasses.length];
855         for (int i=0;i<_configurations.length;i++)
856         {
857             _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
858         }
859     }
860     
861     /* ------------------------------------------------------------ */
862     protected boolean isProtectedTarget(String target)
863     {
864         while (target.startsWith("//"))
865             target=URIUtil.compactPath(target);
866          
867         return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf");
868     }
869     
870 
871     /* ------------------------------------------------------------ */
872     public String toString()
873     {
874         return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+(_war==null?getResourceBase():_war)+"}";
875     }
876     
877     /* ------------------------------------------------------------ */
878     /** Resolve Web App directory
879      * If the BaseResource has not been set, use the war resource to
880      * derive a webapp resource (expanding WAR if required).
881      */
882     protected void resolveWebApp() throws IOException
883     {
884         Resource web_app = super.getBaseResource();
885         if (web_app == null)
886         {
887             if (_war==null || _war.length()==0)
888                 _war=getResourceBase();
889             
890             // Set dir or WAR
891             web_app= Resource.newResource(_war);
892 
893             // Accept aliases for WAR files
894             if (web_app.getAlias() != null)
895             {
896                 Log.debug(web_app + " anti-aliased to " + web_app.getAlias());
897                 web_app= Resource.newResource(web_app.getAlias());
898             }
899 
900             if (Log.isDebugEnabled())
901                 Log.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory());
902 
903             // Is the WAR usable directly?
904             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
905             {
906                 // No - then lets see if it can be turned into a jar URL.
907                 Resource jarWebApp= Resource.newResource("jar:" + web_app + "!/");
908                 if (jarWebApp.exists() && jarWebApp.isDirectory())
909                 {
910                     web_app= jarWebApp;
911                 }
912             }
913 
914             // If we should extract or the URL is still not usable
915             if (web_app.exists()  && (
916                (_copyDir && web_app.getFile()!= null && web_app.getFile().isDirectory()) 
917                ||
918                (_extractWAR && web_app.getFile()!= null && !web_app.getFile().isDirectory())
919                ||
920                (_extractWAR && web_app.getFile() == null)
921                ||
922                !web_app.isDirectory()
923                ))
924             {
925                 // Then extract it if necessary.
926                 File extractedWebAppDir= new File(getTempDirectory(), "webapp");
927                 
928                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
929                 {
930                     // Copy directory
931                     Log.info("Copy " + web_app.getFile() + " to " + extractedWebAppDir);
932                     IO.copyDir(web_app.getFile(),extractedWebAppDir);
933                 }
934                 else
935                 {
936                     if (!extractedWebAppDir.exists())
937                     {
938                         //it hasn't been extracted before so extract it
939                         extractedWebAppDir.mkdir();
940                         Log.info("Extract " + _war + " to " + extractedWebAppDir);
941                         JarResource.extract(web_app, extractedWebAppDir, false);
942                     }
943                     else
944                     {
945                         //only extract if the war file is newer
946                         if (web_app.lastModified() > extractedWebAppDir.lastModified())
947                         {
948                             extractedWebAppDir.delete();
949                             extractedWebAppDir.mkdir();
950                             Log.info("Extract " + _war + " to " + extractedWebAppDir);
951                             JarResource.extract(web_app, extractedWebAppDir, false);
952                         }
953                     }
954                 }
955                 
956                 web_app= Resource.newResource(extractedWebAppDir.getCanonicalPath());
957 
958             }
959 
960             // Now do we have something usable?
961             if (!web_app.exists() || !web_app.isDirectory())
962             {
963                 Log.warn("Web application not found " + _war);
964                 throw new java.io.FileNotFoundException(_war);
965             }
966 
967             if (Log.isDebugEnabled())
968                 Log.debug("webapp=" + web_app);
969 
970             // ResourcePath
971             super.setBaseResource(web_app);
972         }
973     }
974     
975 
976     /* ------------------------------------------------------------ */
977     /**
978      * @param configurations The configuration class names.  If setConfigurations is not called
979      * these classes are used to create a configurations array.
980      */
981     public void setConfigurationClasses(String[] configurations)
982     {
983         _configurationClasses = configurations==null?null:(String[])configurations.clone();
984     }
985     
986     /* ------------------------------------------------------------ */
987     /**
988      * @param configurations The configurations to set.
989      */
990     public void setConfigurations(Configuration[] configurations)
991     {
992         _configurations = configurations==null?null:(Configuration[])configurations.clone();
993     }
994 
995     /* ------------------------------------------------------------ */
996     /** 
997      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
998      * @param defaultsDescriptor The defaultsDescriptor to set.
999      */
1000     public void setDefaultsDescriptor(String defaultsDescriptor)
1001     {
1002         _defaultsDescriptor = defaultsDescriptor;
1003     }
1004 
1005     /* ------------------------------------------------------------ */
1006     /**
1007      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
1008      * @param defaultsDescriptor The overrideDescritpor to set.
1009      */
1010     public void setOverrideDescriptor(String overrideDescriptor)
1011     {
1012         _overrideDescriptor = overrideDescriptor;
1013     }
1014 
1015     /* ------------------------------------------------------------ */
1016     /**
1017      * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
1018      */
1019     public String getDescriptor()
1020     {
1021         return _descriptor;
1022     }
1023 
1024     /* ------------------------------------------------------------ */
1025     /**
1026      * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
1027      */
1028     public void setDescriptor(String descriptor)
1029     {
1030         _descriptor=descriptor;
1031     }
1032     
1033     /* ------------------------------------------------------------ */
1034     /**
1035      * @param distributable The distributable to set.
1036      */
1037     public void setDistributable(boolean distributable)
1038     {
1039         this._distributable = distributable;
1040     }
1041 
1042     /* ------------------------------------------------------------ */
1043     public void setEventListeners(EventListener[] eventListeners)
1044     {
1045         if (_sessionHandler!=null)
1046             _sessionHandler.clearEventListeners();
1047             
1048         super.setEventListeners(eventListeners);
1049       
1050         for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
1051         {
1052             EventListener listener = eventListeners[i];
1053             
1054             if ((listener instanceof HttpSessionActivationListener)
1055                             || (listener instanceof HttpSessionAttributeListener)
1056                             || (listener instanceof HttpSessionBindingListener)
1057                             || (listener instanceof HttpSessionListener))
1058             {
1059                 if (_sessionHandler!=null)
1060                     _sessionHandler.addEventListener(listener);
1061             }
1062             
1063         }
1064     }
1065 
1066     /* ------------------------------------------------------------ */
1067     /** Add EventListener
1068      * Conveniance method that calls {@link #setEventListeners(EventListener[])}
1069      * @param listener
1070      */
1071     public void addEventListener(EventListener listener)
1072     {
1073         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));   
1074     }
1075 
1076     
1077     /* ------------------------------------------------------------ */
1078     /**
1079      * @param extractWAR True if war files are extracted
1080      */
1081     public void setExtractWAR(boolean extractWAR)
1082     {
1083         _extractWAR = extractWAR;
1084     }
1085     
1086     /* ------------------------------------------------------------ */
1087     /**
1088      * 
1089      * @param copy True if the webdir is copied (to allow hot replacement of jars)
1090      */
1091     public void setCopyWebDir(boolean copy)
1092     {
1093         _copyDir = copy;
1094     }
1095 
1096     /* ------------------------------------------------------------ */
1097     /**
1098      * @param java2compliant The java2compliant to set.
1099      */
1100     public void setParentLoaderPriority(boolean java2compliant)
1101     {
1102         _parentLoaderPriority = java2compliant;
1103     }
1104 
1105     /* ------------------------------------------------------------ */
1106     /**
1107      * @param permissions The permissions to set.
1108      */
1109     public void setPermissions(PermissionCollection permissions)
1110     {
1111         _permissions = permissions;
1112     }
1113 
1114     /* ------------------------------------------------------------ */
1115     /**
1116      * @param serverClasses The serverClasses to set.
1117      */
1118     public void setServerClasses(String[] serverClasses) 
1119     {
1120         _serverClasses = serverClasses==null?null:(String[])serverClasses.clone();
1121     }
1122     
1123     /* ------------------------------------------------------------ */
1124     /**
1125      * @param systemClasses The systemClasses to set.
1126      */
1127     public void setSystemClasses(String[] systemClasses)
1128     {
1129         _systemClasses = systemClasses==null?null:(String[])systemClasses.clone();
1130     }
1131     
1132 
1133     /* ------------------------------------------------------------ */
1134     /** Set temporary directory for context.
1135      * The javax.servlet.context.tempdir attribute is also set.
1136      * @param dir Writable temporary directory.
1137      */
1138     public void setTempDirectory(File dir)
1139     {
1140         if (isStarted())
1141             throw new IllegalStateException("Started");
1142 
1143         if (dir!=null)
1144         {
1145             try{dir=new File(dir.getCanonicalPath());}
1146             catch (IOException e){Log.warn(Log.EXCEPTION,e);}
1147         }
1148 
1149         if (dir!=null && !dir.exists())
1150         {
1151             dir.mkdir();
1152             dir.deleteOnExit();
1153         }
1154         else if (dir != null)
1155             _isExistingTmpDir = true;
1156 
1157         if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
1158             throw new IllegalArgumentException("Bad temp directory: "+dir);
1159 
1160         _tmpDir=dir;
1161         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
1162     }
1163     
1164     /* ------------------------------------------------------------ */
1165     /**
1166      * @param war The war to set as a file name or URL
1167      */
1168     public void setWar(String war)
1169     {
1170         _war = war;
1171     }
1172 
1173 
1174     /* ------------------------------------------------------------ */
1175     /**
1176      * @return Comma or semicolon separated path of filenames or URLs
1177      * pointing to directories or jar files. Directories should end
1178      * with '/'.
1179      */
1180     public String getExtraClasspath()
1181     {
1182         return _extraClasspath;
1183     }
1184 
1185     /* ------------------------------------------------------------ */
1186     /**
1187      * @param extraClasspath Comma or semicolon separated path of filenames or URLs
1188      * pointing to directories or jar files. Directories should end
1189      * with '/'.
1190      */
1191     public void setExtraClasspath(String extraClasspath)
1192     {
1193         _extraClasspath=extraClasspath;
1194     }
1195 
1196     /* ------------------------------------------------------------ */
1197     public boolean isLogUrlOnStart() 
1198     {
1199         return _logUrlOnStart;
1200     }
1201 
1202     /* ------------------------------------------------------------ */
1203     /**
1204      * Sets whether or not the web app name and URL is logged on startup
1205      *
1206      * @param logOnStart whether or not the log message is created
1207      */
1208     public void setLogUrlOnStart(boolean logOnStart) 
1209     {
1210         this._logUrlOnStart = logOnStart;
1211     }
1212 
1213     /* ------------------------------------------------------------ */
1214     protected void startContext()
1215         throws Exception
1216     {
1217         // Configure defaults
1218         for (int i=0;i<_configurations.length;i++)
1219             _configurations[i].configureDefaults();
1220         
1221         // Is there a WEB-INF work directory
1222         Resource web_inf=getWebInf();
1223         if (web_inf!=null)
1224         {
1225             Resource work= web_inf.addPath("work");
1226             if (work.exists()
1227                             && work.isDirectory()
1228                             && work.getFile() != null
1229                             && work.getFile().canWrite()
1230                             && getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR) == null)
1231                 setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR, work.getFile());
1232         }
1233         
1234         // Configure webapp
1235         for (int i=0;i<_configurations.length;i++)
1236             _configurations[i].configureWebApp();
1237 
1238         
1239         super.startContext();
1240     }
1241     
1242     /**
1243      * Create a canonical name for a webapp tmp directory.
1244      * The form of the name is:
1245      *  "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36 hashcode of whole string
1246      *  
1247      *  host and port uniquely identify the server
1248      *  context and virtual host uniquely identify the webapp
1249      * @return
1250      */
1251     private String getCanonicalNameForWebAppTmpDir ()
1252     {
1253         StringBuffer canonicalName = new StringBuffer();
1254         canonicalName.append("Jetty");
1255        
1256         //get the host and the port from the first connector 
1257         Connector[] connectors = getServer().getConnectors();
1258         
1259         
1260         //Get the host
1261         canonicalName.append("_");
1262         String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
1263         if (host == null)
1264             host = "0.0.0.0";
1265         canonicalName.append(host.replace('.', '_'));
1266         
1267         //Get the port
1268         canonicalName.append("_");
1269         //try getting the real port being listened on
1270         int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
1271         //if not available (eg no connectors or connector not started), 
1272         //try getting one that was configured.
1273         if (port < 0)
1274             port = connectors[0].getPort();
1275         canonicalName.append(port);
1276 
1277        
1278         //Resource  base
1279         canonicalName.append("_");
1280         try
1281         {
1282             Resource resource = super.getBaseResource();
1283             if (resource == null)
1284             {
1285                 if (_war==null || _war.length()==0)
1286                     resource=Resource.newResource(getResourceBase());
1287                 
1288                 // Set dir or WAR
1289                 resource= Resource.newResource(_war);
1290             }
1291                 
1292             String tmp = URIUtil.decodePath(resource.getURL().getPath());
1293             if (tmp.endsWith("/"))
1294                 tmp = tmp.substring(0, tmp.length()-1);
1295             if (tmp.endsWith("!"))
1296                 tmp = tmp.substring(0, tmp.length() -1);
1297             //get just the last part which is the filename
1298             int i = tmp.lastIndexOf("/");
1299             
1300             canonicalName.append(tmp.substring(i+1, tmp.length()));
1301         }
1302         catch (Exception e)
1303         {
1304             Log.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
1305         }
1306             
1307         //Context name
1308         canonicalName.append("_");
1309         String contextPath = getContextPath();
1310         contextPath=contextPath.replace('/','_');
1311         contextPath=contextPath.replace('\\','_');
1312         canonicalName.append(contextPath);
1313         
1314         //Virtual host (if there is one)
1315         canonicalName.append("_");
1316         String[] vhosts = getVirtualHosts();
1317         canonicalName.append((vhosts==null||vhosts[0]==null?"":vhosts[0]));
1318         
1319         //base36 hash of the whole string for uniqueness
1320         String hash = Integer.toString(canonicalName.toString().hashCode(),36);
1321         canonicalName.append("_");
1322         canonicalName.append(hash);
1323         
1324         // sanitize
1325         for (int i=0;i<canonicalName.length();i++)
1326         {
1327         	char c=canonicalName.charAt(i);
1328         	if (!Character.isJavaIdentifierPart(c))
1329         		canonicalName.setCharAt(i,'.');
1330         }
1331   
1332         return canonicalName.toString();
1333     }
1334 }