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