View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.EnumSet;
27  import java.util.EventListener;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  
33  import javax.servlet.ServletException;
34  
35  import org.eclipse.jetty.security.ConstraintAware;
36  import org.eclipse.jetty.security.ConstraintMapping;
37  import org.eclipse.jetty.security.authentication.FormAuthenticator;
38  import org.eclipse.jetty.server.DispatcherType;
39  import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
40  import org.eclipse.jetty.servlet.FilterHolder;
41  import org.eclipse.jetty.servlet.FilterMapping;
42  import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
43  import org.eclipse.jetty.servlet.ServletContextHandler;
44  import org.eclipse.jetty.servlet.ServletHandler;
45  import org.eclipse.jetty.servlet.ServletHolder;
46  import org.eclipse.jetty.servlet.ServletMapping;
47  import org.eclipse.jetty.util.LazyList;
48  import org.eclipse.jetty.util.Loader;
49  import org.eclipse.jetty.util.log.Log;
50  import org.eclipse.jetty.util.log.Logger;
51  import org.eclipse.jetty.util.resource.Resource;
52  import org.eclipse.jetty.util.security.Constraint;
53  import org.eclipse.jetty.xml.XmlParser;
54  
55  /**
56   * StandardDescriptorProcessor
57   *
58   * Process a web.xml, web-defaults.xml, web-overrides.xml, web-fragment.xml.
59   */
60  public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
61  {
62      private static final Logger LOG = Log.getLogger(StandardDescriptorProcessor.class);
63  
64      public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
65      
66      
67      
68      public StandardDescriptorProcessor ()
69      {
70   
71          try
72          {
73              registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature));
74              registerVisitor("display-name", this.getClass().getDeclaredMethod("visitDisplayName", __signature));
75              registerVisitor("servlet", this.getClass().getDeclaredMethod("visitServlet",  __signature));
76              registerVisitor("servlet-mapping", this.getClass().getDeclaredMethod("visitServletMapping",  __signature));
77              registerVisitor("session-config", this.getClass().getDeclaredMethod("visitSessionConfig",  __signature));
78              registerVisitor("mime-mapping", this.getClass().getDeclaredMethod("visitMimeMapping",  __signature)); 
79              registerVisitor("welcome-file-list", this.getClass().getDeclaredMethod("visitWelcomeFileList",  __signature));
80              registerVisitor("locale-encoding-mapping-list", this.getClass().getDeclaredMethod("visitLocaleEncodingList",  __signature));
81              registerVisitor("error-page", this.getClass().getDeclaredMethod("visitErrorPage",  __signature));
82              registerVisitor("taglib", this.getClass().getDeclaredMethod("visitTagLib",  __signature));
83              registerVisitor("jsp-config", this.getClass().getDeclaredMethod("visitJspConfig",  __signature));
84              registerVisitor("security-constraint", this.getClass().getDeclaredMethod("visitSecurityConstraint",  __signature));
85              registerVisitor("login-config", this.getClass().getDeclaredMethod("visitLoginConfig",  __signature));
86              registerVisitor("security-role", this.getClass().getDeclaredMethod("visitSecurityRole",  __signature));
87              registerVisitor("filter", this.getClass().getDeclaredMethod("visitFilter",  __signature));
88              registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping",  __signature));
89              registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener",  __signature));
90              registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable",  __signature));
91          }
92          catch (Exception e)
93          {
94              throw new IllegalStateException(e);
95          }
96      }
97  
98      
99      
100     /**
101      * {@inheritDoc}
102      */
103     public void start(WebAppContext context, Descriptor descriptor)
104     { 
105     }
106     
107     
108     
109     /** 
110      * {@inheritDoc}
111      */
112     public void end(WebAppContext context, Descriptor descriptor)
113     {
114     }
115     
116     /**
117      * @param context
118      * @param descriptor
119      * @param node
120      */
121     public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
122     {
123         String name = node.getString("param-name", false, true);
124         String value = node.getString("param-value", false, true);
125         Origin o = context.getMetaData().getOrigin("context-param."+name);
126         switch (o)
127         {
128             case NotSet:
129             {
130                 //just set it
131                 context.getInitParams().put(name, value);
132                 context.getMetaData().setOrigin("context-param."+name, descriptor);
133                 break;
134             }
135             case WebXml:
136             case WebDefaults:
137             case WebOverride:
138             {
139                 //previously set by a web xml, allow other web xml files to override
140                 if (!(descriptor instanceof FragmentDescriptor))
141                 {
142                     context.getInitParams().put(name, value);
143                     context.getMetaData().setOrigin("context-param."+name, descriptor); 
144                 }
145                 break;
146             }
147             case WebFragment:
148             {
149                 //previously set by a web-fragment, this fragment's value must be the same
150                 if (descriptor instanceof FragmentDescriptor)
151                 {
152                     if (!((String)context.getInitParams().get(name)).equals(value))
153                         throw new IllegalStateException("Conflicting context-param "+name+"="+value+" in "+descriptor.getResource());
154                 }
155                 break;
156             }
157         }
158         if (LOG.isDebugEnabled()) 
159             LOG.debug("ContextParam: " + name + "=" + value);
160 
161     }
162     
163 
164     /* ------------------------------------------------------------ */
165     /**
166      * @param context
167      * @param descriptor
168      * @param node
169      */
170     protected void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
171     {
172         //Servlet Spec 3.0 p. 74 Ignore from web-fragments
173         if (!(descriptor instanceof FragmentDescriptor))
174         {
175             context.setDisplayName(node.toString(false, true));
176             context.getMetaData().setOrigin("display-name", descriptor);
177         }
178     }
179     
180     
181     /**
182      * @param context
183      * @param descriptor
184      * @param node
185      */
186     protected void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
187     {
188         String id = node.getAttribute("id");
189 
190         // initialize holder
191         String servlet_name = node.getString("servlet-name", false, true);
192         ServletHolder holder = context.getServletHandler().getServlet(servlet_name);
193           
194         /*
195          * If servlet of that name does not already exist, create it.
196          */
197         if (holder == null)
198         {
199             holder = context.getServletHandler().newServletHolder();
200             holder.setName(servlet_name);
201             context.getServletHandler().addServlet(holder);
202         }
203 
204         // init params  
205         Iterator<?> iParamsIter = node.iterator("init-param");
206         while (iParamsIter.hasNext())
207         {
208             XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
209             String pname = paramNode.getString("param-name", false, true);
210             String pvalue = paramNode.getString("param-value", false, true);
211             
212             Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
213             
214             switch (origin)
215             {
216                 case NotSet:
217                 {
218                     //init-param not already set, so set it
219                     
220                     holder.setInitParameter(pname, pvalue); 
221                     context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
222                     break;
223                 }
224                 case WebXml:
225                 case WebDefaults:
226                 case WebOverride:
227                 {
228                     //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
229                     //otherwise just ignore it
230                     if (!(descriptor instanceof FragmentDescriptor))
231                     {
232                         holder.setInitParameter(pname, pvalue); 
233                         context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
234                     }
235                     break;
236                 }
237                 case WebFragment:
238                 {
239                     //previously set by a web-fragment, make sure that the value matches, otherwise its an error
240                     if (!holder.getInitParameter(pname).equals(pvalue))
241                         throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
242                     break;
243                 }
244             }  
245         }
246 
247         String servlet_class = node.getString("servlet-class", false, true);
248 
249         // Handle JSP
250         String jspServletClass=null;;
251 
252         //Handle the default jsp servlet instance
253         if (id != null && id.equals("jsp"))
254         {
255             jspServletClass = servlet_class;
256             try
257             {
258                 Loader.loadClass(this.getClass(), servlet_class);
259                 
260                 //Ensure there is a scratch dir
261                 if (holder.getInitParameter("scratchdir") == null)
262                 {
263                     File tmp = context.getTempDirectory();
264                     File scratch = new File(tmp, "jsp");
265                     if (!scratch.exists()) scratch.mkdir();
266                     holder.setInitParameter("scratchdir", scratch.getAbsolutePath());
267                 }
268             }
269             catch (ClassNotFoundException e)
270             {
271                 LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
272                 jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
273             }
274         }
275         
276        
277         //Set the servlet-class
278         if (servlet_class != null) 
279         {
280             ((WebDescriptor)descriptor).addClassName(servlet_class);
281             
282             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
283             switch (o)
284             {
285                 case NotSet:
286                 {
287                     //the class of the servlet has not previously been set, so set it
288                     holder.setClassName(servlet_class);
289                     context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
290                     break;
291                 }
292                 case WebXml:
293                 case WebDefaults:
294                 case WebOverride:
295                 {
296                     //the class of the servlet was set by a web xml file, only allow web-override/web-default to change it
297                     if (!(descriptor instanceof FragmentDescriptor))
298                     {
299                         holder.setClassName(servlet_class);
300                         context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
301                     }
302                     break;
303                 }
304                 case WebFragment:
305                 {
306                     //the class was set by another fragment, ensure this fragment's value is the same
307                     if (!servlet_class.equals(holder.getClassName()))
308                         throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
309                     break;
310                 }
311             }          
312         }
313 
314         // Handle JSP file
315         String jsp_file = node.getString("jsp-file", false, true);
316         if (jsp_file != null)
317         {
318             holder.setForcedPath(jsp_file);
319             holder.setClassName(jspServletClass); //only use our default instance
320         }
321 
322         // handle load-on-startup 
323         XmlParser.Node startup = node.get("load-on-startup");
324         if (startup != null)
325         {
326             String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
327             int order = 0;
328             if (s.startsWith("t"))
329             {
330                 LOG.warn("Deprecated boolean load-on-startup.  Please use integer");
331                 order = 1; 
332             }
333             else
334             {
335                 try
336                 {
337                     if (s != null && s.trim().length() > 0) order = Integer.parseInt(s);
338                 }
339                 catch (Exception e)
340                 {
341                     LOG.warn("Cannot parse load-on-startup " + s + ". Please use integer");
342                     LOG.ignore(e);
343                 }
344             }
345 
346             Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
347             switch (o)
348             {
349                 case NotSet:
350                 {
351                     //not already set, so set it now
352                     holder.setInitOrder(order);
353                     context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
354                     break;
355                 }
356                 case WebXml:
357                 case WebDefaults:
358                 case WebOverride:
359                 {
360                     //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
361                     if (!(descriptor instanceof FragmentDescriptor))
362                     {
363                         holder.setInitOrder(order);
364                         context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
365                     }
366                     break;
367                 }
368                 case WebFragment:
369                 {
370                     //it was already set by another fragment, if we're parsing a fragment, the values must match
371                     if (order != holder.getInitOrder())
372                         throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
373                     break;
374                 }
375             } 
376         }
377 
378         Iterator sRefsIter = node.iterator("security-role-ref");
379         while (sRefsIter.hasNext())
380         {
381             XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
382             String roleName = securityRef.getString("role-name", false, true);
383             String roleLink = securityRef.getString("role-link", false, true);
384             if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
385             {
386                 if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
387                 Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
388                 switch (o)
389                 {
390                     case NotSet:
391                     {
392                         //set it
393                         holder.setUserRoleLink(roleName, roleLink);
394                         context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
395                         break;
396                     }
397                     case WebXml:
398                     case WebDefaults:
399                     case WebOverride:
400                     {
401                         //only another web xml descriptor (web-default,web-override web.xml) can override an already set value
402                         if (!(descriptor instanceof FragmentDescriptor))
403                         {
404                             holder.setUserRoleLink(roleName, roleLink);
405                             context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
406                         }
407                         break;
408                     }
409                     case WebFragment:
410                     {
411                         if (!holder.getUserRoleLink(roleName).equals(roleLink))
412                             throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
413                         break;
414                     }
415                 }
416             }
417             else
418             {
419                 LOG.warn("Ignored invalid security-role-ref element: " + "servlet-name=" + holder.getName() + ", " + securityRef);
420             }
421         }
422 
423         
424         XmlParser.Node run_as = node.get("run-as");
425         if (run_as != null)
426         { 
427             String roleName = run_as.getString("role-name", false, true);
428 
429             if (roleName != null)
430             {
431                 Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
432                 switch (o)
433                 {
434                     case NotSet:
435                     {
436                         //run-as not set, so set it
437                         holder.setRunAsRole(roleName);
438                         context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
439                         break;
440                     }
441                     case WebXml:
442                     case WebDefaults:
443                     case WebOverride:
444                     {
445                         //run-as was set by a web xml, only allow it to be changed if we're currently parsing another web xml(override/default)
446                         if (!(descriptor instanceof FragmentDescriptor))
447                         {
448                             holder.setRunAsRole(roleName);
449                             context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
450                         }
451                         break;
452                     }
453                     case WebFragment:
454                     {
455                         //run-as was set by another fragment, this fragment must show the same value
456                         if (!holder.getRunAsRole().equals(roleName))
457                             throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
458                         break;
459                     }    
460                 }
461             }
462         }
463     }
464     
465     
466 
467     /**
468      * @param context
469      * @param descriptor
470      * @param node
471      */
472     protected void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
473     {
474         //Servlet Spec 3.0, p74
475         //servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
476         //Maintenance update 3.0a to spec:
477         //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
478         //  <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
479 
480         String servlet_name = node.getString("servlet-name", false, true); 
481         Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
482         
483         switch (origin)
484         {
485             case NotSet:
486             {
487                 //no servlet mappings
488                 context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
489                 addServletMapping(servlet_name, node, context);
490                 break;
491             }
492             case WebXml:
493             case WebDefaults:
494             case WebOverride:
495             {
496                 //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
497                 //otherwise just ignore it
498                 if (!(descriptor instanceof FragmentDescriptor))
499                 {
500                    addServletMapping(servlet_name, node, context);
501                 }
502                 break;
503             }
504             case WebFragment:
505             {
506                 //mappings previously set by another web-fragment, so merge in this web-fragment's mappings
507                 addServletMapping(servlet_name, node, context);
508                 break;
509             }
510         }        
511     }
512     
513     
514     /**
515      * @param context
516      * @param descriptor
517      * @param node
518      */
519     protected void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
520     {
521         XmlParser.Node tNode = node.get("session-timeout");
522         if (tNode != null)
523         {
524             int timeout = Integer.parseInt(tNode.toString(false, true));
525             context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60);
526         }
527     }
528     
529     
530     
531     /**
532      * @param context
533      * @param descriptor
534      * @param node
535      */
536     protected void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
537     {
538         String extension = node.getString("extension", false, true);
539         if (extension != null && extension.startsWith(".")) 
540             extension = extension.substring(1);
541         String mimeType = node.getString("mime-type", false, true);
542         if (extension != null)
543         {
544             Origin o = context.getMetaData().getOrigin("extension."+extension);
545             switch (o)
546             {
547                 case NotSet:
548                 {
549                     //no mime-type set for the extension yet
550                     context.getMimeTypes().addMimeMapping(extension, mimeType);
551                     context.getMetaData().setOrigin("extension."+extension, descriptor);
552                     break;
553                 }
554                 case WebXml:
555                 case WebDefaults:
556                 case WebOverride:
557                 {
558                     //a mime-type was set for the extension in a web xml, only allow web-default/web-override to change
559                     if (!(descriptor instanceof FragmentDescriptor))
560                     {
561                         context.getMimeTypes().addMimeMapping(extension, mimeType);
562                         context.getMetaData().setOrigin("extension."+extension, descriptor);
563                     }
564                     break;
565                 }
566                 case WebFragment:
567                 {
568                     //a web-fragment set the value, all web-fragments must have the same value
569                     if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(context.getMimeTypes().CACHE.lookup(mimeType)))
570                         throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
571                     break;
572                 }
573             }
574         }
575     }
576     
577     /**
578      * @param context
579      * @param descriptor
580      * @param node
581      */
582     protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
583     {
584         Origin o = context.getMetaData().getOrigin("welcome-file-list");
585         switch (o)
586         {
587             case NotSet:
588             {
589                 context.getMetaData().setOrigin("welcome-file-list", descriptor);
590                 addWelcomeFiles(context,node);
591                 break;
592             }
593             case WebXml:
594             {
595                 //web.xml set the welcome-file-list, all other descriptors then just merge in
596                 addWelcomeFiles(context,node);
597                 break;
598             }
599             case WebDefaults:
600             {
601                 //if web-defaults set the welcome-file-list first and
602                 //we're processing web.xml then reset the welcome-file-list
603                 if (!(descriptor instanceof DefaultsDescriptor) && !(descriptor instanceof OverrideDescriptor) && !(descriptor instanceof FragmentDescriptor))
604                 {
605                     context.setWelcomeFiles(new String[0]);
606                 }
607                 addWelcomeFiles(context,node);
608                 break;
609             }
610             case WebOverride:
611             {
612                 //web-override set the list, all other descriptors just merge in
613                 addWelcomeFiles(context,node);
614                 break;
615             }
616             case WebFragment:
617             {
618                 //A web-fragment first set the welcome-file-list. Other descriptors just add. 
619                 addWelcomeFiles(context,node);
620                 break;
621             }
622         }
623     }
624     
625     /**
626      * @param context
627      * @param descriptor
628      * @param node
629      */
630     protected void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
631     {
632         Iterator<XmlParser.Node> iter = node.iterator("locale-encoding-mapping");
633         while (iter.hasNext())
634         {
635             XmlParser.Node mapping = iter.next();
636             String locale = mapping.getString("locale", false, true);
637             String encoding = mapping.getString("encoding", false, true);
638             
639             if (encoding != null)
640             {
641                 Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
642                 switch (o)
643                 {
644                     case NotSet:
645                     {
646                         //no mapping for the locale yet, so set it
647                         context.addLocaleEncoding(locale, encoding);
648                         context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
649                         break;
650                     }
651                     case WebXml:
652                     case WebDefaults:
653                     case WebOverride:
654                     {
655                         //a value was set in a web descriptor, only allow another web descriptor to change it (web-default/web-override)
656                         if (!(descriptor instanceof FragmentDescriptor))
657                         {
658                             context.addLocaleEncoding(locale, encoding);
659                             context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
660                         }
661                         break;
662                     }
663                     case WebFragment:
664                     {
665                         //a value was set by a web-fragment, all fragments must have the same value
666                         if (!encoding.equals(context.getLocaleEncoding(locale)))
667                             throw new IllegalStateException("Conflicting locale-encoding mapping for locale "+locale+" in "+descriptor.getResource());
668                         break;                    
669                     }
670                 }
671             }
672         }
673     }
674 
675     /**
676      * @param context
677      * @param descriptor
678      * @param node
679      */
680     protected void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
681     {
682         String error = node.getString("error-code", false, true);
683         int code=0;
684         if (error == null || error.length() == 0) 
685         {
686             error = node.getString("exception-type", false, true);
687             if (error == null || error.length() == 0)
688                 error = ErrorPageErrorHandler.GLOBAL_ERROR_PAGE;
689         }
690         else
691             code=Integer.valueOf(error);
692         
693         String location = node.getString("location", false, true);
694         ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
695         Origin o = context.getMetaData().getOrigin("error."+error);
696         
697         switch (o)
698         {
699             case NotSet:
700             {
701                 //no error page setup for this code or exception yet
702                 if (code>0)
703                     handler.addErrorPage(code,location);
704                 else
705                     handler.addErrorPage(error,location);
706                 context.getMetaData().setOrigin("error."+error, descriptor);
707                 break;
708             }
709             case WebXml:
710             case WebDefaults:
711             case WebOverride:
712             {
713                 //an error page setup was set in web.xml, only allow other web xml descriptors to override it
714                 if (!(descriptor instanceof FragmentDescriptor))
715                 {
716                     if (descriptor instanceof OverrideDescriptor || descriptor instanceof DefaultsDescriptor)
717                     {
718                         if (code>0)
719                             handler.addErrorPage(code,location);
720                         else
721                             handler.addErrorPage(error,location);
722                         context.getMetaData().setOrigin("error."+error, descriptor);
723                     }
724                     else
725                         throw new IllegalStateException("Duplicate global error-page "+location);
726                 }
727                 break;
728             }
729             case WebFragment:
730             {
731                 //another web fragment set the same error code or exception, if its different its an error
732                 if (!handler.getErrorPages().get(error).equals(location))
733                     throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
734                 break;
735             }
736         }
737        
738     }
739     
740     /**
741      * @param context
742      * @param node
743      */
744     protected void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
745     {
746         Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
747         while (iter.hasNext())
748         {
749             XmlParser.Node indexNode = (XmlParser.Node) iter.next();
750             String welcome = indexNode.toString(false, true);
751             
752             //Servlet Spec 3.0 p. 74 welcome files are additive
753             if (welcome != null && welcome.trim().length() > 0)
754                 context.setWelcomeFiles((String[])LazyList.addToArray(context.getWelcomeFiles(),welcome,String.class));
755         }
756     }
757     
758     
759     /**
760      * @param servletName
761      * @param node
762      * @param context
763      */
764     protected void addServletMapping (String servletName, XmlParser.Node node, WebAppContext context)
765     {
766         ServletMapping mapping = new ServletMapping();
767         mapping.setServletName(servletName);
768         
769         List<String> paths = new ArrayList<String>();
770         Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
771         while (iter.hasNext())
772         {
773             String p = iter.next().toString(false, true);
774             p = normalizePattern(p);
775             paths.add(p);
776         }
777         mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
778         context.getServletHandler().addServletMapping(mapping);
779     }
780     
781     /**
782      * @param filterName
783      * @param node
784      * @param context
785      */
786     protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context)
787     {
788         FilterMapping mapping = new FilterMapping();
789         mapping.setFilterName(filterName);
790 
791         List<String> paths = new ArrayList<String>();
792         Iterator<XmlParser.Node>  iter = node.iterator("url-pattern");
793         while (iter.hasNext())
794         {
795             String p = iter.next().toString(false, true);
796             p = normalizePattern(p);
797             paths.add(p);
798         }
799         mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
800 
801         List<String> names = new ArrayList<String>();
802         iter = node.iterator("servlet-name");
803         while (iter.hasNext())
804         {
805             String n = ((XmlParser.Node) iter.next()).toString(false, true);
806             names.add(n);
807         }
808         mapping.setServletNames((String[]) names.toArray(new String[names.size()]));
809 
810         
811         List<DispatcherType> dispatches = new ArrayList<DispatcherType>();
812         iter=node.iterator("dispatcher");
813         while(iter.hasNext())
814         {
815             String d=((XmlParser.Node)iter.next()).toString(false,true);
816             dispatches.add(FilterMapping.dispatch(d));
817         }
818         
819         if (dispatches.size()>0)
820             mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
821 
822         context.getServletHandler().addFilterMapping(mapping);
823     }
824     
825     
826     /**
827      * @param context
828      * @param descriptor
829      * @param node
830      */
831     protected void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
832     {
833         //Additive across web.xml and web-fragment.xml
834         String uri = node.getString("taglib-uri", false, true);
835         String location = node.getString("taglib-location", false, true);
836 
837         context.setResourceAlias(uri, location);
838     }
839     
840     /**
841      * @param context
842      * @param descriptor
843      * @param node
844      */
845     protected void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
846     {  
847         for (int i = 0; i < node.size(); i++)
848         {
849             Object o = node.get(i);
850             if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag())) 
851                 visitTagLib(context,descriptor, (XmlParser.Node) o);
852         }
853 
854         // Map URLs from jsp property groups to JSP servlet.
855         // this is more JSP stupidness creaping into the servlet spec
856         Iterator<XmlParser.Node> iter = node.iterator("jsp-property-group");
857         List<String> paths = new ArrayList<String>();
858         while (iter.hasNext())
859         {
860             XmlParser.Node group = iter.next();
861             Iterator<XmlParser.Node> iter2 = group.iterator("url-pattern");
862             while (iter2.hasNext())
863             {
864                 String url = iter2.next().toString(false, true);
865                 url = normalizePattern(url);
866                 paths.add( url);
867             }
868         }
869 
870         if (paths.size() > 0)
871         {
872             ServletHandler handler = context.getServletHandler();
873             ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
874             if (jsp_pg_servlet==null)
875             {
876                 jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
877                 handler.addServlet(jsp_pg_servlet);
878             }
879 
880             ServletMapping mapping = new ServletMapping();
881             mapping.setServletName(JspPropertyGroupServlet.NAME);
882             mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
883             context.getServletHandler().addServletMapping(mapping);
884         }
885     }
886     
887     /**
888      * @param context
889      * @param descriptor
890      * @param node
891      */
892     protected void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
893     {
894         Constraint scBase = new Constraint();
895 
896         //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive 
897         //across fragments
898         try
899         {
900             XmlParser.Node auths = node.get("auth-constraint");
901 
902             if (auths != null)
903             {
904                 scBase.setAuthenticate(true);
905                 // auth-constraint
906                 Iterator<XmlParser.Node> iter = auths.iterator("role-name");
907                 List<String> roles = new ArrayList<String>();
908                 while (iter.hasNext())
909                 {
910                     String role = iter.next().toString(false, true);
911                     roles.add(role);
912                 }
913                 scBase.setRoles(roles.toArray(new String[roles.size()]));
914             }
915 
916             XmlParser.Node data = node.get("user-data-constraint");
917             if (data != null)
918             {
919                 data = data.get("transport-guarantee");
920                 String guarantee = data.toString(false, true).toUpperCase(Locale.ENGLISH);
921                 if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee))
922                     scBase.setDataConstraint(Constraint.DC_NONE);
923                 else if ("INTEGRAL".equals(guarantee))
924                     scBase.setDataConstraint(Constraint.DC_INTEGRAL);
925                 else if ("CONFIDENTIAL".equals(guarantee))
926                     scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
927                 else
928                 {
929                     LOG.warn("Unknown user-data-constraint:" + guarantee);
930                     scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
931                 }
932             }
933             Iterator<XmlParser.Node> iter = node.iterator("web-resource-collection");
934             while (iter.hasNext())
935             {
936                 XmlParser.Node collection =  iter.next();
937                 String name = collection.getString("web-resource-name", false, true);
938                 Constraint sc = (Constraint) scBase.clone();
939                 sc.setName(name);
940 
941                 Iterator<XmlParser.Node> iter2 = collection.iterator("url-pattern");
942                 while (iter2.hasNext())
943                 {
944                     String url = iter2.next().toString(false, true);
945                     url = normalizePattern(url);
946 
947                     Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
948                     if (iter3.hasNext())
949                     {
950                         while (iter3.hasNext())
951                         {
952                             String method = ((XmlParser.Node) iter3.next()).toString(false, true);
953                             ConstraintMapping mapping = new ConstraintMapping();
954                             mapping.setMethod(method);
955                             mapping.setPathSpec(url);
956                             mapping.setConstraint(sc);
957                                                         
958                             ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
959                         }
960                     }
961                     else
962                     {
963                         ConstraintMapping mapping = new ConstraintMapping();
964                         mapping.setPathSpec(url);
965                         mapping.setConstraint(sc);
966                         ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
967                     }
968                 }
969             }
970         }
971         catch (CloneNotSupportedException e)
972         {
973             LOG.warn(e);
974         }
975     }
976     
977     /**
978      * @param context
979      * @param descriptor
980      * @param node
981      * @throws Exception
982      */
983     protected void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
984     {
985         //ServletSpec 3.0 p74 says elements present 0/1 time if specified in web.xml take
986         //precendece over any web-fragment. If not specified in web.xml, then if specified
987         //in a web-fragment must be the same across all web-fragments.
988         XmlParser.Node method = node.get("auth-method");
989         if (method != null)
990         {
991             //handle auth-method merge
992             Origin o = context.getMetaData().getOrigin("auth-method");
993             switch (o)
994             {
995                 case NotSet:
996                 {
997                     //not already set, so set it now
998                     context.getSecurityHandler().setAuthMethod(method.toString(false, true));
999                     context.getMetaData().setOrigin("auth-method", descriptor);
1000                     break;
1001                 }
1002                 case WebXml:
1003                 case WebDefaults:
1004                 case WebOverride:
1005                 {
1006                     //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
1007                     if (!(descriptor instanceof FragmentDescriptor))
1008                     {
1009                         context.getSecurityHandler().setAuthMethod(method.toString(false, true));
1010                         context.getMetaData().setOrigin("auth-method", descriptor);
1011                     }
1012                     break;
1013                 }
1014                 case WebFragment:
1015                 {
1016                     //it was already set by another fragment, if we're parsing a fragment, the values must match
1017                     if (!context.getSecurityHandler().getAuthMethod().equals(method.toString(false, true)))
1018                         throw new IllegalStateException("Conflicting auth-method value in "+descriptor.getResource());
1019                     break;
1020                 }
1021             } 
1022             
1023             //handle realm-name merge
1024             XmlParser.Node name = node.get("realm-name");
1025             String nameStr = (name == null ? "default" : name.toString(false, true));
1026             o = context.getMetaData().getOrigin("realm-name");
1027             switch (o)
1028             {
1029                 case NotSet:
1030                 {
1031                     //no descriptor has set the realm-name yet, so set it
1032                     context.getSecurityHandler().setRealmName(nameStr);
1033                     context.getMetaData().setOrigin("realm-name", descriptor);
1034                     break;
1035                 }
1036                 case WebXml:
1037                 case WebDefaults:
1038                 case WebOverride:
1039                 {
1040                     //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
1041                     if (!(descriptor instanceof FragmentDescriptor))
1042                     {
1043                         context.getSecurityHandler().setRealmName(nameStr);
1044                         context.getMetaData().setOrigin("realm-name", descriptor); 
1045                     }
1046                     break;
1047                 }
1048                 case WebFragment:
1049                 {
1050                     //a fragment set it, and we must be parsing another fragment, so the values must match
1051                     if (!context.getSecurityHandler().getRealmName().equals(nameStr))
1052                         throw new IllegalStateException("Conflicting realm-name value in "+descriptor.getResource());
1053                     break;
1054                 }
1055             }
1056  
1057             if (Constraint.__FORM_AUTH.equals(context.getSecurityHandler().getAuthMethod()))
1058             {  
1059                 XmlParser.Node formConfig = node.get("form-login-config");
1060                 if (formConfig != null)
1061                 {
1062                     String loginPageName = null;
1063                     XmlParser.Node loginPage = formConfig.get("form-login-page");
1064                     if (loginPage != null) 
1065                         loginPageName = loginPage.toString(false, true);
1066                     String errorPageName = null;
1067                     XmlParser.Node errorPage = formConfig.get("form-error-page");
1068                     if (errorPage != null) 
1069                         errorPageName = errorPage.toString(false, true);
1070                     
1071                     //handle form-login-page
1072                     o = context.getMetaData().getOrigin("form-login-page");
1073                     switch (o)
1074                     {
1075                         case NotSet:
1076                         {
1077                             //Never been set before, so accept it
1078                             context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
1079                             context.getMetaData().setOrigin("form-login-page",descriptor);
1080                             break;
1081                         }
1082                         case WebXml:
1083                         case WebDefaults:
1084                         case WebOverride:
1085                         {
1086                             //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
1087                             if (!(descriptor instanceof FragmentDescriptor))
1088                             {
1089                                 context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
1090                                 context.getMetaData().setOrigin("form-login-page",descriptor);
1091                             }
1092                             break;
1093                         }
1094                         case WebFragment:
1095                         {
1096                             //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
1097                             if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE).equals(loginPageName))
1098                                 throw new IllegalStateException("Conflicting form-login-page value in "+descriptor.getResource());
1099                             break;
1100                         }
1101                     }
1102                     
1103                     //handle form-error-page
1104                     o = context.getMetaData().getOrigin("form-error-page");
1105                     switch (o)
1106                     {
1107                         case NotSet:
1108                         {
1109                             //Never been set before, so accept it
1110                             context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
1111                             context.getMetaData().setOrigin("form-error-page",descriptor);
1112                             break;
1113                         }
1114                         case WebXml:
1115                         case WebDefaults:
1116                         case WebOverride:
1117                         {
1118                             //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
1119                             if (!(descriptor instanceof FragmentDescriptor))
1120                             {
1121                                 context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
1122                                 context.getMetaData().setOrigin("form-error-page",descriptor);
1123                             }
1124                             break;
1125                         }
1126                         case WebFragment:
1127                         {
1128                             //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
1129                             if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE).equals(errorPageName))
1130                                 throw new IllegalStateException("Conflicting form-error-page value in "+descriptor.getResource());
1131                             break;
1132                         }
1133                     }              
1134                 }
1135                 else
1136                 {
1137                     throw new IllegalStateException("!form-login-config");
1138                 }
1139             }
1140         }
1141     }
1142     
1143     /**
1144      * @param context
1145      * @param descriptor
1146      * @param node
1147      */
1148     protected void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1149     {
1150         //ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged
1151         XmlParser.Node roleNode = node.get("role-name");
1152         String role = roleNode.toString(false, true);
1153         ((ConstraintAware)context.getSecurityHandler()).addRole(role);
1154     }
1155     
1156     
1157     /**
1158      * @param context
1159      * @param descriptor
1160      * @param node
1161      */
1162     protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1163     {
1164         String name = node.getString("filter-name", false, true);
1165         FilterHolder holder = context.getServletHandler().getFilter(name);
1166         if (holder == null)
1167         {
1168             holder = context.getServletHandler().newFilterHolder();
1169             holder.setName(name);
1170             context.getServletHandler().addFilter(holder);
1171         }
1172 
1173         String filter_class = node.getString("filter-class", false, true);
1174         if (filter_class != null) 
1175         {
1176             ((WebDescriptor)descriptor).addClassName(filter_class);
1177             
1178             Origin o = context.getMetaData().getOrigin(name+".filter.filter-class");
1179             switch (o)
1180             {
1181                 case NotSet:
1182                 {
1183                     //no class set yet
1184                     holder.setClassName(filter_class);
1185                     context.getMetaData().setOrigin(name+".filter.filter-class", descriptor);
1186                     break;
1187                 }
1188                 case WebXml:
1189                 case WebDefaults:
1190                 case WebOverride:
1191                 {
1192                     //filter class was set in web.xml, only allow other web xml descriptors (override/default) to change it
1193                     if (!(descriptor instanceof FragmentDescriptor))
1194                     {
1195                         holder.setClassName(filter_class);
1196                         context.getMetaData().setOrigin(name+".filter.filter-class", descriptor); 
1197                     }
1198                     break;
1199                 }
1200                 case WebFragment:
1201                 {
1202                     //the filter class was set up by a web fragment, all fragments must be the same
1203                     if (!holder.getClassName().equals(filter_class))
1204                         throw new IllegalStateException("Conflicting filter-class for filter "+name+" in "+descriptor.getResource());
1205                     break;
1206                 }
1207             }
1208            
1209         }
1210 
1211         Iterator<XmlParser.Node>  iter = node.iterator("init-param");
1212         while (iter.hasNext())
1213         {
1214             XmlParser.Node paramNode = iter.next();
1215             String pname = paramNode.getString("param-name", false, true);
1216             String pvalue = paramNode.getString("param-value", false, true);
1217             
1218             Origin origin = context.getMetaData().getOrigin(name+".filter.init-param."+pname);
1219             switch (origin)
1220             {
1221                 case NotSet:
1222                 {
1223                     //init-param not already set, so set it
1224                     holder.setInitParameter(pname, pvalue); 
1225                     context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
1226                     break;
1227                 }
1228                 case WebXml:
1229                 case WebDefaults:
1230                 case WebOverride:
1231                 {
1232                     //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
1233                     //otherwise just ignore it
1234                     if (!(descriptor instanceof FragmentDescriptor))
1235                     {
1236                         holder.setInitParameter(pname, pvalue); 
1237                         context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
1238                     }
1239                     break;
1240                 }
1241                 case WebFragment:
1242                 {
1243                     //previously set by a web-fragment, make sure that the value matches, otherwise its an error
1244                     if (!holder.getInitParameter(pname).equals(pvalue))
1245                         throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
1246                     break;
1247                 }
1248             }  
1249         }
1250 
1251         String async=node.getString("async-supported",false,true);
1252         if (async!=null)
1253             holder.setAsyncSupported(async.length()==0||Boolean.valueOf(async));
1254         if (async!=null)
1255         {
1256             boolean val = async.length()==0||Boolean.valueOf(async);
1257             Origin o = context.getMetaData().getOrigin(name+".filter.async-supported");
1258             switch (o)
1259             {
1260                 case NotSet:
1261                 {
1262                     //set it
1263                     holder.setAsyncSupported(val);
1264                     context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);
1265                     break;
1266                 }
1267                 case WebXml:
1268                 case WebDefaults:
1269                 case WebOverride:
1270                 {
1271                     //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)
1272                     if (!(descriptor instanceof FragmentDescriptor))
1273                     {
1274                         holder.setAsyncSupported(val);
1275                         context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);  
1276                     }             
1277                     break;
1278                 }
1279                 case WebFragment:
1280                 {
1281                     //async-supported set by another fragment, this fragment's value must match
1282                     if (holder.isAsyncSupported() != val)
1283                         throw new IllegalStateException("Conflicting async-supported="+async+" for filter "+name+" in "+descriptor.getResource());
1284                     break;
1285                 }
1286             }
1287         }
1288         
1289     }
1290 
1291     /**
1292      * @param context
1293      * @param descriptor
1294      * @param node
1295      */
1296     protected void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1297     {
1298         //Servlet Spec 3.0, p74
1299         //filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
1300         //Maintenance update 3.0a to spec:
1301         //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
1302       
1303         
1304         String filter_name = node.getString("filter-name", false, true);
1305         
1306         Origin origin = context.getMetaData().getOrigin(filter_name+".filter.mappings");
1307         
1308         switch (origin)
1309         {
1310             case NotSet:
1311             {
1312                 //no filtermappings for this filter yet defined
1313                 context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor);
1314                 addFilterMapping(filter_name, node, context);
1315                 break;
1316             }
1317             case WebDefaults:
1318             case WebOverride:
1319             case WebXml:
1320             {
1321                 //filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings.
1322                 if (!(descriptor instanceof FragmentDescriptor))
1323                 {
1324                    addFilterMapping(filter_name, node, context);
1325                 }
1326                 break;
1327             }
1328             case WebFragment:
1329             {
1330                 //filter mappings first defined in a web-fragment, allow other fragments to add
1331                 addFilterMapping(filter_name, node, context);
1332                 break;
1333             }
1334         }
1335     }
1336 
1337     
1338     /**
1339      * @param context
1340      * @param descriptor
1341      * @param node
1342      */
1343     protected void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1344     {
1345         String className = node.getString("listener-class", false, true);
1346         EventListener listener = null;
1347         try
1348         {
1349             if (className != null && className.length()> 0)
1350             {
1351                 //Servlet Spec 3.0 p 74
1352                 //Duplicate listener declarations don't result in duplicate listener instances
1353                 EventListener[] listeners=context.getEventListeners();
1354                 if (listeners!=null)
1355                 {
1356                     for (EventListener l : listeners)
1357                     {
1358                         if (l.getClass().getName().equals(className))
1359                             return;
1360                     }
1361                 }
1362                 
1363                 ((WebDescriptor)descriptor).addClassName(className);
1364 
1365                 Class<? extends EventListener> listenerClass = (Class<? extends EventListener>)context.loadClass(className);
1366                 listener = newListenerInstance(context,listenerClass);
1367                 if (!(listener instanceof EventListener))
1368                 {
1369                     LOG.warn("Not an EventListener: " + listener);
1370                     return;
1371                 }
1372                 context.addEventListener(listener);
1373                 context.getMetaData().setOrigin(className+".listener", descriptor);
1374                 
1375             }
1376         }
1377         catch (Exception e)
1378         {
1379             LOG.warn("Could not instantiate listener " + className, e);
1380             return;
1381         }
1382     }
1383     
1384     /**
1385      * @param context
1386      * @param descriptor
1387      * @param node
1388      */
1389     protected void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
1390     {
1391         // the element has no content, so its simple presence
1392         // indicates that the webapp is distributable...
1393         //Servlet Spec 3.0 p.74  distributable only if all fragments are distributable
1394         ((WebDescriptor)descriptor).setDistributable(true);
1395     }
1396     
1397     /**
1398      * @param context
1399      * @param clazz
1400      * @return the new event listener
1401      * @throws ServletException
1402      * @throws InstantiationException
1403      * @throws IllegalAccessException
1404      */
1405     protected EventListener newListenerInstance(WebAppContext context,Class<? extends EventListener> clazz) throws ServletException, InstantiationException, IllegalAccessException
1406     {
1407         try
1408         {
1409             return ((ServletContextHandler.Context)context.getServletContext()).createListener(clazz);
1410         }
1411         catch (ServletException se)
1412         {
1413             Throwable cause = se.getRootCause();
1414             if (cause instanceof InstantiationException)
1415                 throw (InstantiationException)cause;
1416             if (cause instanceof IllegalAccessException)
1417                 throw (IllegalAccessException)cause;
1418             throw se;
1419         }
1420     }
1421     
1422     /**
1423      * @param p
1424      * @return the normalized pattern
1425      */
1426     protected String normalizePattern(String p)
1427     {
1428         if (p != null && p.length() > 0 && !p.startsWith("/") && !p.startsWith("*")) return "/" + p;
1429         return p;
1430     }
1431 
1432   
1433 }