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