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.JspPropertyGroupServlet;
45  import org.eclipse.jetty.servlet.ServletContextHandler;
46  import org.eclipse.jetty.servlet.ServletHandler;
47  import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
48  import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
49  import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
50  import org.eclipse.jetty.servlet.ServletHolder;
51  import org.eclipse.jetty.servlet.ServletMapping;
52  import org.eclipse.jetty.util.ArrayUtil;
53  import org.eclipse.jetty.util.Loader;
54  import org.eclipse.jetty.util.log.Log;
55  import org.eclipse.jetty.util.log.Logger;
56  import org.eclipse.jetty.util.security.Constraint;
57  import org.eclipse.jetty.xml.XmlParser;
58  
59  /**
60   * StandardDescriptorProcessor
61   *
62   * Process a web.xml, web-defaults.xml, web-overrides.xml, web-fragment.xml.
63   */
64  public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
65  {
66      private static final Logger LOG = Log.getLogger(StandardDescriptorProcessor.class);
67  
68      public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
69  
70  
71  
72      public StandardDescriptorProcessor ()
73      {
74  
75          try
76          {
77              registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature));
78              registerVisitor("display-name", this.getClass().getDeclaredMethod("visitDisplayName", __signature));
79              registerVisitor("servlet", this.getClass().getDeclaredMethod("visitServlet",  __signature));
80              registerVisitor("servlet-mapping", this.getClass().getDeclaredMethod("visitServletMapping",  __signature));
81              registerVisitor("session-config", this.getClass().getDeclaredMethod("visitSessionConfig",  __signature));
82              registerVisitor("mime-mapping", this.getClass().getDeclaredMethod("visitMimeMapping",  __signature));
83              registerVisitor("welcome-file-list", this.getClass().getDeclaredMethod("visitWelcomeFileList",  __signature));
84              registerVisitor("locale-encoding-mapping-list", this.getClass().getDeclaredMethod("visitLocaleEncodingList",  __signature));
85              registerVisitor("error-page", this.getClass().getDeclaredMethod("visitErrorPage",  __signature));
86              registerVisitor("taglib", this.getClass().getDeclaredMethod("visitTagLib",  __signature));
87              registerVisitor("jsp-config", this.getClass().getDeclaredMethod("visitJspConfig",  __signature));
88              registerVisitor("security-constraint", this.getClass().getDeclaredMethod("visitSecurityConstraint",  __signature));
89              registerVisitor("login-config", this.getClass().getDeclaredMethod("visitLoginConfig",  __signature));
90              registerVisitor("security-role", this.getClass().getDeclaredMethod("visitSecurityRole",  __signature));
91              registerVisitor("filter", this.getClass().getDeclaredMethod("visitFilter",  __signature));
92              registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping",  __signature));
93              registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener",  __signature));
94              registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable",  __signature));
95          }
96          catch (Exception e)
97          {
98              throw new IllegalStateException(e);
99          }
100     }
101 
102 
103 
104     /**
105      * {@inheritDoc}
106      */
107     public void start(WebAppContext context, Descriptor descriptor)
108     {
109     }
110 
111 
112 
113     /**
114      * {@inheritDoc}
115      */
116     public void end(WebAppContext context, Descriptor descriptor)
117     {
118     }
119 
120     /**
121      * @param context
122      * @param descriptor
123      * @param node
124      */
125     public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
126     {
127         String name = node.getString("param-name", false, true);
128         String value = node.getString("param-value", false, true);
129         Origin o = context.getMetaData().getOrigin("context-param."+name);
130         switch (o)
131         {
132             case NotSet:
133             {
134                 //just set it
135                 context.getInitParams().put(name, value);
136                 context.getMetaData().setOrigin("context-param."+name, descriptor);
137                 break;
138             }
139             case WebXml:
140             case WebDefaults:
141             case WebOverride:
142             {
143                 //previously set by a web xml, allow other web xml files to override
144                 if (!(descriptor instanceof FragmentDescriptor))
145                 {
146                     context.getInitParams().put(name, value);
147                     context.getMetaData().setOrigin("context-param."+name, descriptor);
148                 }
149                 break;
150             }
151             case WebFragment:
152             {
153                 //previously set by a web-fragment, this fragment's value must be the same
154                 if (descriptor instanceof FragmentDescriptor)
155                 {
156                     if (!((String)context.getInitParams().get(name)).equals(value))
157                         throw new IllegalStateException("Conflicting context-param "+name+"="+value+" in "+descriptor.getResource());
158                 }
159                 break;
160             }
161         }
162         if (LOG.isDebugEnabled())
163             LOG.debug("ContextParam: " + name + "=" + value);
164 
165     }
166 
167 
168     /* ------------------------------------------------------------ */
169     /**
170      * @param context
171      * @param descriptor
172      * @param node
173      */
174     protected void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
175     {
176         //Servlet Spec 3.0 p. 74 Ignore from web-fragments
177         if (!(descriptor instanceof FragmentDescriptor))
178         {
179             context.setDisplayName(node.toString(false, true));
180             context.getMetaData().setOrigin("display-name", descriptor);
181         }
182     }
183 
184 
185     /**
186      * @param context
187      * @param descriptor
188      * @param node
189      */
190     protected void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
191     {
192         String id = node.getAttribute("id");
193 
194         // initialize holder
195         String servlet_name = node.getString("servlet-name", false, true);
196         ServletHolder holder = context.getServletHandler().getServlet(servlet_name);
197 
198         /*
199          * If servlet of that name does not already exist, create it.
200          */
201         if (holder == null)
202         {
203             holder = context.getServletHandler().newServletHolder(Holder.Source.DESCRIPTOR);
204             holder.setName(servlet_name);
205             context.getServletHandler().addServlet(holder);
206         }
207 
208         // init params
209         Iterator<?> iParamsIter = node.iterator("init-param");
210         while (iParamsIter.hasNext())
211         {
212             XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
213             String pname = paramNode.getString("param-name", false, true);
214             String pvalue = paramNode.getString("param-value", false, true);
215 
216             Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
217 
218             switch (origin)
219             {
220                 case NotSet:
221                 {
222                     //init-param not already set, so set it
223 
224                     holder.setInitParameter(pname, pvalue);
225                     context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
226                     break;
227                 }
228                 case WebXml:
229                 case WebDefaults:
230                 case WebOverride:
231                 {
232                     //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
233                     //otherwise just ignore it
234                     if (!(descriptor instanceof FragmentDescriptor))
235                     {
236                         holder.setInitParameter(pname, pvalue);
237                         context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
238                     }
239                     break;
240                 }
241                 case WebFragment:
242                 {
243                     //previously set by a web-fragment, make sure that the value matches, otherwise its an error
244                     if (!holder.getInitParameter(pname).equals(pvalue))
245                         throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
246                     break;
247                 }
248             }
249         }
250 
251         String servlet_class = node.getString("servlet-class", false, true);
252 
253         // Handle JSP
254         String jspServletClass=null;
255 
256         //Handle the default jsp servlet instance
257         if (id != null && id.equals("jsp"))
258         {
259             jspServletClass = servlet_class;
260             try
261             {
262                 Loader.loadClass(this.getClass(), servlet_class);
263 
264                 //Ensure there is a scratch dir
265                 if (holder.getInitParameter("scratchdir") == null)
266                 {
267                     File tmp = context.getTempDirectory();
268                     File scratch = new File(tmp, "jsp");
269                     if (!scratch.exists()) scratch.mkdir();
270                     holder.setInitParameter("scratchdir", scratch.getAbsolutePath());
271                 }
272             }
273             catch (ClassNotFoundException e)
274             {
275                 LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
276                 jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
277             }
278         }
279 
280 
281         //Set the servlet-class
282         if (servlet_class != null)
283         {
284             ((WebDescriptor)descriptor).addClassName(servlet_class);
285 
286             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
287             switch (o)
288             {
289                 case NotSet:
290                 {
291                     //the class of the servlet has not previously been set, so set it
292                     holder.setClassName(servlet_class);
293                     context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
294                     break;
295                 }
296                 case WebXml:
297                 case WebDefaults:
298                 case WebOverride:
299                 {
300                     //the class of the servlet was set by a web xml file, only allow web-override/web-default to change it
301                     if (!(descriptor instanceof FragmentDescriptor))
302                     {
303                         holder.setClassName(servlet_class);
304                         context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
305                     }
306                     break;
307                 }
308                 case WebFragment:
309                 {
310                     //the class was set by another fragment, ensure this fragment's value is the same
311                     if (!servlet_class.equals(holder.getClassName()))
312                         throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
313                     break;
314                 }
315             }
316         }
317 
318         // Handle JSP file
319         String jsp_file = node.getString("jsp-file", false, true);
320         if (jsp_file != null)
321         {
322             holder.setForcedPath(jsp_file);
323             ServletHolder jsp=context.getServletHandler().getServlet("jsp");
324             if (jsp!=null)
325                 holder.setClassName(jsp.getClassName());
326         }
327 
328         // handle load-on-startup
329         XmlParser.Node startup = node.get("load-on-startup");
330         if (startup != null)
331         {
332             String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
333             int order = 0;
334             if (s.startsWith("t"))
335             {
336                 LOG.warn("Deprecated boolean load-on-startup.  Please use integer");
337                 order = 1;
338             }
339             else
340             {
341                 try
342                 {
343                     if (s != null && s.trim().length() > 0) order = Integer.parseInt(s);
344                 }
345                 catch (Exception e)
346                 {
347                     LOG.warn("Cannot parse load-on-startup " + s + ". Please use integer");
348                     LOG.ignore(e);
349                 }
350             }
351 
352             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
353             switch (o)
354             {
355                 case NotSet:
356                 {
357                     //not already set, so set it now
358                     holder.setInitOrder(order);
359                     context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
360                     break;
361                 }
362                 case WebXml:
363                 case WebDefaults:
364                 case WebOverride:
365                 {
366                     //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
367                     if (!(descriptor instanceof FragmentDescriptor))
368                     {
369                         holder.setInitOrder(order);
370                         context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
371                     }
372                     break;
373                 }
374                 case WebFragment:
375                 {
376                     //it was already set by another fragment, if we're parsing a fragment, the values must match
377                     if (order != holder.getInitOrder())
378                         throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
379                     break;
380                 }
381             }
382         }
383 
384         Iterator sRefsIter = node.iterator("security-role-ref");
385         while (sRefsIter.hasNext())
386         {
387             XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
388             String roleName = securityRef.getString("role-name", false, true);
389             String roleLink = securityRef.getString("role-link", false, true);
390             if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
391             {
392                 if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
393                 Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
394                 switch (o)
395                 {
396                     case NotSet:
397                     {
398                         //set it
399                         holder.setUserRoleLink(roleName, roleLink);
400                         context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
401                         break;
402                     }
403                     case WebXml:
404                     case WebDefaults:
405                     case WebOverride:
406                     {
407                         //only another web xml descriptor (web-default,web-override web.xml) can override an already set value
408                         if (!(descriptor instanceof FragmentDescriptor))
409                         {
410                             holder.setUserRoleLink(roleName, roleLink);
411                             context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
412                         }
413                         break;
414                     }
415                     case WebFragment:
416                     {
417                         if (!holder.getUserRoleLink(roleName).equals(roleLink))
418                             throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
419                         break;
420                     }
421                 }
422             }
423             else
424             {
425                 LOG.warn("Ignored invalid security-role-ref element: " + "servlet-name=" + holder.getName() + ", " + securityRef);
426             }
427         }
428 
429 
430         XmlParser.Node run_as = node.get("run-as");
431         if (run_as != null)
432         {
433             String roleName = run_as.getString("role-name", false, true);
434 
435             if (roleName != null)
436             {
437                 Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
438                 switch (o)
439                 {
440                     case NotSet:
441                     {
442                         //run-as not set, so set it
443                         holder.setRunAsRole(roleName);
444                         context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
445                         break;
446                     }
447                     case WebXml:
448                     case WebDefaults:
449                     case WebOverride:
450                     {
451                         //run-as was set by a web xml, only allow it to be changed if we're currently parsing another web xml(override/default)
452                         if (!(descriptor instanceof FragmentDescriptor))
453                         {
454                             holder.setRunAsRole(roleName);
455                             context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
456                         }
457                         break;
458                     }
459                     case WebFragment:
460                     {
461                         //run-as was set by another fragment, this fragment must show the same value
462                         if (!holder.getRunAsRole().equals(roleName))
463                             throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
464                         break;
465                     }
466                 }
467             }
468         }
469 
470         String async=node.getString("async-supported",false,true);
471         if (async!=null)
472         {
473             boolean val = async.length()==0||Boolean.valueOf(async);
474             Origin o =context.getMetaData().getOrigin(servlet_name+".servlet.async-supported");
475             switch (o)
476             {
477                 case NotSet:
478                 {
479                     //set it
480                     holder.setAsyncSupported(val);
481                     context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
482                     break;
483                 }
484                 case WebXml:
485                 case WebDefaults:
486                 case WebOverride:
487                 {
488                     //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)
489                     if (!(descriptor instanceof FragmentDescriptor))
490                     {
491                         holder.setAsyncSupported(val);
492                         context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
493                     }
494                     break;
495                 }
496                 case WebFragment:
497                 {
498                     //async-supported set by another fragment, this fragment's value must match
499                     if (holder.isAsyncSupported() != val)
500                         throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+servlet_name+" in "+descriptor.getResource());
501                     break;
502                 }
503             }
504         }
505 
506         String enabled = node.getString("enabled", false, true);
507         if (enabled!=null)
508         {
509             boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);
510             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.enabled");
511             switch (o)
512             {
513                 case NotSet:
514                 {
515                     //hasn't been set yet, so set it
516                     holder.setEnabled(is_enabled);
517                     context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
518                     break;
519                 }
520                 case WebXml:
521                 case WebDefaults:
522                 case WebOverride:
523                 {
524                     //was set in a web xml descriptor, only allow override from another web xml descriptor
525                     if (!(descriptor instanceof FragmentDescriptor))
526                     {
527                         holder.setEnabled(is_enabled);
528                         context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
529                     }
530                     break;
531                 }
532                 case WebFragment:
533                 {
534                     //was set by another fragment, this fragment's value must match
535                     if (holder.isEnabled() != is_enabled)
536                         throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+servlet_name+" in "+descriptor.getResource());
537                     break;
538                 }
539             }
540         }
541 
542         /*
543          * If multipart config not set, then set it and record it was by the web.xml or fragment.
544          * If it was set by web.xml then if this is a fragment, ignore the settings.
545          * If it was set by a fragment, if this is a fragment and the values are different, error!
546          */
547         XmlParser.Node multipart = node.get("multipart-config");
548         if (multipart != null)
549         {
550             String location = multipart.getString("location", false, true);
551             String maxFile = multipart.getString("max-file-size", false, true);
552             String maxRequest = multipart.getString("max-request-size", false, true);
553             String threshold = multipart.getString("file-size-threshold",false,true);
554             MultipartConfigElement element = new MultipartConfigElement(location,
555                                                                         (maxFile==null||"".equals(maxFile)?-1L:Long.parseLong(maxFile)),
556                                                                         (maxRequest==null||"".equals(maxRequest)?-1L:Long.parseLong(maxRequest)),
557                                                                         (threshold==null||"".equals(threshold)?0:Integer.parseInt(threshold)));
558 
559             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config");
560             switch (o)
561             {
562                 case NotSet:
563                 {
564                     //hasn't been set, so set it
565                     holder.getRegistration().setMultipartConfig(element);
566                     context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
567                     break;
568                 }
569                 case WebXml:
570                 case WebDefaults:
571                 case WebOverride:
572                 {
573                     //was set in a web xml, only allow changes if we're parsing another web xml (web.xml/web-default.xml/web-override.xml)
574                     if (!(descriptor instanceof FragmentDescriptor))
575                     {
576                         holder.getRegistration().setMultipartConfig(element);
577                         context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
578                     }
579                     break;
580                 }
581                 case WebFragment:
582                 {
583                     //another fragment set the value, this fragment's values must match exactly or it is an error
584                     MultipartConfigElement cfg = ((ServletHolder.Registration)holder.getRegistration()).getMultipartConfig();
585 
586                     if (cfg.getMaxFileSize() != element.getMaxFileSize())
587                         throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet "+servlet_name+" in "+descriptor.getResource());
588                     if (cfg.getMaxRequestSize() != element.getMaxRequestSize())
589                         throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet "+servlet_name+" in "+descriptor.getResource());
590                     if (cfg.getFileSizeThreshold() != element.getFileSizeThreshold())
591                         throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet "+servlet_name+" in "+descriptor.getResource());
592                     if ((cfg.getLocation() != null && (element.getLocation() == null || element.getLocation().length()==0))
593                             || (cfg.getLocation() == null && (element.getLocation()!=null || element.getLocation().length() > 0)))
594                         throw new IllegalStateException("Conflicting multipart-config location for servlet "+servlet_name+" in "+descriptor.getResource());
595                     break;
596                 }
597             }
598         }
599     }
600 
601 
602 
603     /**
604      * @param context
605      * @param descriptor
606      * @param node
607      */
608     protected void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
609     {
610         //Servlet Spec 3.0, p74
611         //servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
612         //Maintenance update 3.0a to spec:
613         //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
614         //  <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
615 
616         String servlet_name = node.getString("servlet-name", false, true);
617         Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
618 
619         switch (origin)
620         {
621             case NotSet:
622             {
623                 //no servlet mappings
624                 context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
625                 ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
626                 mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
627                 break;
628             }
629             case WebXml:
630             case WebDefaults:
631             case WebOverride:
632             {
633                 //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
634                 //otherwise just ignore it
635                 if (!(descriptor instanceof FragmentDescriptor))
636                 {
637                    addServletMapping(servlet_name, node, context, descriptor);
638                 }
639                 break;
640             }
641             case WebFragment:
642             {
643                 //mappings previously set by another web-fragment, so merge in this web-fragment's mappings
644                 addServletMapping(servlet_name, node, context, descriptor);
645                 break;
646             }
647         }
648     }
649 
650 
651     /**
652      * @param context
653      * @param descriptor
654      * @param node
655      */
656     protected void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
657     {
658         XmlParser.Node tNode = node.get("session-timeout");
659         if (tNode != null)
660         {
661             int timeout = Integer.parseInt(tNode.toString(false, true));
662             context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60);
663         }
664 
665         //Servlet Spec 3.0
666         // <tracking-mode>
667         // this is additive across web-fragments
668         Iterator iter = node.iterator("tracking-mode");
669         Set<SessionTrackingMode> modes = new HashSet<SessionTrackingMode>();
670         modes.addAll(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes());
671         while (iter.hasNext())
672         {
673             XmlParser.Node mNode = (XmlParser.Node) iter.next();
674             String trackMode = mNode.toString(false, true);
675             modes.add(SessionTrackingMode.valueOf(trackMode));
676         }
677         context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes);
678 
679 
680         //Servlet Spec 3.0
681         //<cookie-config>
682         XmlParser.Node cookieConfig = node.get("cookie-config");
683         if (cookieConfig != null)
684         {
685             //  <name>
686             String name = cookieConfig.getString("name", false, true);
687             if (name != null)
688             {
689                 Origin o = context.getMetaData().getOrigin("cookie-config.name");
690                 switch (o)
691                 {
692                     case NotSet:
693                     {
694                         //no <cookie-config><name> set yet, accept it
695                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
696                         context.getMetaData().setOrigin("cookie-config.name", descriptor);
697                         break;
698                     }
699                     case WebXml:
700                     case WebDefaults:
701                     case WebOverride:
702                     {
703                         //<cookie-config><name> set in a web xml, only allow web-default/web-override to change
704                         if (!(descriptor instanceof FragmentDescriptor))
705                         {
706                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
707                             context.getMetaData().setOrigin("cookie-config.name", descriptor);
708                         }
709                         break;
710                     }
711                     case WebFragment:
712                     {
713                         //a web-fragment set the value, all web-fragments must have the same value
714                         if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getName().equals(name))
715                             throw new IllegalStateException("Conflicting cookie-config name "+name+" in "+descriptor.getResource());
716                         break;
717                     }
718                 }
719             }
720 
721             //  <domain>
722             String domain = cookieConfig.getString("domain", false, true);
723             if (domain != null)
724             {
725                 Origin o = context.getMetaData().getOrigin("cookie-config.domain");
726                 switch (o)
727                 {
728                     case NotSet:
729                     {
730                         //no <cookie-config><domain> set yet, accept it
731                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
732                         context.getMetaData().setOrigin("cookie-config.domain", descriptor);
733                         break;
734                     }
735                     case WebXml:
736                     case WebDefaults:
737                     case WebOverride:
738                     {
739                         //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
740                         if (!(descriptor instanceof FragmentDescriptor))
741                         {
742                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
743                             context.getMetaData().setOrigin("cookie-config.domain", descriptor);
744                         }
745                         break;
746                     }
747                     case WebFragment:
748                     {
749                         //a web-fragment set the value, all web-fragments must have the same value
750                         if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getDomain().equals(domain))
751                             throw new IllegalStateException("Conflicting cookie-config domain "+domain+" in "+descriptor.getResource());
752                         break;
753                     }
754                 }
755             }
756 
757             //  <path>
758             String path = cookieConfig.getString("path", false, true);
759             if (path != null)
760             {
761                 Origin o = context.getMetaData().getOrigin("cookie-config.path");
762                 switch (o)
763                 {
764                     case NotSet:
765                     {
766                         //no <cookie-config><domain> set yet, accept it
767                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
768                         context.getMetaData().setOrigin("cookie-config.path", descriptor);
769                         break;
770                     }
771                     case WebXml:
772                     case WebDefaults:
773                     case WebOverride:
774                     {
775                         //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
776                         if (!(descriptor instanceof FragmentDescriptor))
777                         {
778                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
779                             context.getMetaData().setOrigin("cookie-config.path", descriptor);
780                         }
781                         break;
782                     }
783                     case WebFragment:
784                     {
785                         //a web-fragment set the value, all web-fragments must have the same value
786                         if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getPath().equals(path))
787                             throw new IllegalStateException("Conflicting cookie-config path "+path+" in "+descriptor.getResource());
788                         break;
789                     }
790                 }
791             }
792 
793             //  <comment>
794             String comment = cookieConfig.getString("comment", false, true);
795             if (comment != null)
796             {
797                 Origin o = context.getMetaData().getOrigin("cookie-config.comment");
798                 switch (o)
799                 {
800                     case NotSet:
801                     {
802                         //no <cookie-config><comment> set yet, accept it
803                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
804                         context.getMetaData().setOrigin("cookie-config.comment", descriptor);
805                         break;
806                     }
807                     case WebXml:
808                     case WebDefaults:
809                     case WebOverride:
810                     {
811                         //<cookie-config><comment> set in a web xml, only allow web-default/web-override to change
812                         if (!(descriptor instanceof FragmentDescriptor))
813                         {
814                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
815                             context.getMetaData().setOrigin("cookie-config.comment", descriptor);
816                         }
817                         break;
818                     }
819                     case WebFragment:
820                     {
821                         //a web-fragment set the value, all web-fragments must have the same value
822                         if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getComment().equals(comment))
823                             throw new IllegalStateException("Conflicting cookie-config comment "+comment+" in "+descriptor.getResource());
824                         break;
825                     }
826                 }
827             }
828 
829             //  <http-only>true/false
830             tNode = cookieConfig.get("http-only");
831             if (tNode != null)
832             {
833                 boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));
834                 Origin o = context.getMetaData().getOrigin("cookie-config.http-only");
835                 switch (o)
836                 {
837                     case NotSet:
838                     {
839                         //no <cookie-config><http-only> set yet, accept it
840                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
841                         context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
842                         break;
843                     }
844                     case WebXml:
845                     case WebDefaults:
846                     case WebOverride:
847                     {
848                         //<cookie-config><http-only> set in a web xml, only allow web-default/web-override to change
849                         if (!(descriptor instanceof FragmentDescriptor))
850                         {
851                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
852                             context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
853                         }
854                         break;
855                     }
856                     case WebFragment:
857                     {
858                         //a web-fragment set the value, all web-fragments must have the same value
859                         if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isHttpOnly() != httpOnly)
860                             throw new IllegalStateException("Conflicting cookie-config http-only "+httpOnly+" in "+descriptor.getResource());
861                         break;
862                     }
863                 }
864             }
865 
866             //  <secure>true/false
867             tNode = cookieConfig.get("secure");
868             if (tNode != null)
869             {
870                 boolean secure = Boolean.parseBoolean(tNode.toString(false,true));
871                 Origin o = context.getMetaData().getOrigin("cookie-config.secure");
872                 switch (o)
873                 {
874                     case NotSet:
875                     {
876                         //no <cookie-config><secure> set yet, accept it
877                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
878                         context.getMetaData().setOrigin("cookie-config.secure", descriptor);
879                         break;
880                     }
881                     case WebXml:
882                     case WebDefaults:
883                     case WebOverride:
884                     {
885                         //<cookie-config><secure> set in a web xml, only allow web-default/web-override to change
886                         if (!(descriptor instanceof FragmentDescriptor))
887                         {
888                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
889                             context.getMetaData().setOrigin("cookie-config.secure", descriptor);
890                         }
891                         break;
892                     }
893                     case WebFragment:
894                     {
895                         //a web-fragment set the value, all web-fragments must have the same value
896                         if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isSecure() != secure)
897                             throw new IllegalStateException("Conflicting cookie-config secure "+secure+" in "+descriptor.getResource());
898                         break;
899                     }
900                 }
901             }
902 
903             //  <max-age>
904             tNode = cookieConfig.get("max-age");
905             if (tNode != null)
906             {
907                 int maxAge = Integer.parseInt(tNode.toString(false,true));
908                 Origin o = context.getMetaData().getOrigin("cookie-config.max-age");
909                 switch (o)
910                 {
911                     case NotSet:
912                     {
913                         //no <cookie-config><max-age> set yet, accept it
914                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
915                         context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
916                         break;
917                     }
918                     case WebXml:
919                     case WebDefaults:
920                     case WebOverride:
921                     {
922                         //<cookie-config><max-age> set in a web xml, only allow web-default/web-override to change
923                         if (!(descriptor instanceof FragmentDescriptor))
924                         {
925                             context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
926                             context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
927                         }
928                         break;
929                     }
930                     case WebFragment:
931                     {
932                         //a web-fragment set the value, all web-fragments must have the same value
933                         if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().getMaxAge() != maxAge)
934                             throw new IllegalStateException("Conflicting cookie-config max-age "+maxAge+" in "+descriptor.getResource());
935                         break;
936                     }
937                 }
938             }
939         }
940     }
941 
942 
943 
944     /**
945      * @param context
946      * @param descriptor
947      * @param node
948      */
949     protected void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
950     {
951         String extension = node.getString("extension", false, true);
952         if (extension != null && extension.startsWith("."))
953             extension = extension.substring(1);
954         String mimeType = node.getString("mime-type", false, true);
955         if (extension != null)
956         {
957             Origin o = context.getMetaData().getOrigin("extension."+extension);
958             switch (o)
959             {
960                 case NotSet:
961                 {
962                     //no mime-type set for the extension yet
963                     context.getMimeTypes().addMimeMapping(extension, mimeType);
964                     context.getMetaData().setOrigin("extension."+extension, descriptor);
965                     break;
966                 }
967                 case WebXml:
968                 case WebDefaults:
969                 case WebOverride:
970                 {
971                     //a mime-type was set for the extension in a web xml, only allow web-default/web-override to change
972                     if (!(descriptor instanceof FragmentDescriptor))
973                     {
974                         context.getMimeTypes().addMimeMapping(extension, mimeType);
975                         context.getMetaData().setOrigin("extension."+extension, descriptor);
976                     }
977                     break;
978                 }
979                 case WebFragment:
980                 {
981                     //a web-fragment set the value, all web-fragments must have the same value
982                     if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(mimeType))
983                         throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
984                     break;
985                 }
986             }
987         }
988     }
989 
990     /**
991      * @param context
992      * @param descriptor
993      * @param node
994      */
995     protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
996     {
997         Origin o = context.getMetaData().getOrigin("welcome-file-list");
998         switch (o)
999         {
1000             case NotSet:
1001             {
1002                 context.getMetaData().setOrigin("welcome-file-list", descriptor);
1003                 addWelcomeFiles(context,node);
1004                 break;
1005             }
1006             case WebXml:
1007             {
1008                 //web.xml set the welcome-file-list, all other descriptors then just merge in
1009                 addWelcomeFiles(context,node);
1010                 break;
1011             }
1012             case WebDefaults:
1013             {
1014                 //if web-defaults set the welcome-file-list first and
1015                 //we're processing web.xml then reset the welcome-file-list
1016                 if (!(descriptor instanceof DefaultsDescriptor) && !(descriptor instanceof OverrideDescriptor) && !(descriptor instanceof FragmentDescriptor))
1017                 {
1018                     context.setWelcomeFiles(new String[0]);
1019                 }
1020                 addWelcomeFiles(context,node);
1021                 break;
1022             }
1023             case WebOverride:
1024             {
1025                 //web-override set the list, all other descriptors just merge in
1026                 addWelcomeFiles(context,node);
1027                 break;
1028             }
1029             case WebFragment:
1030             {
1031                 //A web-fragment first set the welcome-file-list. Other descriptors just add.
1032                 addWelcomeFiles(context,node);
1033                 break;
1034             }
1035         }
1036     }
1037 
1038     /**
1039      * @param context
1040      * @param descriptor
1041      * @param node
1042      */
1043     protected void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1044     {
1045         Iterator<XmlParser.Node> iter = node.iterator("locale-encoding-mapping");
1046         while (iter.hasNext())
1047         {
1048             XmlParser.Node mapping = iter.next();
1049             String locale = mapping.getString("locale", false, true);
1050             String encoding = mapping.getString("encoding", false, true);
1051 
1052             if (encoding != null)
1053             {
1054                 Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
1055                 switch (o)
1056                 {
1057                     case NotSet:
1058                     {
1059                         //no mapping for the locale yet, so set it
1060                         context.addLocaleEncoding(locale, encoding);
1061                         context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
1062                         break;
1063                     }
1064                     case WebXml:
1065                     case WebDefaults:
1066                     case WebOverride:
1067                     {
1068                         //a value was set in a web descriptor, only allow another web descriptor to change it (web-default/web-override)
1069                         if (!(descriptor instanceof FragmentDescriptor))
1070                         {
1071                             context.addLocaleEncoding(locale, encoding);
1072                             context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
1073                         }
1074                         break;
1075                     }
1076                     case WebFragment:
1077                     {
1078                         //a value was set by a web-fragment, all fragments must have the same value
1079                         if (!encoding.equals(context.getLocaleEncoding(locale)))
1080                             throw new IllegalStateException("Conflicting loacle-encoding mapping for locale "+locale+" in "+descriptor.getResource());
1081                         break;
1082                     }
1083                 }
1084             }
1085         }
1086     }
1087 
1088     /**
1089      * @param context
1090      * @param descriptor
1091      * @param node
1092      */
1093     protected void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1094     {
1095         String error = node.getString("error-code", false, true);
1096         int code=0;
1097         if (error == null || error.length() == 0)
1098         {
1099             error = node.getString("exception-type", false, true);
1100             if (error == null || error.length() == 0)
1101                 error = ErrorPageErrorHandler.GLOBAL_ERROR_PAGE;
1102         }
1103         else
1104             code=Integer.valueOf(error);
1105 
1106         String location = node.getString("location", false, true);
1107         ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
1108 
1109 
1110         Origin o = context.getMetaData().getOrigin("error."+error);
1111         
1112         switch (o)
1113         {
1114             case NotSet:
1115             {
1116                 //no error page setup for this code or exception yet
1117                 if (code>0)
1118                     handler.addErrorPage(code,location);
1119                 else
1120                     handler.addErrorPage(error,location);
1121                 context.getMetaData().setOrigin("error."+error, descriptor);
1122                 break;
1123             }
1124             case WebXml:
1125             case WebDefaults:
1126             case WebOverride:
1127             {
1128                 //an error page setup was set in web.xml, only allow other web xml descriptors to override it
1129                 if (!(descriptor instanceof FragmentDescriptor))
1130                 {
1131                     if (descriptor instanceof OverrideDescriptor || descriptor instanceof DefaultsDescriptor)
1132                     {
1133                         if (code>0)
1134                             handler.addErrorPage(code,location);
1135                         else
1136                             handler.addErrorPage(error,location);
1137                         context.getMetaData().setOrigin("error."+error, descriptor);
1138                     }
1139                     else
1140                         throw new IllegalStateException("Duplicate global error-page "+location);
1141                 }
1142                 break;
1143             }
1144             case WebFragment:
1145             {
1146                 //another web fragment set the same error code or exception, if its different its an error
1147                 if (!handler.getErrorPages().get(error).equals(location))
1148                     throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
1149                 break;
1150             }
1151         }
1152 
1153     }
1154 
1155     /**
1156      * @param context
1157      * @param node
1158      */
1159     protected void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
1160     {
1161         Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
1162         while (iter.hasNext())
1163         {
1164             XmlParser.Node indexNode = (XmlParser.Node) iter.next();
1165             String welcome = indexNode.toString(false, true);
1166 
1167             //Servlet Spec 3.0 p. 74 welcome files are additive
1168             context.setWelcomeFiles((String[])ArrayUtil.addToArray(context.getWelcomeFiles(),welcome,String.class));
1169         }
1170     }
1171 
1172 
1173     /**
1174      * @param servletName
1175      * @param node
1176      * @param context
1177      */
1178     protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
1179     {
1180         ServletMapping mapping = new ServletMapping();
1181         mapping.setServletName(servletName);
1182 
1183         List<String> paths = new ArrayList<String>();
1184         Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
1185         while (iter.hasNext())
1186         {
1187             String p = iter.next().toString(false, true);
1188             p = normalizePattern(p);
1189             paths.add(p);
1190             context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
1191         }
1192         mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
1193         context.getServletHandler().addServletMapping(mapping);
1194         return mapping;
1195     }
1196 
1197     /**
1198      * @param filterName
1199      * @param node
1200      * @param context
1201      */
1202     protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
1203     {
1204         FilterMapping mapping = new FilterMapping();
1205         mapping.setFilterName(filterName);
1206 
1207         List<String> paths = new ArrayList<String>();
1208         Iterator<XmlParser.Node>  iter = node.iterator("url-pattern");
1209         while (iter.hasNext())
1210         {
1211             String p = iter.next().toString(false, true);
1212             p = normalizePattern(p);
1213             paths.add(p);
1214             context.getMetaData().setOrigin(filterName+".filter.mapping."+p, descriptor);
1215         }
1216         mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
1217 
1218         List<String> names = new ArrayList<String>();
1219         iter = node.iterator("servlet-name");
1220         while (iter.hasNext())
1221         {
1222             String n = ((XmlParser.Node) iter.next()).toString(false, true);
1223             names.add(n);
1224         }
1225         mapping.setServletNames((String[]) names.toArray(new String[names.size()]));
1226 
1227 
1228         List<DispatcherType> dispatches = new ArrayList<DispatcherType>();
1229         iter=node.iterator("dispatcher");
1230         while(iter.hasNext())
1231         {
1232             String d=((XmlParser.Node)iter.next()).toString(false,true);
1233             dispatches.add(FilterMapping.dispatch(d));
1234         }
1235 
1236         if (dispatches.size()>0)
1237             mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
1238 
1239         context.getServletHandler().addFilterMapping(mapping);
1240     }
1241 
1242 
1243     /**
1244      * @param context
1245      * @param descriptor
1246      * @param node
1247      */
1248     protected void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1249     {
1250         //Additive across web.xml and web-fragment.xml
1251         String uri = node.getString("taglib-uri", false, true);
1252         String location = node.getString("taglib-location", false, true);
1253 
1254         context.setResourceAlias(uri, location);
1255 
1256         JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
1257         if (config == null)
1258         {
1259             config = new JspConfig();
1260             context.getServletContext().setJspConfigDescriptor(config);
1261         }
1262 
1263         TagLib tl = new TagLib();
1264         tl.setTaglibLocation(location);
1265         tl.setTaglibURI(uri);
1266         config.addTaglibDescriptor(tl);
1267     }
1268 
1269     /**
1270      * @param context
1271      * @param descriptor
1272      * @param node
1273      */
1274     protected void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1275     {
1276         //Additive across web.xml and web-fragment.xml
1277         JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
1278         if (config == null)
1279         {
1280            config = new JspConfig();
1281            context.getServletContext().setJspConfigDescriptor(config);
1282         }
1283 
1284 
1285         for (int i = 0; i < node.size(); i++)
1286         {
1287             Object o = node.get(i);
1288             if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag()))
1289                 visitTagLib(context,descriptor, (XmlParser.Node) o);
1290         }
1291 
1292         // Map URLs from jsp property groups to JSP servlet.
1293         // this is more JSP stupidness creeping into the servlet spec
1294         Iterator<XmlParser.Node> iter = node.iterator("jsp-property-group");
1295         List<String> paths = new ArrayList<String>();
1296         while (iter.hasNext())
1297         {
1298             JspPropertyGroup jpg = new JspPropertyGroup();
1299             config.addJspPropertyGroup(jpg);
1300             XmlParser.Node group = iter.next();
1301 
1302             //url-patterns
1303             Iterator<XmlParser.Node> iter2 = group.iterator("url-pattern");
1304             while (iter2.hasNext())
1305             {
1306                 String url = iter2.next().toString(false, true);
1307                 url = normalizePattern(url);
1308                 paths.add( url);
1309                 jpg.addUrlPattern(url);
1310             }
1311 
1312             jpg.setElIgnored(group.getString("el-ignored", false, true));
1313             jpg.setPageEncoding(group.getString("page-encoding", false, true));
1314             jpg.setScriptingInvalid(group.getString("scripting-invalid", false, true));
1315             jpg.setIsXml(group.getString("is-xml", false, true));
1316             jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true));
1317             jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true));
1318             jpg.setDefaultContentType(group.getString("default-content-type", false, true));
1319             jpg.setBuffer(group.getString("buffer", false, true));
1320             jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
1321 
1322             //preludes
1323             Iterator<XmlParser.Node> preludes = group.iterator("include-prelude");
1324             while (preludes.hasNext())
1325             {
1326                 String prelude = preludes.next().toString(false, true);
1327                 jpg.addIncludePrelude(prelude);
1328             }
1329             //codas
1330             Iterator<XmlParser.Node> codas = group.iterator("include-coda");
1331             while (codas.hasNext())
1332             {
1333                 String coda = codas.next().toString(false, true);
1334                 jpg.addIncludeCoda(coda);
1335             }
1336 
1337             if (LOG.isDebugEnabled()) LOG.debug(config.toString());
1338         }
1339 
1340         if (paths.size() > 0)
1341         {
1342             ServletHandler handler = context.getServletHandler();
1343             ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
1344             if (jsp_pg_servlet==null)
1345             {
1346                 jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
1347                 handler.addServlet(jsp_pg_servlet);
1348             }
1349 
1350             ServletMapping mapping = new ServletMapping();
1351             mapping.setServletName(JspPropertyGroupServlet.NAME);
1352             mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
1353             context.getServletHandler().addServletMapping(mapping);
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 }