View Javadoc

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