View Javadoc

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