View Javadoc

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