View Javadoc

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