View Javadoc

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