View Javadoc

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