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