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.URL;
24import java.net.URLClassLoader;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.EnumSet;
28import java.util.EventListener;
29import java.util.HashSet;
30import java.util.Iterator;
31import java.util.List;
32import java.util.Locale;
33import java.util.Map;
34import java.util.Set;
35
36import javax.servlet.DispatcherType;
37import javax.servlet.MultipartConfigElement;
38import javax.servlet.ServletException;
39import javax.servlet.ServletRegistration;
40import javax.servlet.SessionTrackingMode;
41import javax.servlet.descriptor.JspConfigDescriptor;
42import javax.servlet.descriptor.JspPropertyGroupDescriptor;
43import javax.servlet.descriptor.TaglibDescriptor;
44
45import org.eclipse.jetty.security.ConstraintAware;
46import org.eclipse.jetty.security.ConstraintMapping;
47import org.eclipse.jetty.security.authentication.FormAuthenticator;
48import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
49import org.eclipse.jetty.servlet.FilterHolder;
50import org.eclipse.jetty.servlet.FilterMapping;
51import org.eclipse.jetty.servlet.Holder;
52import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
53import org.eclipse.jetty.servlet.ServletContextHandler;
54import org.eclipse.jetty.servlet.ServletHandler;
55import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
56import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
57import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
58import org.eclipse.jetty.servlet.ServletHolder;
59import org.eclipse.jetty.servlet.ServletMapping;
60import org.eclipse.jetty.util.LazyList;
61import org.eclipse.jetty.util.Loader;
62import org.eclipse.jetty.util.log.Log;
63import org.eclipse.jetty.util.log.Logger;
64import org.eclipse.jetty.util.resource.Resource;
65import org.eclipse.jetty.util.security.Constraint;
66import org.eclipse.jetty.xml.XmlParser;
67
68/**
69 * StandardDescriptorProcessor
70 *
71 * Process a web.xml, web-defaults.xml, web-overrides.xml, web-fragment.xml.
72 */
73public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
74{
75    private static final Logger LOG = Log.getLogger(StandardDescriptorProcessor.class);
76
77    public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
78
79
80
81    public StandardDescriptorProcessor ()
82    {
83
84        try
85        {
86            registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature));
87            registerVisitor("display-name", this.getClass().getDeclaredMethod("visitDisplayName", __signature));
88            registerVisitor("servlet", this.getClass().getDeclaredMethod("visitServlet",  __signature));
89            registerVisitor("servlet-mapping", this.getClass().getDeclaredMethod("visitServletMapping",  __signature));
90            registerVisitor("session-config", this.getClass().getDeclaredMethod("visitSessionConfig",  __signature));
91            registerVisitor("mime-mapping", this.getClass().getDeclaredMethod("visitMimeMapping",  __signature));
92            registerVisitor("welcome-file-list", this.getClass().getDeclaredMethod("visitWelcomeFileList",  __signature));
93            registerVisitor("locale-encoding-mapping-list", this.getClass().getDeclaredMethod("visitLocaleEncodingList",  __signature));
94            registerVisitor("error-page", this.getClass().getDeclaredMethod("visitErrorPage",  __signature));
95            registerVisitor("taglib", this.getClass().getDeclaredMethod("visitTagLib",  __signature));
96            registerVisitor("jsp-config", this.getClass().getDeclaredMethod("visitJspConfig",  __signature));
97            registerVisitor("security-constraint", this.getClass().getDeclaredMethod("visitSecurityConstraint",  __signature));
98            registerVisitor("login-config", this.getClass().getDeclaredMethod("visitLoginConfig",  __signature));
99            registerVisitor("security-role", this.getClass().getDeclaredMethod("visitSecurityRole",  __signature));
100            registerVisitor("filter", this.getClass().getDeclaredMethod("visitFilter",  __signature));
101            registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping",  __signature));
102            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener",  __signature));
103            registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable",  __signature));
104        }
105        catch (Exception e)
106        {
107            throw new IllegalStateException(e);
108        }
109    }
110
111
112
113    /**
114     * {@inheritDoc}
115     */
116    public void start(WebAppContext context, Descriptor descriptor)
117    {
118    }
119
120
121
122    /**
123     * {@inheritDoc}
124     */
125    public void end(WebAppContext context, Descriptor descriptor)
126    {
127    }
128
129    /**
130     * @param context
131     * @param descriptor
132     * @param node
133     */
134    public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
135    {
136        String name = node.getString("param-name", false, true);
137        String value = node.getString("param-value", false, true);
138        Origin o = context.getMetaData().getOrigin("context-param."+name);
139        switch (o)
140        {
141            case NotSet:
142            {
143                //just set it
144                context.getInitParams().put(name, value);
145                context.getMetaData().setOrigin("context-param."+name, descriptor);
146                break;
147            }
148            case WebXml:
149            case WebDefaults:
150            case WebOverride:
151            {
152                //previously set by a web xml, allow other web xml files to override
153                if (!(descriptor instanceof FragmentDescriptor))
154                {
155                    context.getInitParams().put(name, value);
156                    context.getMetaData().setOrigin("context-param."+name, descriptor);
157                }
158                break;
159            }
160            case WebFragment:
161            {
162                //previously set by a web-fragment, this fragment's value must be the same
163                if (descriptor instanceof FragmentDescriptor)
164                {
165                    if (!((String)context.getInitParams().get(name)).equals(value))
166                        throw new IllegalStateException("Conflicting context-param "+name+"="+value+" in "+descriptor.getResource());
167                }
168                break;
169            }
170        }
171        if (LOG.isDebugEnabled())
172            LOG.debug("ContextParam: " + name + "=" + value);
173
174    }
175
176
177    /* ------------------------------------------------------------ */
178    /**
179     * @param context
180     * @param descriptor
181     * @param node
182     */
183    protected void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
184    {
185        //Servlet Spec 3.0 p. 74 Ignore from web-fragments
186        if (!(descriptor instanceof FragmentDescriptor))
187        {
188            context.setDisplayName(node.toString(false, true));
189            context.getMetaData().setOrigin("display-name", descriptor);
190        }
191    }
192
193
194    /**
195     * @param context
196     * @param descriptor
197     * @param node
198     */
199    protected void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
200    {
201        String id = node.getAttribute("id");
202
203        // initialize holder
204        String servlet_name = node.getString("servlet-name", false, true);
205        ServletHolder holder = context.getServletHandler().getServlet(servlet_name);
206
207        /*
208         * If servlet of that name does not already exist, create it.
209         */
210        if (holder == null)
211        {
212            holder = context.getServletHandler().newServletHolder(Holder.Source.DESCRIPTOR);
213            holder.setName(servlet_name);
214            context.getServletHandler().addServlet(holder);
215        }
216
217        // init params
218        Iterator<?> iParamsIter = node.iterator("init-param");
219        while (iParamsIter.hasNext())
220        {
221            XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
222            String pname = paramNode.getString("param-name", false, true);
223            String pvalue = paramNode.getString("param-value", false, true);
224
225            Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
226
227            switch (origin)
228            {
229                case NotSet:
230                {
231                    //init-param not already set, so set it
232
233                    holder.setInitParameter(pname, pvalue);
234                    context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
235                    break;
236                }
237                case WebXml:
238                case WebDefaults:
239                case WebOverride:
240                {
241                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
242                    //otherwise just ignore it
243                    if (!(descriptor instanceof FragmentDescriptor))
244                    {
245                        holder.setInitParameter(pname, pvalue);
246                        context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
247                    }
248                    break;
249                }
250                case WebFragment:
251                {
252                    //previously set by a web-fragment, make sure that the value matches, otherwise its an error
253                    if (!holder.getInitParameter(pname).equals(pvalue))
254                        throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
255                    break;
256                }
257            }
258        }
259
260        String servlet_class = node.getString("servlet-class", false, true);
261
262        // Handle JSP
263        String jspServletClass=null;;
264
265        //Handle the default jsp servlet instance
266        if (id != null && id.equals("jsp"))
267        {
268            jspServletClass = servlet_class;
269            try
270            {
271                Loader.loadClass(this.getClass(), servlet_class);
272
273                //Ensure there is a scratch dir
274                if (holder.getInitParameter("scratchdir") == null)
275                {
276                    File tmp = context.getTempDirectory();
277                    File scratch = new File(tmp, "jsp");
278                    if (!scratch.exists()) scratch.mkdir();
279                    holder.setInitParameter("scratchdir", scratch.getAbsolutePath());
280                }
281            }
282            catch (ClassNotFoundException e)
283            {
284                LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
285                jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
286            }
287        }
288
289
290        //Set the servlet-class
291        if (servlet_class != null)
292        {
293            ((WebDescriptor)descriptor).addClassName(servlet_class);
294
295            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
296            switch (o)
297            {
298                case NotSet:
299                {
300                    //the class of the servlet has not previously been set, so set it
301                    holder.setClassName(servlet_class);
302                    context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
303                    break;
304                }
305                case WebXml:
306                case WebDefaults:
307                case WebOverride:
308                {
309                    //the class of the servlet was set by a web xml file, only allow web-override/web-default to change it
310                    if (!(descriptor instanceof FragmentDescriptor))
311                    {
312                        holder.setClassName(servlet_class);
313                        context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
314                    }
315                    break;
316                }
317                case WebFragment:
318                {
319                    //the class was set by another fragment, ensure this fragment's value is the same
320                    if (!servlet_class.equals(holder.getClassName()))
321                        throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
322                    break;
323                }
324            }
325        }
326
327        // Handle JSP file
328        String jsp_file = node.getString("jsp-file", false, true);
329        if (jsp_file != null)
330        {
331            holder.setForcedPath(jsp_file);
332            ServletHolder jsp=context.getServletHandler().getServlet("jsp");
333            if (jsp!=null)
334                holder.setClassName(jsp.getClassName());
335        }
336
337        // handle load-on-startup
338        XmlParser.Node startup = node.get("load-on-startup");
339        if (startup != null)
340        {
341            String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
342            int order = 0;
343            if (s.startsWith("t"))
344            {
345                LOG.warn("Deprecated boolean load-on-startup.  Please use integer");
346                order = 1;
347            }
348            else
349            {
350                try
351                {
352                    if (s != null && s.trim().length() > 0) order = Integer.parseInt(s);
353                }
354                catch (Exception e)
355                {
356                    LOG.warn("Cannot parse load-on-startup " + s + ". Please use integer");
357                    LOG.ignore(e);
358                }
359            }
360
361            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
362            switch (o)
363            {
364                case NotSet:
365                {
366                    //not already set, so set it now
367                    holder.setInitOrder(order);
368                    context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
369                    break;
370                }
371                case WebXml:
372                case WebDefaults:
373                case WebOverride:
374                {
375                    //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
376                    if (!(descriptor instanceof FragmentDescriptor))
377                    {
378                        holder.setInitOrder(order);
379                        context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
380                    }
381                    break;
382                }
383                case WebFragment:
384                {
385                    //it was already set by another fragment, if we're parsing a fragment, the values must match
386                    if (order != holder.getInitOrder())
387                        throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
388                    break;
389                }
390            }
391        }
392
393        Iterator sRefsIter = node.iterator("security-role-ref");
394        while (sRefsIter.hasNext())
395        {
396            XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
397            String roleName = securityRef.getString("role-name", false, true);
398            String roleLink = securityRef.getString("role-link", false, true);
399            if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
400            {
401                if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
402                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
403                switch (o)
404                {
405                    case NotSet:
406                    {
407                        //set it
408                        holder.setUserRoleLink(roleName, roleLink);
409                        context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
410                        break;
411                    }
412                    case WebXml:
413                    case WebDefaults:
414                    case WebOverride:
415                    {
416                        //only another web xml descriptor (web-default,web-override web.xml) can override an already set value
417                        if (!(descriptor instanceof FragmentDescriptor))
418                        {
419                            holder.setUserRoleLink(roleName, roleLink);
420                            context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
421                        }
422                        break;
423                    }
424                    case WebFragment:
425                    {
426                        if (!holder.getUserRoleLink(roleName).equals(roleLink))
427                            throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
428                        break;
429                    }
430                }
431            }
432            else
433            {
434                LOG.warn("Ignored invalid security-role-ref element: " + "servlet-name=" + holder.getName() + ", " + securityRef);
435            }
436        }
437
438
439        XmlParser.Node run_as = node.get("run-as");
440        if (run_as != null)
441        {
442            String roleName = run_as.getString("role-name", false, true);
443
444            if (roleName != null)
445            {
446                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
447                switch (o)
448                {
449                    case NotSet:
450                    {
451                        //run-as not set, so set it
452                        holder.setRunAsRole(roleName);
453                        context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
454                        break;
455                    }
456                    case WebXml:
457                    case WebDefaults:
458                    case WebOverride:
459                    {
460                        //run-as was set by a web xml, only allow it to be changed if we're currently parsing another web xml(override/default)
461                        if (!(descriptor instanceof FragmentDescriptor))
462                        {
463                            holder.setRunAsRole(roleName);
464                            context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
465                        }
466                        break;
467                    }
468                    case WebFragment:
469                    {
470                        //run-as was set by another fragment, this fragment must show the same value
471                        if (!holder.getRunAsRole().equals(roleName))
472                            throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
473                        break;
474                    }
475                }
476            }
477        }
478
479        String async=node.getString("async-supported",false,true);
480        if (async!=null)
481        {
482            boolean val = async.length()==0||Boolean.valueOf(async);
483            Origin o =context.getMetaData().getOrigin(servlet_name+".servlet.async-supported");
484            switch (o)
485            {
486                case NotSet:
487                {
488                    //set it
489                    holder.setAsyncSupported(val);
490                    context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
491                    break;
492                }
493                case WebXml:
494                case WebDefaults:
495                case WebOverride:
496                {
497                    //async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
498                    if (!(descriptor instanceof FragmentDescriptor))
499                    {
500                        holder.setAsyncSupported(val);
501                        context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
502                    }
503                    break;
504                }
505                case WebFragment:
506                {
507                    //async-supported set by another fragment, this fragment's value must match
508                    if (holder.isAsyncSupported() != val)
509                        throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+servlet_name+" in "+descriptor.getResource());
510                    break;
511                }
512            }
513        }
514
515        String enabled = node.getString("enabled", false, true);
516        if (enabled!=null)
517        {
518            boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);
519            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.enabled");
520            switch (o)
521            {
522                case NotSet:
523                {
524                    //hasn't been set yet, so set it
525                    holder.setEnabled(is_enabled);
526                    context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
527                    break;
528                }
529                case WebXml:
530                case WebDefaults:
531                case WebOverride:
532                {
533                    //was set in a web xml descriptor, only allow override from another web xml descriptor
534                    if (!(descriptor instanceof FragmentDescriptor))
535                    {
536                        holder.setEnabled(is_enabled);
537                        context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
538                    }
539                    break;
540                }
541                case WebFragment:
542                {
543                    //was set by another fragment, this fragment's value must match
544                    if (holder.isEnabled() != is_enabled)
545                        throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+servlet_name+" in "+descriptor.getResource());
546                    break;
547                }
548            }
549        }
550
551        /*
552         * If multipart config not set, then set it and record it was by the web.xml or fragment.
553         * If it was set by web.xml then if this is a fragment, ignore the settings.
554         * If it was set by a fragment, if this is a fragment and the values are different, error!
555         */
556        XmlParser.Node multipart = node.get("multipart-config");
557        if (multipart != null)
558        {
559            String location = multipart.getString("location", false, true);
560            String maxFile = multipart.getString("max-file-size", false, true);
561            String maxRequest = multipart.getString("max-request-size", false, true);
562            String threshold = multipart.getString("file-size-threshold",false,true);
563            MultipartConfigElement element = new MultipartConfigElement(location,
564                                                                        (maxFile==null||"".equals(maxFile)?-1L:Long.parseLong(maxFile)),
565                                                                        (maxRequest==null||"".equals(maxRequest)?-1L:Long.parseLong(maxRequest)),
566                                                                        (threshold==null||"".equals(threshold)?0:Integer.parseInt(threshold)));
567
568            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config");
569            switch (o)
570            {
571                case NotSet:
572                {
573                    //hasn't been set, so set it
574                    holder.getRegistration().setMultipartConfig(element);
575                    context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
576                    break;
577                }
578                case WebXml:
579                case WebDefaults:
580                case WebOverride:
581                {
582                    //was set in a web xml, only allow changes if we're parsing another web xml (web.xml/web-default.xml/web-override.xml)
583                    if (!(descriptor instanceof FragmentDescriptor))
584                    {
585                        holder.getRegistration().setMultipartConfig(element);
586                        context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
587                    }
588                    break;
589                }
590                case WebFragment:
591                {
592                    //another fragment set the value, this fragment's values must match exactly or it is an error
593                    MultipartConfigElement cfg = ((ServletHolder.Registration)holder.getRegistration()).getMultipartConfig();
594
595                    if (cfg.getMaxFileSize() != element.getMaxFileSize())
596                        throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet "+servlet_name+" in "+descriptor.getResource());
597                    if (cfg.getMaxRequestSize() != element.getMaxRequestSize())
598                        throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet "+servlet_name+" in "+descriptor.getResource());
599                    if (cfg.getFileSizeThreshold() != element.getFileSizeThreshold())
600                        throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet "+servlet_name+" in "+descriptor.getResource());
601                    if ((cfg.getLocation() != null && (element.getLocation() == null || element.getLocation().length()==0))
602                            || (cfg.getLocation() == null && (element.getLocation()!=null || element.getLocation().length() > 0)))
603                        throw new IllegalStateException("Conflicting multipart-config location for servlet "+servlet_name+" in "+descriptor.getResource());
604                    break;
605                }
606            }
607        }
608    }
609
610
611
612    /**
613     * @param context
614     * @param descriptor
615     * @param node
616     */
617    protected void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
618    {
619        //Servlet Spec 3.0, p74
620        //servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
621        //Maintenance update 3.0a to spec:
622        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
623        //  <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
624
625        String servlet_name = node.getString("servlet-name", false, true);
626        Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
627
628        switch (origin)
629        {
630            case NotSet:
631            {
632                //no servlet mappings
633                context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
634                ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
635                mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
636                break;
637            }
638            case WebXml:
639            case WebDefaults:
640            case WebOverride:
641            {
642                //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
643                //otherwise just ignore it
644                if (!(descriptor instanceof FragmentDescriptor))
645                {
646                   addServletMapping(servlet_name, node, context, descriptor);
647                }
648                break;
649            }
650            case WebFragment:
651            {
652                //mappings previously set by another web-fragment, so merge in this web-fragment's mappings
653                addServletMapping(servlet_name, node, context, descriptor);
654                break;
655            }
656        }
657    }
658
659
660    /**
661     * @param context
662     * @param descriptor
663     * @param node
664     */
665    protected void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
666    {
667        XmlParser.Node tNode = node.get("session-timeout");
668        if (tNode != null)
669        {
670            int timeout = Integer.parseInt(tNode.toString(false, true));
671            context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60);
672        }
673
674        //Servlet Spec 3.0
675        // <tracking-mode>
676        // this is additive across web-fragments
677        Iterator iter = node.iterator("tracking-mode");
678        if (iter.hasNext())
679        {
680            Set<SessionTrackingMode> modes = null;
681            Origin o = context.getMetaData().getOrigin("session.tracking-mode");
682            switch (o)
683            {
684                case NotSet://not previously set, starting fresh
685                case WebDefaults://previously set in web defaults, allow this descriptor to start fresh
686                {
687
688                    modes = new HashSet<SessionTrackingMode>();
689                    context.getMetaData().setOrigin("session.tracking-mode", descriptor);
690                    break;
691                }
692                case WebXml:
693                case WebFragment:
694                case WebOverride:
695                {
696                    //if setting from an override descriptor, start afresh, otherwise add-in tracking-modes
697                    if (descriptor instanceof OverrideDescriptor)
698                        modes = new HashSet<SessionTrackingMode>();
699                    else
700                        modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes());
701                    context.getMetaData().setOrigin("session.tracking-mode", descriptor);
702                    break;
703                }
704            }
705
706            while (iter.hasNext())
707            {
708                XmlParser.Node mNode = (XmlParser.Node) iter.next();
709                String trackMode = mNode.toString(false, true);
710                modes.add(SessionTrackingMode.valueOf(trackMode));
711            }
712            context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes);
713        }
714
715
716        //Servlet Spec 3.0
717        //<cookie-config>
718        XmlParser.Node cookieConfig = node.get("cookie-config");
719        if (cookieConfig != null)
720        {
721            //  <name>
722            String name = cookieConfig.getString("name", false, true);
723            if (name != null)
724            {
725                Origin o = context.getMetaData().getOrigin("cookie-config.name");
726                switch (o)
727                {
728                    case NotSet:
729                    {
730                        //no <cookie-config><name> set yet, accept it
731                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
732                        context.getMetaData().setOrigin("cookie-config.name", descriptor);
733                        break;
734                    }
735                    case WebXml:
736                    case WebDefaults:
737                    case WebOverride:
738                    {
739                        //<cookie-config><name> set in a web xml, only allow web-default/web-override to change
740                        if (!(descriptor instanceof FragmentDescriptor))
741                        {
742                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
743                            context.getMetaData().setOrigin("cookie-config.name", descriptor);
744                        }
745                        break;
746                    }
747                    case WebFragment:
748                    {
749                        //a web-fragment set the value, all web-fragments must have the same value
750                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getName().equals(name))
751                            throw new IllegalStateException("Conflicting cookie-config name "+name+" in "+descriptor.getResource());
752                        break;
753                    }
754                }
755            }
756
757            //  <domain>
758            String domain = cookieConfig.getString("domain", false, true);
759            if (domain != null)
760            {
761                Origin o = context.getMetaData().getOrigin("cookie-config.domain");
762                switch (o)
763                {
764                    case NotSet:
765                    {
766                        //no <cookie-config><domain> set yet, accept it
767                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
768                        context.getMetaData().setOrigin("cookie-config.domain", descriptor);
769                        break;
770                    }
771                    case WebXml:
772                    case WebDefaults:
773                    case WebOverride:
774                    {
775                        //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
776                        if (!(descriptor instanceof FragmentDescriptor))
777                        {
778                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
779                            context.getMetaData().setOrigin("cookie-config.domain", descriptor);
780                        }
781                        break;
782                    }
783                    case WebFragment:
784                    {
785                        //a web-fragment set the value, all web-fragments must have the same value
786                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getDomain().equals(domain))
787                            throw new IllegalStateException("Conflicting cookie-config domain "+domain+" in "+descriptor.getResource());
788                        break;
789                    }
790                }
791            }
792
793            //  <path>
794            String path = cookieConfig.getString("path", false, true);
795            if (path != null)
796            {
797                Origin o = context.getMetaData().getOrigin("cookie-config.path");
798                switch (o)
799                {
800                    case NotSet:
801                    {
802                        //no <cookie-config><domain> set yet, accept it
803                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
804                        context.getMetaData().setOrigin("cookie-config.path", descriptor);
805                        break;
806                    }
807                    case WebXml:
808                    case WebDefaults:
809                    case WebOverride:
810                    {
811                        //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
812                        if (!(descriptor instanceof FragmentDescriptor))
813                        {
814                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
815                            context.getMetaData().setOrigin("cookie-config.path", descriptor);
816                        }
817                        break;
818                    }
819                    case WebFragment:
820                    {
821                        //a web-fragment set the value, all web-fragments must have the same value
822                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getPath().equals(path))
823                            throw new IllegalStateException("Conflicting cookie-config path "+path+" in "+descriptor.getResource());
824                        break;
825                    }
826                }
827            }
828
829            //  <comment>
830            String comment = cookieConfig.getString("comment", false, true);
831            if (comment != null)
832            {
833                Origin o = context.getMetaData().getOrigin("cookie-config.comment");
834                switch (o)
835                {
836                    case NotSet:
837                    {
838                        //no <cookie-config><comment> set yet, accept it
839                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
840                        context.getMetaData().setOrigin("cookie-config.comment", descriptor);
841                        break;
842                    }
843                    case WebXml:
844                    case WebDefaults:
845                    case WebOverride:
846                    {
847                        //<cookie-config><comment> set in a web xml, only allow web-default/web-override to change
848                        if (!(descriptor instanceof FragmentDescriptor))
849                        {
850                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
851                            context.getMetaData().setOrigin("cookie-config.comment", descriptor);
852                        }
853                        break;
854                    }
855                    case WebFragment:
856                    {
857                        //a web-fragment set the value, all web-fragments must have the same value
858                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getComment().equals(comment))
859                            throw new IllegalStateException("Conflicting cookie-config comment "+comment+" in "+descriptor.getResource());
860                        break;
861                    }
862                }
863            }
864
865            //  <http-only>true/false
866            tNode = cookieConfig.get("http-only");
867            if (tNode != null)
868            {
869                boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));
870                Origin o = context.getMetaData().getOrigin("cookie-config.http-only");
871                switch (o)
872                {
873                    case NotSet:
874                    {
875                        //no <cookie-config><http-only> set yet, accept it
876                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
877                        context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
878                        break;
879                    }
880                    case WebXml:
881                    case WebDefaults:
882                    case WebOverride:
883                    {
884                        //<cookie-config><http-only> set in a web xml, only allow web-default/web-override to change
885                        if (!(descriptor instanceof FragmentDescriptor))
886                        {
887                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
888                            context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
889                        }
890                        break;
891                    }
892                    case WebFragment:
893                    {
894                        //a web-fragment set the value, all web-fragments must have the same value
895                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isHttpOnly() != httpOnly)
896                            throw new IllegalStateException("Conflicting cookie-config http-only "+httpOnly+" in "+descriptor.getResource());
897                        break;
898                    }
899                }
900            }
901
902            //  <secure>true/false
903            tNode = cookieConfig.get("secure");
904            if (tNode != null)
905            {
906                boolean secure = Boolean.parseBoolean(tNode.toString(false,true));
907                Origin o = context.getMetaData().getOrigin("cookie-config.secure");
908                switch (o)
909                {
910                    case NotSet:
911                    {
912                        //no <cookie-config><secure> set yet, accept it
913                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
914                        context.getMetaData().setOrigin("cookie-config.secure", descriptor);
915                        break;
916                    }
917                    case WebXml:
918                    case WebDefaults:
919                    case WebOverride:
920                    {
921                        //<cookie-config><secure> set in a web xml, only allow web-default/web-override to change
922                        if (!(descriptor instanceof FragmentDescriptor))
923                        {
924                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
925                            context.getMetaData().setOrigin("cookie-config.secure", descriptor);
926                        }
927                        break;
928                    }
929                    case WebFragment:
930                    {
931                        //a web-fragment set the value, all web-fragments must have the same value
932                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isSecure() != secure)
933                            throw new IllegalStateException("Conflicting cookie-config secure "+secure+" in "+descriptor.getResource());
934                        break;
935                    }
936                }
937            }
938
939            //  <max-age>
940            tNode = cookieConfig.get("max-age");
941            if (tNode != null)
942            {
943                int maxAge = Integer.parseInt(tNode.toString(false,true));
944                Origin o = context.getMetaData().getOrigin("cookie-config.max-age");
945                switch (o)
946                {
947                    case NotSet:
948                    {
949                        //no <cookie-config><max-age> set yet, accept it
950                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
951                        context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
952                        break;
953                    }
954                    case WebXml:
955                    case WebDefaults:
956                    case WebOverride:
957                    {
958                        //<cookie-config><max-age> set in a web xml, only allow web-default/web-override to change
959                        if (!(descriptor instanceof FragmentDescriptor))
960                        {
961                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
962                            context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
963                        }
964                        break;
965                    }
966                    case WebFragment:
967                    {
968                        //a web-fragment set the value, all web-fragments must have the same value
969                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().getMaxAge() != maxAge)
970                            throw new IllegalStateException("Conflicting cookie-config max-age "+maxAge+" in "+descriptor.getResource());
971                        break;
972                    }
973                }
974            }
975        }
976    }
977
978
979
980    /**
981     * @param context
982     * @param descriptor
983     * @param node
984     */
985    protected void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
986    {
987        String extension = node.getString("extension", false, true);
988        if (extension != null && extension.startsWith("."))
989            extension = extension.substring(1);
990        String mimeType = node.getString("mime-type", false, true);
991        if (extension != null)
992        {
993            Origin o = context.getMetaData().getOrigin("extension."+extension);
994            switch (o)
995            {
996                case NotSet:
997                {
998                    //no mime-type set for the extension yet
999                    context.getMimeTypes().addMimeMapping(extension, mimeType);
1000                    context.getMetaData().setOrigin("extension."+extension, descriptor);
1001                    break;
1002                }
1003                case WebXml:
1004                case WebDefaults:
1005                case WebOverride:
1006                {
1007                    //a mime-type was set for the extension in a web xml, only allow web-default/web-override to change
1008                    if (!(descriptor instanceof FragmentDescriptor))
1009                    {
1010                        context.getMimeTypes().addMimeMapping(extension, mimeType);
1011                        context.getMetaData().setOrigin("extension."+extension, descriptor);
1012                    }
1013                    break;
1014                }
1015                case WebFragment:
1016                {
1017                    //a web-fragment set the value, all web-fragments must have the same value
1018                    if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(context.getMimeTypes().CACHE.lookup(mimeType)))
1019                        throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
1020                    break;
1021                }
1022            }
1023        }
1024    }
1025
1026    /**
1027     * @param context
1028     * @param descriptor
1029     * @param node
1030     */
1031    protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1032    {
1033        Origin o = context.getMetaData().getOrigin("welcome-file-list");
1034        switch (o)
1035        {
1036            case NotSet:
1037            {
1038                context.getMetaData().setOrigin("welcome-file-list", descriptor);
1039                addWelcomeFiles(context,node);
1040                break;
1041            }
1042            case WebXml:
1043            {
1044                //web.xml set the welcome-file-list, all other descriptors then just merge in
1045                addWelcomeFiles(context,node);
1046                break;
1047            }
1048            case WebDefaults:
1049            {
1050                //if web-defaults set the welcome-file-list first and
1051                //we're processing web.xml then reset the welcome-file-list
1052                if (!(descriptor instanceof DefaultsDescriptor) && !(descriptor instanceof OverrideDescriptor) && !(descriptor instanceof FragmentDescriptor))
1053                {
1054                    context.setWelcomeFiles(new String[0]);
1055                }
1056                addWelcomeFiles(context,node);
1057                break;
1058            }
1059            case WebOverride:
1060            {
1061                //web-override set the list, all other descriptors just merge in
1062                addWelcomeFiles(context,node);
1063                break;
1064            }
1065            case WebFragment:
1066            {
1067                //A web-fragment first set the welcome-file-list. Other descriptors just add.
1068                addWelcomeFiles(context,node);
1069                break;
1070            }
1071        }
1072    }
1073
1074    /**
1075     * @param context
1076     * @param descriptor
1077     * @param node
1078     */
1079    protected void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1080    {
1081        Iterator<XmlParser.Node> iter = node.iterator("locale-encoding-mapping");
1082        while (iter.hasNext())
1083        {
1084            XmlParser.Node mapping = iter.next();
1085            String locale = mapping.getString("locale", false, true);
1086            String encoding = mapping.getString("encoding", false, true);
1087
1088            if (encoding != null)
1089            {
1090                Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
1091                switch (o)
1092                {
1093                    case NotSet:
1094                    {
1095                        //no mapping for the locale yet, so set it
1096                        context.addLocaleEncoding(locale, encoding);
1097                        context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
1098                        break;
1099                    }
1100                    case WebXml:
1101                    case WebDefaults:
1102                    case WebOverride:
1103                    {
1104                        //a value was set in a web descriptor, only allow another web descriptor to change it (web-default/web-override)
1105                        if (!(descriptor instanceof FragmentDescriptor))
1106                        {
1107                            context.addLocaleEncoding(locale, encoding);
1108                            context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
1109                        }
1110                        break;
1111                    }
1112                    case WebFragment:
1113                    {
1114                        //a value was set by a web-fragment, all fragments must have the same value
1115                        if (!encoding.equals(context.getLocaleEncoding(locale)))
1116                            throw new IllegalStateException("Conflicting loacle-encoding mapping for locale "+locale+" in "+descriptor.getResource());
1117                        break;
1118                    }
1119                }
1120            }
1121        }
1122    }
1123
1124    /**
1125     * @param context
1126     * @param descriptor
1127     * @param node
1128     */
1129    protected void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1130    {
1131        String error = node.getString("error-code", false, true);
1132        int code=0;
1133        if (error == null || error.length() == 0)
1134        {
1135            error = node.getString("exception-type", false, true);
1136            if (error == null || error.length() == 0)
1137                error = ErrorPageErrorHandler.GLOBAL_ERROR_PAGE;
1138        }
1139        else
1140            code=Integer.valueOf(error);
1141
1142        String location = node.getString("location", false, true);
1143        ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
1144        Origin o = context.getMetaData().getOrigin("error."+error);
1145
1146        switch (o)
1147        {
1148            case NotSet:
1149            {
1150                //no error page setup for this code or exception yet
1151                if (code>0)
1152                    handler.addErrorPage(code,location);
1153                else
1154                    handler.addErrorPage(error,location);
1155                context.getMetaData().setOrigin("error."+error, descriptor);
1156                break;
1157            }
1158            case WebXml:
1159            case WebDefaults:
1160            case WebOverride:
1161            {
1162                //an error page setup was set in web.xml, only allow other web xml descriptors to override it
1163                if (!(descriptor instanceof FragmentDescriptor))
1164                {
1165                    if (descriptor instanceof OverrideDescriptor || descriptor instanceof DefaultsDescriptor)
1166                    {
1167                        if (code>0)
1168                            handler.addErrorPage(code,location);
1169                        else
1170                            handler.addErrorPage(error,location);
1171                        context.getMetaData().setOrigin("error."+error, descriptor);
1172                    }
1173                    else
1174                        throw new IllegalStateException("Duplicate global error-page "+location);
1175                }
1176                break;
1177            }
1178            case WebFragment:
1179            {
1180                //another web fragment set the same error code or exception, if its different its an error
1181                if (!handler.getErrorPages().get(error).equals(location))
1182                    throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
1183                break;
1184            }
1185        }
1186
1187    }
1188
1189    /**
1190     * @param context
1191     * @param node
1192     */
1193    protected void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
1194    {
1195        Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
1196        while (iter.hasNext())
1197        {
1198            XmlParser.Node indexNode = (XmlParser.Node) iter.next();
1199            String welcome = indexNode.toString(false, true);
1200
1201            //Servlet Spec 3.0 p. 74 welcome files are additive
1202            if (welcome != null && welcome.trim().length() > 0)
1203                context.setWelcomeFiles((String[])LazyList.addToArray(context.getWelcomeFiles(),welcome,String.class));
1204        }
1205    }
1206
1207
1208    /**
1209     * @param servletName
1210     * @param node
1211     * @param context
1212     */
1213    protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
1214    {
1215        ServletMapping mapping = new ServletMapping();
1216        mapping.setServletName(servletName);
1217
1218        List<String> paths = new ArrayList<String>();
1219        Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
1220        while (iter.hasNext())
1221        {
1222            String p = iter.next().toString(false, true);
1223            p = normalizePattern(p);
1224            paths.add(p);
1225            context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
1226        }
1227        mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
1228        context.getServletHandler().addServletMapping(mapping);
1229        return mapping;
1230    }
1231
1232    /**
1233     * @param filterName
1234     * @param node
1235     * @param context
1236     */
1237    protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
1238    {
1239        FilterMapping mapping = new FilterMapping();
1240        mapping.setFilterName(filterName);
1241
1242        List<String> paths = new ArrayList<String>();
1243        Iterator<XmlParser.Node>  iter = node.iterator("url-pattern");
1244        while (iter.hasNext())
1245        {
1246            String p = iter.next().toString(false, true);
1247            p = normalizePattern(p);
1248            paths.add(p);
1249            context.getMetaData().setOrigin(filterName+".filter.mapping."+p, descriptor);
1250        }
1251        mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
1252
1253        List<String> names = new ArrayList<String>();
1254        iter = node.iterator("servlet-name");
1255        while (iter.hasNext())
1256        {
1257            String n = ((XmlParser.Node) iter.next()).toString(false, true);
1258            names.add(n);
1259        }
1260        mapping.setServletNames((String[]) names.toArray(new String[names.size()]));
1261
1262
1263        List<DispatcherType> dispatches = new ArrayList<DispatcherType>();
1264        iter=node.iterator("dispatcher");
1265        while(iter.hasNext())
1266        {
1267            String d=((XmlParser.Node)iter.next()).toString(false,true);
1268            dispatches.add(FilterMapping.dispatch(d));
1269        }
1270
1271        if (dispatches.size()>0)
1272            mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
1273
1274        context.getServletHandler().addFilterMapping(mapping);
1275    }
1276
1277
1278    /**
1279     * @param context
1280     * @param descriptor
1281     * @param node
1282     */
1283    protected void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1284    {
1285        //Additive across web.xml and web-fragment.xml
1286        String uri = node.getString("taglib-uri", false, true);
1287        String location = node.getString("taglib-location", false, true);
1288
1289        context.setResourceAlias(uri, location);
1290
1291        JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
1292        if (config == null)
1293        {
1294            config = new JspConfig();
1295            context.getServletContext().setJspConfigDescriptor(config);
1296        }
1297
1298        TagLib tl = new TagLib();
1299        tl.setTaglibLocation(location);
1300        tl.setTaglibURI(uri);
1301        config.addTaglibDescriptor(tl);
1302    }
1303
1304    /**
1305     * @param context
1306     * @param descriptor
1307     * @param node
1308     */
1309    protected void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1310    {
1311        //Additive across web.xml and web-fragment.xml
1312        JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
1313        if (config == null)
1314        {
1315           config = new JspConfig();
1316           context.getServletContext().setJspConfigDescriptor(config);
1317        }
1318
1319
1320        for (int i = 0; i < node.size(); i++)
1321        {
1322            Object o = node.get(i);
1323            if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag()))
1324                visitTagLib(context,descriptor, (XmlParser.Node) o);
1325        }
1326
1327        // Map URLs from jsp property groups to JSP servlet.
1328        // this is more JSP stupidness creeping into the servlet spec
1329        Iterator<XmlParser.Node> iter = node.iterator("jsp-property-group");
1330        List<String> paths = new ArrayList<String>();
1331        while (iter.hasNext())
1332        {
1333            JspPropertyGroup jpg = new JspPropertyGroup();
1334            config.addJspPropertyGroup(jpg);
1335            XmlParser.Node group = iter.next();
1336
1337            //url-patterns
1338            Iterator<XmlParser.Node> iter2 = group.iterator("url-pattern");
1339            while (iter2.hasNext())
1340            {
1341                String url = iter2.next().toString(false, true);
1342                url = normalizePattern(url);
1343                paths.add( url);
1344                jpg.addUrlPattern(url);
1345            }
1346
1347            jpg.setElIgnored(group.getString("el-ignored", false, true));
1348            jpg.setPageEncoding(group.getString("page-encoding", false, true));
1349            jpg.setScriptingInvalid(group.getString("scripting-invalid", false, true));
1350            jpg.setIsXml(group.getString("is-xml", false, true));
1351            jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true));
1352            jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true));
1353            jpg.setDefaultContentType(group.getString("default-content-type", false, true));
1354            jpg.setBuffer(group.getString("buffer", false, true));
1355            jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
1356
1357            //preludes
1358            Iterator<XmlParser.Node> preludes = group.iterator("include-prelude");
1359            while (preludes.hasNext())
1360            {
1361                String prelude = preludes.next().toString(false, true);
1362                jpg.addIncludePrelude(prelude);
1363            }
1364            //codas
1365            Iterator<XmlParser.Node> codas = group.iterator("include-coda");
1366            while (codas.hasNext())
1367            {
1368                String coda = codas.next().toString(false, true);
1369                jpg.addIncludeCoda(coda);
1370            }
1371
1372            if (LOG.isDebugEnabled()) LOG.debug(config.toString());
1373        }
1374
1375        if (paths.size() > 0)
1376        {
1377            ServletHandler handler = context.getServletHandler();
1378            ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
1379            if (jsp_pg_servlet==null)
1380            {
1381                jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
1382                handler.addServlet(jsp_pg_servlet);
1383            }
1384
1385            ServletMapping mapping = new ServletMapping();
1386            mapping.setServletName(JspPropertyGroupServlet.NAME);
1387            mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
1388            context.getServletHandler().addServletMapping(mapping);
1389        }
1390    }
1391
1392    /**
1393     * @param context
1394     * @param descriptor
1395     * @param node
1396     */
1397    protected void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1398    {
1399        Constraint scBase = new Constraint();
1400
1401        //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive
1402        //across fragments
1403
1404        //TODO: need to remember origin of the constraints
1405        try
1406        {
1407            XmlParser.Node auths = node.get("auth-constraint");
1408
1409            if (auths != null)
1410            {
1411                scBase.setAuthenticate(true);
1412                // auth-constraint
1413                Iterator<XmlParser.Node> iter = auths.iterator("role-name");
1414                List<String> roles = new ArrayList<String>();
1415                while (iter.hasNext())
1416                {
1417                    String role = iter.next().toString(false, true);
1418                    roles.add(role);
1419                }
1420                scBase.setRoles(roles.toArray(new String[roles.size()]));
1421            }
1422
1423            XmlParser.Node data = node.get("user-data-constraint");
1424            if (data != null)
1425            {
1426                data = data.get("transport-guarantee");
1427                String guarantee = data.toString(false, true).toUpperCase(Locale.ENGLISH);
1428                if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee))
1429                    scBase.setDataConstraint(Constraint.DC_NONE);
1430                else if ("INTEGRAL".equals(guarantee))
1431                    scBase.setDataConstraint(Constraint.DC_INTEGRAL);
1432                else if ("CONFIDENTIAL".equals(guarantee))
1433                    scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
1434                else
1435                {
1436                    LOG.warn("Unknown user-data-constraint:" + guarantee);
1437                    scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
1438                }
1439            }
1440            Iterator<XmlParser.Node> iter = node.iterator("web-resource-collection");
1441            while (iter.hasNext())
1442            {
1443                XmlParser.Node collection =  iter.next();
1444                String name = collection.getString("web-resource-name", false, true);
1445                Constraint sc = (Constraint) scBase.clone();
1446                sc.setName(name);
1447
1448                Iterator<XmlParser.Node> iter2 = collection.iterator("url-pattern");
1449                while (iter2.hasNext())
1450                {
1451                    String url = iter2.next().toString(false, true);
1452                    url = normalizePattern(url);
1453                    //remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
1454                    context.getMetaData().setOrigin("constraint.url."+url, descriptor);
1455
1456                    Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
1457                    Iterator<XmlParser.Node> iter4 = collection.iterator("http-method-omission");
1458
1459                    if (iter3.hasNext())
1460                    {
1461                        if (iter4.hasNext())
1462                            throw new IllegalStateException ("web-resource-collection cannot contain both http-method and http-method-omission");
1463
1464                        //configure all the http-method elements for each url
1465                        while (iter3.hasNext())
1466                        {
1467                            String method = ((XmlParser.Node) iter3.next()).toString(false, true);
1468                            ConstraintMapping mapping = new ConstraintMapping();
1469                            mapping.setMethod(method);
1470                            mapping.setPathSpec(url);
1471                            mapping.setConstraint(sc);
1472                            ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
1473                        }
1474                    }
1475                    else if (iter4.hasNext())
1476                    {
1477                        //configure all the http-method-omission elements for each url
1478                        while (iter4.hasNext())
1479                        {
1480                            String method = ((XmlParser.Node)iter4.next()).toString(false, true);
1481                            ConstraintMapping mapping = new ConstraintMapping();
1482                            mapping.setMethodOmissions(new String[]{method});
1483                            mapping.setPathSpec(url);
1484                            mapping.setConstraint(sc);
1485                            ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
1486                        }
1487                    }
1488                    else
1489                    {
1490                        //No http-methods or http-method-omissions specified, the constraint applies to all
1491                        ConstraintMapping mapping = new ConstraintMapping();
1492                        mapping.setPathSpec(url);
1493                        mapping.setConstraint(sc);
1494                        ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
1495                    }
1496                }
1497            }
1498        }
1499        catch (CloneNotSupportedException e)
1500        {
1501            LOG.warn(e);
1502        }
1503    }
1504
1505    /**
1506     * @param context
1507     * @param descriptor
1508     * @param node
1509     * @throws Exception
1510     */
1511    protected void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
1512    {
1513        //ServletSpec 3.0 p74 says elements present 0/1 time if specified in web.xml take
1514        //precendece over any web-fragment. If not specified in web.xml, then if specified
1515        //in a web-fragment must be the same across all web-fragments.
1516        XmlParser.Node method = node.get("auth-method");
1517        if (method != null)
1518        {
1519            //handle auth-method merge
1520            Origin o = context.getMetaData().getOrigin("auth-method");
1521            switch (o)
1522            {
1523                case NotSet:
1524                {
1525                    //not already set, so set it now
1526                    context.getSecurityHandler().setAuthMethod(method.toString(false, true));
1527                    context.getMetaData().setOrigin("auth-method", descriptor);
1528                    break;
1529                }
1530                case WebXml:
1531                case WebDefaults:
1532                case WebOverride:
1533                {
1534                    //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
1535                    if (!(descriptor instanceof FragmentDescriptor))
1536                    {
1537                        context.getSecurityHandler().setAuthMethod(method.toString(false, true));
1538                        context.getMetaData().setOrigin("auth-method", descriptor);
1539                    }
1540                    break;
1541                }
1542                case WebFragment:
1543                {
1544                    //it was already set by another fragment, if we're parsing a fragment, the values must match
1545                    if (!context.getSecurityHandler().getAuthMethod().equals(method.toString(false, true)))
1546                        throw new IllegalStateException("Conflicting auth-method value in "+descriptor.getResource());
1547                    break;
1548                }
1549            }
1550
1551            //handle realm-name merge
1552            XmlParser.Node name = node.get("realm-name");
1553            String nameStr = (name == null ? "default" : name.toString(false, true));
1554            o = context.getMetaData().getOrigin("realm-name");
1555            switch (o)
1556            {
1557                case NotSet:
1558                {
1559                    //no descriptor has set the realm-name yet, so set it
1560                    context.getSecurityHandler().setRealmName(nameStr);
1561                    context.getMetaData().setOrigin("realm-name", descriptor);
1562                    break;
1563                }
1564                case WebXml:
1565                case WebDefaults:
1566                case WebOverride:
1567                {
1568                    //set by a web xml file (web.xml/web-default.xm/web-override.xml), only allow it to be changed by another web xml file
1569                    if (!(descriptor instanceof FragmentDescriptor))
1570                    {
1571                        context.getSecurityHandler().setRealmName(nameStr);
1572                        context.getMetaData().setOrigin("realm-name", descriptor);
1573                    }
1574                    break;
1575                }
1576                case WebFragment:
1577                {
1578                    //a fragment set it, and we must be parsing another fragment, so the values must match
1579                    if (!context.getSecurityHandler().getRealmName().equals(nameStr))
1580                        throw new IllegalStateException("Conflicting realm-name value in "+descriptor.getResource());
1581                    break;
1582                }
1583            }
1584
1585            if (Constraint.__FORM_AUTH.equals(context.getSecurityHandler().getAuthMethod()))
1586            {
1587                XmlParser.Node formConfig = node.get("form-login-config");
1588                if (formConfig != null)
1589                {
1590                    String loginPageName = null;
1591                    XmlParser.Node loginPage = formConfig.get("form-login-page");
1592                    if (loginPage != null)
1593                        loginPageName = loginPage.toString(false, true);
1594                    String errorPageName = null;
1595                    XmlParser.Node errorPage = formConfig.get("form-error-page");
1596                    if (errorPage != null)
1597                        errorPageName = errorPage.toString(false, true);
1598
1599                    //handle form-login-page
1600                    o = context.getMetaData().getOrigin("form-login-page");
1601                    switch (o)
1602                    {
1603                        case NotSet:
1604                        {
1605                            //Never been set before, so accept it
1606                            context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
1607                            context.getMetaData().setOrigin("form-login-page",descriptor);
1608                            break;
1609                        }
1610                        case WebXml:
1611                        case WebDefaults:
1612                        case WebOverride:
1613                        {
1614                            //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
1615                            if (!(descriptor instanceof FragmentDescriptor))
1616                            {
1617                                context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
1618                                context.getMetaData().setOrigin("form-login-page",descriptor);
1619                            }
1620                            break;
1621                        }
1622                        case WebFragment:
1623                        {
1624                            //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
1625                            if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE).equals(loginPageName))
1626                                throw new IllegalStateException("Conflicting form-login-page value in "+descriptor.getResource());
1627                            break;
1628                        }
1629                    }
1630
1631                    //handle form-error-page
1632                    o = context.getMetaData().getOrigin("form-error-page");
1633                    switch (o)
1634                    {
1635                        case NotSet:
1636                        {
1637                            //Never been set before, so accept it
1638                            context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
1639                            context.getMetaData().setOrigin("form-error-page",descriptor);
1640                            break;
1641                        }
1642                        case WebXml:
1643                        case WebDefaults:
1644                        case WebOverride:
1645                        {
1646                            //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
1647                            if (!(descriptor instanceof FragmentDescriptor))
1648                            {
1649                                context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
1650                                context.getMetaData().setOrigin("form-error-page",descriptor);
1651                            }
1652                            break;
1653                        }
1654                        case WebFragment:
1655                        {
1656                            //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
1657                            if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE).equals(errorPageName))
1658                                throw new IllegalStateException("Conflicting form-error-page value in "+descriptor.getResource());
1659                            break;
1660                        }
1661                    }
1662                }
1663                else
1664                {
1665                    throw new IllegalStateException("!form-login-config");
1666                }
1667            }
1668        }
1669    }
1670
1671    /**
1672     * @param context
1673     * @param descriptor
1674     * @param node
1675     */
1676    protected void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1677    {
1678        //ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged
1679        XmlParser.Node roleNode = node.get("role-name");
1680        String role = roleNode.toString(false, true);
1681        ((ConstraintAware)context.getSecurityHandler()).addRole(role);
1682    }
1683
1684
1685    /**
1686     * @param context
1687     * @param descriptor
1688     * @param node
1689     */
1690    protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1691    {
1692        String name = node.getString("filter-name", false, true);
1693        FilterHolder holder = context.getServletHandler().getFilter(name);
1694        if (holder == null)
1695        {
1696            holder = context.getServletHandler().newFilterHolder(Holder.Source.DESCRIPTOR);
1697            holder.setName(name);
1698            context.getServletHandler().addFilter(holder);
1699        }
1700
1701        String filter_class = node.getString("filter-class", false, true);
1702        if (filter_class != null)
1703        {
1704            ((WebDescriptor)descriptor).addClassName(filter_class);
1705
1706            Origin o = context.getMetaData().getOrigin(name+".filter.filter-class");
1707            switch (o)
1708            {
1709                case NotSet:
1710                {
1711                    //no class set yet
1712                    holder.setClassName(filter_class);
1713                    context.getMetaData().setOrigin(name+".filter.filter-class", descriptor);
1714                    break;
1715                }
1716                case WebXml:
1717                case WebDefaults:
1718                case WebOverride:
1719                {
1720                    //filter class was set in web.xml, only allow other web xml descriptors (override/default) to change it
1721                    if (!(descriptor instanceof FragmentDescriptor))
1722                    {
1723                        holder.setClassName(filter_class);
1724                        context.getMetaData().setOrigin(name+".filter.filter-class", descriptor);
1725                    }
1726                    break;
1727                }
1728                case WebFragment:
1729                {
1730                    //the filter class was set up by a web fragment, all fragments must be the same
1731                    if (!holder.getClassName().equals(filter_class))
1732                        throw new IllegalStateException("Conflicting filter-class for filter "+name+" in "+descriptor.getResource());
1733                    break;
1734                }
1735            }
1736
1737        }
1738
1739        Iterator<XmlParser.Node>  iter = node.iterator("init-param");
1740        while (iter.hasNext())
1741        {
1742            XmlParser.Node paramNode = iter.next();
1743            String pname = paramNode.getString("param-name", false, true);
1744            String pvalue = paramNode.getString("param-value", false, true);
1745
1746            Origin origin = context.getMetaData().getOrigin(name+".filter.init-param."+pname);
1747            switch (origin)
1748            {
1749                case NotSet:
1750                {
1751                    //init-param not already set, so set it
1752                    holder.setInitParameter(pname, pvalue);
1753                    context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
1754                    break;
1755                }
1756                case WebXml:
1757                case WebDefaults:
1758                case WebOverride:
1759                {
1760                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
1761                    //otherwise just ignore it
1762                    if (!(descriptor instanceof FragmentDescriptor))
1763                    {
1764                        holder.setInitParameter(pname, pvalue);
1765                        context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
1766                    }
1767                    break;
1768                }
1769                case WebFragment:
1770                {
1771                    //previously set by a web-fragment, make sure that the value matches, otherwise its an error
1772                    if (!holder.getInitParameter(pname).equals(pvalue))
1773                        throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
1774                    break;
1775                }
1776            }
1777        }
1778
1779        String async=node.getString("async-supported",false,true);
1780        if (async!=null)
1781            holder.setAsyncSupported(async.length()==0||Boolean.valueOf(async));
1782        if (async!=null)
1783        {
1784            boolean val = async.length()==0||Boolean.valueOf(async);
1785            Origin o = context.getMetaData().getOrigin(name+".filter.async-supported");
1786            switch (o)
1787            {
1788                case NotSet:
1789                {
1790                    //set it
1791                    holder.setAsyncSupported(val);
1792                    context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);
1793                    break;
1794                }
1795                case WebXml:
1796                case WebDefaults:
1797                case WebOverride:
1798                {
1799                    //async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
1800                    if (!(descriptor instanceof FragmentDescriptor))
1801                    {
1802                        holder.setAsyncSupported(val);
1803                        context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);
1804                    }
1805                    break;
1806                }
1807                case WebFragment:
1808                {
1809                    //async-supported set by another fragment, this fragment's value must match
1810                    if (holder.isAsyncSupported() != val)
1811                        throw new IllegalStateException("Conflicting async-supported="+async+" for filter "+name+" in "+descriptor.getResource());
1812                    break;
1813                }
1814            }
1815        }
1816
1817    }
1818
1819    /**
1820     * @param context
1821     * @param descriptor
1822     * @param node
1823     */
1824    protected void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1825    {
1826        //Servlet Spec 3.0, p74
1827        //filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
1828        //Maintenance update 3.0a to spec:
1829        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
1830
1831
1832        String filter_name = node.getString("filter-name", false, true);
1833
1834        Origin origin = context.getMetaData().getOrigin(filter_name+".filter.mappings");
1835
1836        switch (origin)
1837        {
1838            case NotSet:
1839            {
1840                //no filtermappings for this filter yet defined
1841                context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor);
1842                addFilterMapping(filter_name, node, context, descriptor);
1843                break;
1844            }
1845            case WebDefaults:
1846            case WebOverride:
1847            case WebXml:
1848            {
1849                //filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings.
1850                if (!(descriptor instanceof FragmentDescriptor))
1851                {
1852                   addFilterMapping(filter_name, node, context, descriptor);
1853                }
1854                break;
1855            }
1856            case WebFragment:
1857            {
1858                //filter mappings first defined in a web-fragment, allow other fragments to add
1859                addFilterMapping(filter_name, node, context, descriptor);
1860                break;
1861            }
1862        }
1863    }
1864
1865
1866    /**
1867     * @param context
1868     * @param descriptor
1869     * @param node
1870     */
1871    protected void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1872    {
1873        String className = node.getString("listener-class", false, true);
1874        EventListener listener = null;
1875        try
1876        {
1877            if (className != null && className.length()> 0)
1878            {
1879                //Servlet Spec 3.0 p 74
1880                //Duplicate listener declarations don't result in duplicate listener instances
1881                EventListener[] listeners=context.getEventListeners();
1882                if (listeners!=null)
1883                {
1884                    for (EventListener l : listeners)
1885                    {
1886                        if (l.getClass().getName().equals(className))
1887                            return;
1888                    }
1889                }
1890
1891                ((WebDescriptor)descriptor).addClassName(className);
1892
1893                Class<? extends EventListener> listenerClass = (Class<? extends EventListener>)context.loadClass(className);
1894                listener = newListenerInstance(context,listenerClass);
1895                if (!(listener instanceof EventListener))
1896                {
1897                    LOG.warn("Not an EventListener: " + listener);
1898                    return;
1899                }
1900                context.addEventListener(listener);
1901                context.getMetaData().setOrigin(className+".listener", descriptor);
1902
1903            }
1904        }
1905        catch (Exception e)
1906        {
1907            LOG.warn("Could not instantiate listener " + className, e);
1908            return;
1909        }
1910    }
1911
1912    /**
1913     * @param context
1914     * @param descriptor
1915     * @param node
1916     */
1917    protected void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1918    {
1919        // the element has no content, so its simple presence
1920        // indicates that the webapp is distributable...
1921        //Servlet Spec 3.0 p.74  distributable only if all fragments are distributable
1922        ((WebDescriptor)descriptor).setDistributable(true);
1923    }
1924
1925    /**
1926     * @param context
1927     * @param clazz
1928     * @return the new event listener
1929     * @throws ServletException
1930     * @throws InstantiationException
1931     * @throws IllegalAccessException
1932     */
1933    protected EventListener newListenerInstance(WebAppContext context,Class<? extends EventListener> clazz) throws ServletException, InstantiationException, IllegalAccessException
1934    {
1935        try
1936        {
1937            return context.getServletContext().createListener(clazz);
1938        }
1939        catch (ServletException se)
1940        {
1941            Throwable cause = se.getRootCause();
1942            if (cause instanceof InstantiationException)
1943                throw (InstantiationException)cause;
1944            if (cause instanceof IllegalAccessException)
1945                throw (IllegalAccessException)cause;
1946            throw se;
1947        }
1948    }
1949
1950    /**
1951     * @param p
1952     * @return the normalized pattern
1953     */
1954    protected String normalizePattern(String p)
1955    {
1956        if (p != null && p.length() > 0 && !p.startsWith("/") && !p.startsWith("*")) return "/" + p;
1957        return p;
1958    }
1959
1960
1961}
1962