View Javadoc

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