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