View Javadoc

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