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