View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.webapp;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.net.MalformedURLException;
19  import java.net.URL;
20  import java.security.PermissionCollection;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.EventListener;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.servlet.ServletContext;
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.eclipse.jetty.security.SecurityHandler;
35  import org.eclipse.jetty.server.Connector;
36  import org.eclipse.jetty.server.HandlerContainer;
37  import org.eclipse.jetty.server.Server;
38  import org.eclipse.jetty.server.handler.ContextHandler;
39  import org.eclipse.jetty.server.handler.ErrorHandler;
40  import org.eclipse.jetty.server.session.SessionHandler;
41  import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
42  import org.eclipse.jetty.servlet.ServletContextHandler;
43  import org.eclipse.jetty.servlet.ServletHandler;
44  import org.eclipse.jetty.util.LazyList;
45  import org.eclipse.jetty.util.Loader;
46  import org.eclipse.jetty.util.MultiException;
47  import org.eclipse.jetty.util.StringUtil;
48  import org.eclipse.jetty.util.URIUtil;
49  import org.eclipse.jetty.util.log.Log;
50  import org.eclipse.jetty.util.log.Logger;
51  import org.eclipse.jetty.util.resource.Resource;
52  import org.eclipse.jetty.util.resource.ResourceCollection;
53  
54  /* ------------------------------------------------------------ */
55  /** Web Application Context Handler.
56   * The WebAppContext handler is an extension of ContextHandler that
57   * coordinates the construction and configuration of nested handlers:
58   * {@link org.eclipse.jetty.security.ConstraintSecurityHandler}, {@link org.eclipse.jetty.server.session.SessionHandler}
59   * and {@link org.eclipse.jetty.servlet.ServletHandler}.
60   * The handlers are configured by pluggable configuration classes, with
61   * the default being  {@link org.eclipse.jetty.webapp.WebXmlConfiguration} and
62   * {@link org.eclipse.jetty.webapp.JettyWebXmlConfiguration}.
63   * 
64   * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base"
65   * 
66   */
67  public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context
68  {
69      private static final Logger LOG = Log.getLogger(WebAppContext.class);
70  
71      public static final String TEMPDIR = "javax.servlet.context.tempdir";
72      public static final String BASETEMPDIR = "org.eclipse.jetty.webapp.basetempdir";
73      public final static String WEB_DEFAULTS_XML="org/eclipse/jetty/webapp/webdefault.xml";
74      public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
75      public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration";
76      public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";
77      public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses";
78      
79      private static String[] __dftConfigurationClasses =
80      {
81          "org.eclipse.jetty.webapp.WebInfConfiguration",
82          "org.eclipse.jetty.webapp.WebXmlConfiguration",
83          "org.eclipse.jetty.webapp.MetaInfConfiguration",
84          "org.eclipse.jetty.webapp.FragmentConfiguration",
85          "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
86          //"org.eclipse.jetty.webapp.TagLibConfiguration"
87      } ;
88      
89      // System classes are classes that cannot be replaced by 
90      // the web application, and they are *always* loaded via 
91      // system classloader.
92      public final static String[] __dftSystemClasses = 
93      {
94          "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) 
95          "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
96          "org.xml.",                         // needed by javax.xml
97          "org.w3c.",                         // needed by javax.xml
98          "org.apache.commons.logging.",      // TODO: review if special case still needed 
99          "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
100         "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
101         "org.eclipse.jetty.plus.jaas.",     // webapp cannot change jaas classes
102         "org.eclipse.jetty.websocket.",     // WebSocket is a jetty extension
103         "org.eclipse.jetty.servlet.DefaultServlet"  // webapp cannot change default servlets
104     } ;
105     
106     // Server classes are classes that are hidden from being
107     // loaded by the web application using system classloader,
108     // so if web application needs to load any of such classes,
109     // it has to include them in its distribution.
110     public final static String[] __dftServerClasses = 
111     {
112         "-org.eclipse.jetty.continuation.", // don't hide continuation classes
113         "-org.eclipse.jetty.jndi.",         // don't hide naming classes
114         "-org.eclipse.jetty.plus.jaas.",    // don't hide jaas classes
115         "-org.eclipse.jetty.websocket.",    // don't hide websocket extension
116         "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
117         "-org.eclipse.jetty.servlet.listener.", //don't hide useful listeners
118         "org.eclipse.jetty."                // hide other jetty classes
119     } ; 
120 
121     private String[] _configurationClasses = __dftConfigurationClasses;
122     private ClasspathPattern _systemClasses = null;
123     private ClasspathPattern _serverClasses = null;
124 
125     private Configuration[] _configurations;
126     private String _defaultsDescriptor=WEB_DEFAULTS_XML;
127     private String _descriptor=null;
128     private final List<String> _overrideDescriptors = new ArrayList<String>();
129     private boolean _distributable=false;
130     private boolean _extractWAR=true;
131     private boolean _copyDir=false;
132     private boolean _copyWebInf=true; // TODO change to false?
133     private boolean _logUrlOnStart =false;
134     private boolean _parentLoaderPriority= Boolean.getBoolean("org.eclipse.jetty.server.webapp.parentLoaderPriority");
135     private PermissionCollection _permissions;
136 
137     private String[] _contextWhiteList = null;
138     
139     private File _tmpDir;
140     private String _war;
141     private String _extraClasspath;
142     private Throwable _unavailableException;
143     
144     private Map<String, String> _resourceAliases;
145     private boolean _ownClassLoader=false;
146     private boolean _configurationDiscovered=true;
147     private boolean _configurationClassesSet=false;
148     private boolean _configurationsSet=false;
149     private boolean _allowDuplicateFragmentNames = false;
150     private boolean _throwUnavailableOnStartupException = false;
151     
152     private MetaData _metadata=new MetaData();
153 
154     public static WebAppContext getCurrentWebAppContext()
155     {
156         ContextHandler.Context context=ContextHandler.getCurrentContext();
157         if (context!=null)
158         {
159             ContextHandler handler = context.getContextHandler();
160             if (handler instanceof WebAppContext)
161                 return (WebAppContext)handler;
162         }
163         return null;
164     }
165     
166     /* ------------------------------------------------------------ */
167     public WebAppContext()
168     {
169         super(SESSIONS|SECURITY); 
170         _scontext=new Context();
171         setErrorHandler(new ErrorPageErrorHandler());    
172     }
173     
174     /* ------------------------------------------------------------ */
175     /**
176      * @param contextPath The context path
177      * @param webApp The URL or filename of the webapp directory or war file.
178      */
179     public WebAppContext(String webApp,String contextPath)
180     {
181         super(null,contextPath,SESSIONS|SECURITY);
182         _scontext=new Context();
183         setContextPath(contextPath);
184         setWar(webApp);
185         setErrorHandler(new ErrorPageErrorHandler()); 
186     }
187     
188     /* ------------------------------------------------------------ */
189     /**
190      * @param parent The parent HandlerContainer.
191      * @param contextPath The context path
192      * @param webApp The URL or filename of the webapp directory or war file.
193      */
194     public WebAppContext(HandlerContainer parent, String webApp, String contextPath)
195     {
196         super(parent,contextPath,SESSIONS|SECURITY);
197         _scontext=new Context();
198         setWar(webApp);
199         setErrorHandler(new ErrorPageErrorHandler());   
200     }
201 
202     /* ------------------------------------------------------------ */
203 
204     /**
205      * This constructor is used in the geronimo integration.
206      *
207      * @param sessionHandler SessionHandler for this web app
208      * @param securityHandler SecurityHandler for this web app
209      * @param servletHandler ServletHandler for this web app
210      * @param errorHandler ErrorHandler for this web app
211      */
212     public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) {
213         super(null, sessionHandler, securityHandler, servletHandler, errorHandler);
214         _scontext = new Context();
215         setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler());
216     }
217     
218     /* ------------------------------------------------------------ */
219     /**
220      * @param servletContextName The servletContextName to set.
221      */
222     @Override
223     public void setDisplayName(String servletContextName)
224     {
225         super.setDisplayName(servletContextName);
226         ClassLoader cl = getClassLoader();
227         if (cl!=null && cl instanceof WebAppClassLoader && servletContextName!=null)
228             ((WebAppClassLoader)cl).setName(servletContextName);
229     }
230     
231     /* ------------------------------------------------------------ */
232     /** Get an exception that caused the webapp to be unavailable
233      * @return A throwable if the webapp is unavailable or null
234      */
235     public Throwable getUnavailableException()
236     {
237         return _unavailableException;
238     }
239 
240     
241     /* ------------------------------------------------------------ */
242     /** Set Resource Alias.
243      * Resource aliases map resource uri's within a context.
244      * They may optionally be used by a handler when looking for
245      * a resource.
246      * @param alias
247      * @param uri
248      */
249     public void setResourceAlias(String alias, String uri)
250     {
251         if (_resourceAliases == null)
252             _resourceAliases= new HashMap<String, String>(5);
253         _resourceAliases.put(alias, uri);
254     }
255 
256     /* ------------------------------------------------------------ */
257     public Map<String, String> getResourceAliases()
258     {
259         if (_resourceAliases == null)
260             return null;
261         return _resourceAliases;
262     }
263     
264     /* ------------------------------------------------------------ */
265     public void setResourceAliases(Map<String, String> map)
266     {
267         _resourceAliases = map;
268     }
269     
270     /* ------------------------------------------------------------ */
271     public String getResourceAlias(String alias)
272     {
273         if (_resourceAliases == null)
274             return null;
275         return _resourceAliases.get(alias);
276     }
277 
278     /* ------------------------------------------------------------ */
279     public String removeResourceAlias(String alias)
280     {
281         if (_resourceAliases == null)
282             return null;
283         return _resourceAliases.remove(alias);
284     }
285 
286     /* ------------------------------------------------------------ */
287     /* (non-Javadoc)
288      * @see org.eclipse.jetty.server.server.handler.ContextHandler#setClassLoader(java.lang.ClassLoader)
289      */
290     @Override
291     public void setClassLoader(ClassLoader classLoader)
292     {
293         super.setClassLoader(classLoader);
294         
295 //        if ( !(classLoader instanceof WebAppClassLoader) )
296 //        {
297 //            LOG.info("NOTE: detected a classloader which is not an instance of WebAppClassLoader being set on WebAppContext, some typical class and resource locations may be missing on: " + toString() );
298 //        }
299         
300         if (classLoader!=null && classLoader instanceof WebAppClassLoader && getDisplayName()!=null)
301             ((WebAppClassLoader)classLoader).setName(getDisplayName());
302     }
303     
304     /* ------------------------------------------------------------ */
305     @Override
306     public Resource getResource(String uriInContext) throws MalformedURLException
307     {
308         if (uriInContext==null || !uriInContext.startsWith(URIUtil.SLASH))
309             throw new MalformedURLException(uriInContext);
310 
311         IOException ioe= null;
312         Resource resource= null;
313         int loop=0;
314         while (uriInContext!=null && loop++<100)
315         {
316             try
317             {
318                 resource= super.getResource(uriInContext);
319                 if (resource != null && resource.exists())
320                     return resource;
321                 
322                 uriInContext = getResourceAlias(uriInContext);
323             }
324             catch (IOException e)
325             {
326                 LOG.ignore(e);
327                 if (ioe==null)
328                     ioe= e;
329             }
330         }
331 
332         if (ioe != null && ioe instanceof MalformedURLException)
333             throw (MalformedURLException)ioe;
334 
335         return resource;
336     }
337     
338 
339     /* ------------------------------------------------------------ */
340     /** Is the context Automatically configured.
341      * 
342      * @return true if configuration discovery.
343      */
344     public boolean isConfigurationDiscovered()
345     {
346         return _configurationDiscovered;
347     }
348 
349     /* ------------------------------------------------------------ */
350     /** Set the configuration discovery mode.
351      * If configuration discovery is set to true, then the JSR315
352      * servlet 3.0 discovered configuration features are enabled.
353      * These are:<ul>
354      * <li>Web Fragments</li>
355      * <li>META-INF/resource directories</li>
356      * </ul>
357      * @param discovered true if configuration discovery is enabled for automatic configuration from the context
358      */
359     public void setConfigurationDiscovered(boolean discovered)
360     {
361         _configurationDiscovered = discovered;
362     }
363     
364     /* ------------------------------------------------------------ */
365     /** Pre configure the web application.
366      * <p>
367      * The method is normally called from {@link #start()}. It performs
368      * the discovery of the configurations to be applied to this context,
369      * specifically:<ul>
370      * <li>Instantiate the {@link Configuration} instances with a call to {@link #loadConfigurations()}.
371      * <li>Setup the default System classes by calling {@link #loadSystemClasses()}
372      * <li>Setup the default Server classes by calling <code>loadServerClasses()</code>
373      * <li>Instantiates a classload (if one is not already set)
374      * <li>Calls the {@link Configuration#preConfigure(WebAppContext)} method of all 
375      * Configuration instances.
376      * </ul>
377      * @throws Exception
378      */
379     public void preConfigure() throws Exception
380     {
381         // Setup configurations
382         loadConfigurations();
383 
384         // Setup system classes
385         loadSystemClasses();
386         
387         // Setup server classes
388         loadServerClasses();
389 
390         // Configure classloader
391         _ownClassLoader=false;
392         if (getClassLoader()==null)
393         {
394             WebAppClassLoader classLoader = new WebAppClassLoader(this);
395             setClassLoader(classLoader);
396             _ownClassLoader=true;
397         }
398 
399         if (LOG.isDebugEnabled())
400         {
401             ClassLoader loader = getClassLoader();
402             LOG.debug("Thread Context class loader is: " + loader);
403             loader=loader.getParent();
404             while(loader!=null)
405             {
406                 LOG.debug("Parent class loader is: " + loader);
407                 loader=loader.getParent();
408             }
409         }
410       
411         // Prepare for configuration     
412         for (int i=0;i<_configurations.length;i++)
413         {
414             LOG.debug("preConfigure {} with {}",this,_configurations[i]);
415             _configurations[i].preConfigure(this);
416         }
417     }
418 
419     /* ------------------------------------------------------------ */
420     public void configure() throws Exception
421     {
422         // Configure webapp
423         for (int i=0;i<_configurations.length;i++)
424         {
425             LOG.debug("configure {} with {}",this,_configurations[i]);
426             _configurations[i].configure(this);
427         }
428     }
429     
430     /* ------------------------------------------------------------ */
431     public void postConfigure() throws Exception
432     {   
433         // Clean up after configuration
434         for (int i=0;i<_configurations.length;i++)
435         {
436             LOG.debug("postConfigure {} with {}",this,_configurations[i]);
437             _configurations[i].postConfigure(this);
438         }
439     }
440     
441     /* ------------------------------------------------------------ */
442     /*
443      * @see org.eclipse.thread.AbstractLifeCycle#doStart()
444      */
445     @Override
446     protected void doStart() throws Exception
447     {
448         try
449         {
450             _metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
451             preConfigure();
452             super.doStart();
453             postConfigure();
454 
455             if (isLogUrlOnStart())
456                 dumpUrl();
457         }
458         catch (Exception e)
459         {
460             //start up of the webapp context failed, make sure it is not started
461             LOG.warn("Failed startup of context "+this, e);
462             _unavailableException=e;
463             setAvailable(false);
464             if (isThrowUnavailableOnStartupException())
465                 throw e;
466         }
467     }
468     
469     /* ------------------------------------------------------------ */
470     /*
471      * @see org.eclipse.thread.AbstractLifeCycle#doStop()
472      */
473     @Override
474     protected void doStop() throws Exception
475     {
476         super.doStop();
477 
478         try
479         {
480             for (int i=_configurations.length;i-->0;)
481                 _configurations[i].deconfigure(this);
482             
483             if (_metadata != null)
484                 _metadata.clear();
485             _metadata=new MetaData();
486             
487         }
488         finally
489         {
490             if (_ownClassLoader)
491                 setClassLoader(null);
492 
493             setAvailable(true);
494             _unavailableException=null;
495         }
496     }
497     
498     /* ------------------------------------------------------------ */
499     @Override
500     public void destroy()
501     {
502         // Prepare for configuration     
503         MultiException mx=new MultiException();
504         if (_configurations!=null)
505         {
506             for (int i=_configurations.length;i-->0;)
507             {
508                 try
509                 {   
510                     _configurations[i].destroy(this);
511                 }
512                 catch(Exception e)
513                 {
514                     mx.add(e);
515                 }
516             }
517         }
518         _configurations=null;
519         super.destroy();
520         mx.ifExceptionThrowRuntime();
521     }
522 
523 
524     /* ------------------------------------------------------------ */
525     /*
526      * Dumps the current web app name and URL to the log
527      */
528     private void dumpUrl()
529     {
530         Connector[] connectors = getServer().getConnectors();
531         for (int i=0;i<connectors.length;i++)
532         {
533             String connectorName = connectors[i].getName();
534             String displayName = getDisplayName();
535             if (displayName == null)
536                 displayName = "WebApp@"+connectors.hashCode();
537            
538             LOG.info(displayName + " at http://" + connectorName + getContextPath());
539         }
540     }
541 
542     /* ------------------------------------------------------------ */
543     /**
544      * @return Returns the configurations.
545      */
546     public String[] getConfigurationClasses()
547     {
548         return _configurationClasses;
549     }
550     
551     /* ------------------------------------------------------------ */
552     /**
553      * @return Returns the configurations.
554      */
555     public Configuration[] getConfigurations()
556     {
557         return _configurations;
558     }
559     
560     /* ------------------------------------------------------------ */
561     /**
562      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
563      * @return Returns the defaultsDescriptor.
564      */
565     public String getDefaultsDescriptor()
566     {
567         return _defaultsDescriptor;
568     }
569     
570     /* ------------------------------------------------------------ */
571     /**
572      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
573      * @return Returns the Override Descriptor.
574      * @deprecated use {@link #getOverrideDescriptors()}
575      */
576     public String getOverrideDescriptor()
577     {
578         if (_overrideDescriptors.size()!=1)
579             return null;
580         return _overrideDescriptors.get(0);
581     }
582     
583     /* ------------------------------------------------------------ */
584     /**
585      * An override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
586      * @return Returns the Override Descriptor list
587      */
588     public List<String> getOverrideDescriptors()
589     {
590         return Collections.unmodifiableList(_overrideDescriptors);
591     }
592     
593     /* ------------------------------------------------------------ */
594     /**
595      * @return Returns the permissions.
596      */
597     public PermissionCollection getPermissions()
598     {
599         return _permissions;
600     }
601 
602     /* ------------------------------------------------------------ */
603     /**
604      * @see #setServerClasses(String[])
605      * @return Returns the serverClasses.
606      */
607     public String[] getServerClasses()
608     {
609         if (_serverClasses == null)
610             loadServerClasses();
611         
612         return _serverClasses.getPatterns();
613     }
614 
615     public void addServerClass(String classname)
616     {
617         if (_serverClasses == null)
618             loadServerClasses();
619         
620         _serverClasses.addPattern(classname);
621     }
622     
623     /* ------------------------------------------------------------ */
624     /**
625      * @see #setSystemClasses(String[])
626      * @return Returns the systemClasses.
627      */
628     public String[] getSystemClasses()
629     {
630         if (_systemClasses == null)
631             loadSystemClasses();
632         
633         return _systemClasses.getPatterns();
634     }
635     
636     /* ------------------------------------------------------------ */
637     public void addSystemClass(String classname)
638     {
639         if (_systemClasses == null)
640             loadSystemClasses();
641         
642         _systemClasses.addPattern(classname);
643     }
644     
645     /* ------------------------------------------------------------ */
646     public boolean isServerClass(String name)
647     {
648         if (_serverClasses == null)
649             loadServerClasses();
650         
651         return _serverClasses.match(name);
652     }
653 
654     /* ------------------------------------------------------------ */
655     public boolean isSystemClass(String name)
656     {
657         if (_systemClasses == null)
658             loadSystemClasses();
659         
660         return _systemClasses.match(name);
661     }
662     
663     /* ------------------------------------------------------------ */
664     protected void loadSystemClasses()
665     {
666         if (_systemClasses != null)
667             return;
668         
669         //look for a Server attribute with the list of System classes
670         //to apply to every web application. If not present, use our defaults.
671         Server server = getServer();
672         if (server != null)
673         {
674             Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
675             if (systemClasses != null && systemClasses instanceof String[])
676                 _systemClasses = new ClasspathPattern((String[])systemClasses);
677         }
678         
679         if (_systemClasses == null)
680             _systemClasses = new ClasspathPattern(__dftSystemClasses);
681     }
682     
683     /* ------------------------------------------------------------ */
684     private void loadServerClasses()
685     {
686         if (_serverClasses != null)
687             return;
688         
689         //look for a Server attribute with the list of Server classes
690         //to apply to every web application. If not present, use our defaults.
691         Server server = getServer();
692         if (server != null)
693         {
694             Object serverClasses = server.getAttribute(SERVER_SRV_CLASSES);
695             if (serverClasses != null || serverClasses instanceof String[])
696                 _serverClasses = new ClasspathPattern((String[])serverClasses);
697         }
698         
699         if (_serverClasses == null)
700             _serverClasses = new ClasspathPattern(__dftServerClasses);
701     }
702     
703     /* ------------------------------------------------------------ */
704     /**
705      * @return Returns the war as a file or URL string (Resource)
706      */
707     public String getWar()
708     {
709         if (_war==null)
710             _war=getResourceBase();
711         return _war;
712     }
713 
714     /* ------------------------------------------------------------ */
715     public Resource getWebInf() throws IOException
716     {
717         if (super.getBaseResource() == null)
718             return null;
719 
720         // Iw there a WEB-INF directory?
721         Resource web_inf= super.getBaseResource().addPath("WEB-INF/");
722         if (!web_inf.exists() || !web_inf.isDirectory())
723             return null;
724         
725         return web_inf;
726     }
727     
728     /* ------------------------------------------------------------ */
729     /**
730      * @return Returns the distributable.
731      */
732     public boolean isDistributable()
733     {
734         return _distributable;
735     }
736 
737     /* ------------------------------------------------------------ */
738     /**
739      * @return Returns the extractWAR.
740      */
741     public boolean isExtractWAR()
742     {
743         return _extractWAR;
744     }
745 
746     /* ------------------------------------------------------------ */
747     /**
748      * @return True if the webdir is copied (to allow hot replacement of jars on windows)
749      */
750     public boolean isCopyWebDir()
751     {
752         return _copyDir;
753     }
754 
755     /* ------------------------------------------------------------ */
756     /**
757      * @return True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
758      */
759     public boolean isCopyWebInf()
760     {
761         return _copyWebInf;
762     }
763 
764     /* ------------------------------------------------------------ */
765     /**
766      * @return True if the classloader should delegate first to the parent 
767      * classloader (standard java behaviour) or false if the classloader 
768      * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet 
769      * spec recommendation).
770      */
771     public boolean isParentLoaderPriority()
772     {
773         return _parentLoaderPriority;
774     }
775     
776     
777     /* ------------------------------------------------------------ */
778     public String[] getDefaultConfigurationClasses ()
779     {
780         return __dftConfigurationClasses;
781     }
782     
783     /* ------------------------------------------------------------ */
784     public String[] getDefaultServerClasses ()
785     {
786         return __dftServerClasses;
787     }
788     
789     /* ------------------------------------------------------------ */
790     public String[] getDefaultSystemClasses ()
791     {
792         return __dftSystemClasses;
793     }
794     
795     /* ------------------------------------------------------------ */
796     protected void loadConfigurations()
797     	throws Exception
798     {
799         //if the configuration instances have been set explicitly, use them
800         if (_configurations!=null)
801             return;
802 
803         //if the configuration classnames have been set explicitly use them
804         if (!_configurationClassesSet)
805             _configurationClasses=__dftConfigurationClasses;
806 
807         _configurations = new Configuration[_configurationClasses.length];
808         for (int i = 0; i < _configurationClasses.length; i++)
809         {
810             _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
811         }
812     }
813     
814     /* ------------------------------------------------------------ */
815     @Override
816     protected boolean isProtectedTarget(String target)
817     {
818         while (target.startsWith("//"))
819             target=URIUtil.compactPath(target);
820          
821         return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf");
822     }
823     
824 
825     /* ------------------------------------------------------------ */
826     @Override
827     public String toString()
828     {
829         return super.toString()+(_war==null?"":(","+_war));
830     }
831 
832     /* ------------------------------------------------------------ */
833     /**
834      * @param configurations The configuration class names.  If setConfigurations is not called
835      * these classes are used to create a configurations array.
836      */
837     public void setConfigurationClasses(String[] configurations)
838     {
839         if (isRunning())
840             throw new IllegalStateException();
841         _configurationClasses = configurations==null?null:(String[])configurations.clone();
842         _configurationClassesSet = true;
843         _configurations=null;
844     }
845     
846     /* ------------------------------------------------------------ */
847     /**
848      * @param configurations The configurations to set.
849      */
850     public void setConfigurations(Configuration[] configurations)
851     {
852         if (isRunning())
853             throw new IllegalStateException();
854         _configurations = configurations==null?null:(Configuration[])configurations.clone();
855         _configurationsSet = true;
856     }
857 
858     /* ------------------------------------------------------------ */
859     /**
860      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
861      * @param defaultsDescriptor The defaultsDescriptor to set.
862      */
863     public void setDefaultsDescriptor(String defaultsDescriptor)
864     {
865         _defaultsDescriptor = defaultsDescriptor;
866     }
867 
868     /* ------------------------------------------------------------ */
869     /**
870      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
871      * @param overrideDescriptor The overrideDescritpor to set.
872      * @deprecated use {@link #setOverrideDescriptors(List)}
873      */
874     public void setOverrideDescriptor(String overrideDescriptor)
875     {
876         _overrideDescriptors.clear();
877         _overrideDescriptors.add(overrideDescriptor);
878     }
879     
880     /* ------------------------------------------------------------ */
881     /**
882      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
883      * @param overrideDescriptors The overrideDescriptors (file or URL) to set.
884      */
885     public void setOverrideDescriptors(List<String> overrideDescriptors)
886     {
887         _overrideDescriptors.clear();
888         _overrideDescriptors.addAll(overrideDescriptors);
889     }
890     
891     /* ------------------------------------------------------------ */
892     /**
893      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
894      * @param overrideDescriptor The overrideDescriptor (file or URL) to add.
895      */
896     public void addOverrideDescriptor(String overrideDescriptor)
897     {
898         _overrideDescriptors.add(overrideDescriptor);
899     }
900 
901     /* ------------------------------------------------------------ */
902     /**
903      * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
904      */
905     public String getDescriptor()
906     {
907         return _descriptor;
908     }
909 
910     /* ------------------------------------------------------------ */
911     /**
912      * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
913      */
914     public void setDescriptor(String descriptor)
915     {
916         _descriptor=descriptor;
917     }
918     
919     /* ------------------------------------------------------------ */
920     /**
921      * @param distributable The distributable to set.
922      */
923     public void setDistributable(boolean distributable)
924     {
925         this._distributable = distributable;
926     }
927 
928     /* ------------------------------------------------------------ */
929     @Override
930     public void setEventListeners(EventListener[] eventListeners)
931     {
932         if (_sessionHandler!=null)
933             _sessionHandler.clearEventListeners();
934             
935         super.setEventListeners(eventListeners);
936       
937         for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
938         {
939             EventListener listener = eventListeners[i];
940             
941             if ((listener instanceof HttpSessionActivationListener)
942                             || (listener instanceof HttpSessionAttributeListener)
943                             || (listener instanceof HttpSessionBindingListener)
944                             || (listener instanceof HttpSessionListener))
945             {
946                 if (_sessionHandler!=null)
947                     _sessionHandler.addEventListener(listener);
948             }
949             
950         }
951     }
952 
953     /* ------------------------------------------------------------ */
954     /** Add EventListener
955      * Conveniance method that calls {@link #setEventListeners(EventListener[])}
956      * @param listener
957      */
958     @Override
959     public void addEventListener(EventListener listener)
960     {   
961         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));
962     }
963 
964     
965     /* ------------------------------------------------------------ */
966     /**
967      * @param extractWAR True if war files are extracted
968      */
969     public void setExtractWAR(boolean extractWAR)
970     {
971         _extractWAR = extractWAR;
972     }
973     
974     /* ------------------------------------------------------------ */
975     /**
976      * @param copy True if the webdir is copied (to allow hot replacement of jars)
977      */
978     public void setCopyWebDir(boolean copy)
979     {
980         _copyDir = copy;
981     }
982 
983     /* ------------------------------------------------------------ */
984     /**
985      * @param copyWebInf True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
986      */
987     public void setCopyWebInf(boolean copyWebInf)
988     {
989         _copyWebInf = copyWebInf;
990     }
991 
992     /* ------------------------------------------------------------ */
993     /**
994      * @param java2compliant The java2compliant to set.
995      */
996     public void setParentLoaderPriority(boolean java2compliant)
997     {
998         _parentLoaderPriority = java2compliant;
999     }
1000 
1001     /* ------------------------------------------------------------ */
1002     /**
1003      * @param permissions The permissions to set.
1004      */
1005     public void setPermissions(PermissionCollection permissions)
1006     {
1007         _permissions = permissions;
1008     }
1009     
1010     /**
1011      * Set the context white list
1012      * 
1013      * In certain circumstances you want may want to deny access of one webapp from another
1014      * when you may not fully trust the webapp.  Setting this white list will enable a
1015      * check when a servlet called getContext(String), validating that the uriInPath
1016      * for the given webapp has been declaratively allows access to the context.
1017      * @param contextWhiteList
1018      */
1019     public void setContextWhiteList(String[] contextWhiteList)
1020     {
1021         _contextWhiteList = contextWhiteList;
1022     }
1023 
1024     /* ------------------------------------------------------------ */
1025     /**
1026      * Set the server classes patterns.
1027      * <p>
1028      * Server classes/packages are classes used to implement the server and are hidden
1029      * from the context.  If the context needs to load these classes, it must have its
1030      * own copy of them in WEB-INF/lib or WEB-INF/classes.
1031      * A class pattern is a string of one of the forms:<dl>
1032      * <dt>org.package.Classname</dt><dd>Match a specific class</dd>
1033      * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd>
1034      * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd>
1035      * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd>
1036      * </dl>
1037      * @param serverClasses The serverClasses to set.
1038      */
1039     public void setServerClasses(String[] serverClasses)
1040     {
1041         _serverClasses = new ClasspathPattern(serverClasses);
1042     }
1043     
1044     /* ------------------------------------------------------------ */
1045     /**
1046      * Set the system classes patterns.
1047      * <p>
1048      * System classes/packages are classes provided by the JVM and that
1049      * cannot be replaced by classes of the same name from WEB-INF,
1050      * regardless of the value of {@link #setParentLoaderPriority(boolean)}.
1051      * A class pattern is a string of one of the forms:<dl>
1052      * <dt>org.package.Classname</dt><dd>Match a specific class</dd>
1053      * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd>
1054      * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd>
1055      * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd>
1056      * </dl>
1057      * @param systemClasses The systemClasses to set.
1058      */
1059     public void setSystemClasses(String[] systemClasses)
1060     {
1061         _systemClasses = new ClasspathPattern(systemClasses);
1062     }
1063     
1064 
1065     /* ------------------------------------------------------------ */
1066     /** Set temporary directory for context.
1067      * The javax.servlet.context.tempdir attribute is also set.
1068      * @param dir Writable temporary directory.
1069      */
1070     public void setTempDirectory(File dir)
1071     {
1072         if (isStarted())
1073             throw new IllegalStateException("Started");
1074 
1075         if (dir!=null)
1076         {
1077             try{dir=new File(dir.getCanonicalPath());}
1078             catch (IOException e){LOG.warn(Log.EXCEPTION,e);}
1079         }
1080 
1081         if (dir!=null && !dir.exists())
1082         {
1083             dir.mkdir();
1084             dir.deleteOnExit();
1085         }
1086 
1087         if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
1088             throw new IllegalArgumentException("Bad temp directory: "+dir);
1089         
1090         try
1091         {
1092             if (dir!=null)
1093                 dir=dir.getCanonicalFile();
1094         }
1095         catch(Exception e)
1096         {
1097             LOG.warn(e);
1098         }
1099         _tmpDir=dir;
1100         setAttribute(TEMPDIR,_tmpDir);
1101     }
1102     
1103     /* ------------------------------------------------------------ */
1104     public File getTempDirectory ()
1105     {
1106         return _tmpDir;
1107     }
1108     
1109     /* ------------------------------------------------------------ */
1110     /**
1111      * @param war The war to set as a file name or URL
1112      */
1113     public void setWar(String war)
1114     {
1115         _war = war;
1116     }
1117 
1118     /* ------------------------------------------------------------ */
1119     /**
1120      * @return Comma or semicolon separated path of filenames or URLs
1121      * pointing to directories or jar files. Directories should end
1122      * with '/'.
1123      */
1124     public String getExtraClasspath()
1125     {
1126         return _extraClasspath;
1127     }
1128 
1129     /* ------------------------------------------------------------ */
1130     /**
1131      * @param extraClasspath Comma or semicolon separated path of filenames or URLs
1132      * pointing to directories or jar files. Directories should end
1133      * with '/'.
1134      */
1135     public void setExtraClasspath(String extraClasspath)
1136     {
1137         _extraClasspath=extraClasspath;
1138     }
1139 
1140     /* ------------------------------------------------------------ */
1141     public boolean isLogUrlOnStart()
1142     {
1143         return _logUrlOnStart;
1144     }
1145 
1146     /* ------------------------------------------------------------ */
1147     /**
1148      * Sets whether or not the web app name and URL is logged on startup
1149      *
1150      * @param logOnStart whether or not the log message is created
1151      */
1152     public void setLogUrlOnStart(boolean logOnStart)
1153     {
1154         this._logUrlOnStart = logOnStart;
1155     }
1156     
1157     
1158     /* ------------------------------------------------------------ */
1159     @Override
1160     public void setServer(Server server)
1161     {
1162         super.setServer(server);
1163         //if we haven't been given a set of configuration instances to 
1164         //use, and we haven't been given a set of configuration classes
1165         //to use, use the configuration classes that came from the
1166         //Server (if there are any)
1167         if (!_configurationsSet && !_configurationClassesSet && server != null)
1168         {
1169             String[] serverConfigs = (String[])server.getAttribute(SERVER_CONFIG);
1170             if (serverConfigs != null)
1171                 setConfigurationClasses(serverConfigs);
1172         }
1173     }
1174     
1175     
1176     /* ------------------------------------------------------------ */
1177     public boolean isAllowDuplicateFragmentNames()
1178     {
1179         return _allowDuplicateFragmentNames;
1180     }
1181     
1182     
1183     /* ------------------------------------------------------------ */
1184     public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
1185     {
1186         _allowDuplicateFragmentNames = allowDuplicateFragmentNames;
1187     }
1188 
1189     
1190     /* ------------------------------------------------------------ */
1191     public void setThrowUnavailableOnStartupException (boolean throwIfStartupException) {
1192         _throwUnavailableOnStartupException = throwIfStartupException;
1193     }
1194     
1195 
1196     /* ------------------------------------------------------------ */
1197     public boolean isThrowUnavailableOnStartupException () {
1198         return _throwUnavailableOnStartupException;
1199     }
1200     
1201     /* ------------------------------------------------------------ */
1202     @Override
1203     protected void startContext()
1204         throws Exception
1205     {
1206         configure();
1207         
1208         //resolve the metadata
1209         _metadata.resolve(this);
1210         
1211         super.startContext();
1212     }
1213     
1214     /* ------------------------------------------------------------ */
1215     public class Context extends ServletContextHandler.Context
1216     {
1217         /* ------------------------------------------------------------ */
1218         public URL getResource(String path) throws MalformedURLException
1219         {
1220             Resource resource=WebAppContext.this.getResource(path);
1221             if (resource==null || !resource.exists())
1222                 return null;
1223             
1224             // Should we go to the original war?
1225             if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR())
1226             {
1227                 Resource[] resources = ((ResourceCollection)resource).getResources();
1228                 for (int i=resources.length;i-->0;)
1229                 {
1230                     if (resources[i].getName().startsWith("jar:file"))
1231                         return resources[i].getURL();
1232                 }
1233             }
1234                 
1235             return resource.getURL();
1236         }
1237 
1238         /* ------------------------------------------------------------ */
1239         @Override
1240         public ServletContext getContext(String uripath)
1241         {
1242             ServletContext servletContext = super.getContext(uripath);
1243             
1244             if ( servletContext != null && _contextWhiteList != null )
1245             {
1246                 for ( String context : _contextWhiteList )
1247                 {
1248                     if ( context.equals(uripath) )
1249                     {
1250                         return servletContext;
1251                     }
1252                 }
1253                 
1254                 return null;
1255             }
1256             else
1257             {
1258                 return servletContext;
1259             }
1260         }
1261 
1262     }
1263 
1264     /* ------------------------------------------------------------ */
1265     public MetaData getMetaData()
1266     {
1267         return _metadata;
1268     }
1269 
1270 }