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