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