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