View Javadoc

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