View Javadoc

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