View Javadoc

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