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