2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.tiger.view.faces;
19  
20  import java.io.IOException;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.net.JarURLConnection;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.Enumeration;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.jar.JarEntry;
35  import java.util.jar.JarFile;
36  import javax.faces.FacesException;
37  import javax.faces.FactoryFinder;
38  import javax.faces.application.Application;
39  import javax.faces.application.ApplicationFactory;
40  import javax.faces.context.FacesContext;
41  import javax.faces.event.PhaseListener;
42  import javax.faces.lifecycle.Lifecycle;
43  import javax.faces.lifecycle.LifecycleFactory;
44  import javax.faces.render.RenderKit;
45  import javax.faces.render.RenderKitFactory;
46  import javax.faces.render.Renderer;
47  import javax.servlet.ServletContext;
48  import javax.servlet.ServletContextAttributeEvent;
49  import javax.servlet.ServletContextEvent;
50  import javax.servlet.ServletRequest;
51  import javax.servlet.ServletRequestAttributeEvent;
52  import javax.servlet.ServletRequestEvent;
53  import javax.servlet.http.HttpSessionBindingEvent;
54  import javax.servlet.http.HttpSessionEvent;
55  import org.apache.commons.logging.Log;
56  import org.apache.commons.logging.LogFactory;
57  import org.apache.shale.tiger.config.FacesConfigConfig;
58  import org.apache.shale.tiger.config.FacesConfigParser;
59  import org.apache.shale.tiger.managed.Bean;
60  import org.apache.shale.tiger.managed.Property;
61  import org.apache.shale.tiger.managed.Value;
62  import org.apache.shale.tiger.managed.config.ManagedBeanConfig;
63  import org.apache.shale.tiger.managed.config.ManagedPropertyConfig;
64  import org.apache.shale.tiger.register.FacesComponent;
65  import org.apache.shale.tiger.register.FacesConverter;
66  import org.apache.shale.tiger.register.FacesPhaseListener;
67  import org.apache.shale.tiger.register.FacesRenderer;
68  import org.apache.shale.tiger.register.FacesValidator;
69  import org.apache.shale.tiger.register.faces.PhaseListenerAdapter;
70  import org.apache.shale.tiger.view.Activate;
71  import org.apache.shale.tiger.view.Destroy;
72  import org.apache.shale.tiger.view.Init;
73  import org.apache.shale.tiger.view.Passivate;
74  import org.apache.shale.tiger.view.Preprocess;
75  import org.apache.shale.tiger.view.Prerender;
76  import org.apache.shale.tiger.view.Request;
77  import org.apache.shale.tiger.view.Session;
78  import org.apache.shale.tiger.view.View;
79  import org.apache.shale.util.Messages;
80  import org.apache.shale.view.AbstractApplicationBean;
81  import org.apache.shale.view.AbstractRequestBean;
82  import org.apache.shale.view.AbstractSessionBean;
83  import org.apache.shale.view.ViewController;
84  import org.apache.shale.view.faces.FacesConstants;
85  import org.apache.shale.view.faces.LifecycleListener;
86  import org.xml.sax.SAXException;
87  
88  /***
89   * <p>Specialized version of
90   * <code>org.apache.shale.view.faces.LifecycleListener</code>
91   * that implements callbacks to methods tagged by appropriate annotations,
92   * rather than requiring the containing classes to implement a particular
93   * interface or extend a particular subclass.</p>
94   *
95   * <p><strong>IMPLEMENTATION NOTE:</strong> The standard LifecycleListener
96   * instance will <em>delegate</em> to methods of this class after
97   * performing its own appropriate processing.  Therefore, implementation
98   * methods must <strong>NOT</strong> call their superclass counterparts.
99   * Doing so will cause any infinite recursion and ultimately a stack
100  * overflow error.</p>
101  *
102  * $Id: LifecycleListener2.java 489966 2006-12-24 01:43:42Z craigmcc $
103  *
104  * @since 1.0.3
105  */
106 public class LifecycleListener2 extends LifecycleListener {
107 
108 
109     // ------------------------------------------------------------- Constructor
110 
111 
112     /***
113      * <p>Create a new lifecycle listener.</p>
114      */
115     public LifecycleListener2() {
116     }
117 
118 
119     // ------------------------------------------------------ Manifest Constants
120 
121 
122     /***
123      * <p> Servlet context init parameter which defines which packages to scan
124      * for beans.</p>
125      */
126     public static final String SCAN_PACKAGES =
127             "org.apache.shale.tiger.SCAN_PACKAGES";
128 
129 
130     /***
131      * <p>Application scope attribute under which a configured
132      * {@link FacesConfigConfig} bean will be stored, containing
133      * information parsed from the relevant <code>faces-config.xml</code>
134      * resource(s) for this application.</p>
135      */
136     public static final String FACES_CONFIG_CONFIG =
137             "org.apache.shale.tiger.FACES_CONFIG_CONFIG";
138 
139 
140     /***
141      * <p>Context relative path to the default <code>faces-config.xml</code>
142      * resource for a JavaServer Faces application.</p>
143      */
144     private static final String FACES_CONFIG_DEFAULT =
145             "/WEB-INF/faces-config.xml";
146 
147 
148     /***
149      * <p>Resource path used to acquire implicit resources buried
150      * inside application JARs.</p>
151      */
152     private static final String FACES_CONFIG_IMPLICIT =
153             "META-INF/faces-config.xml";
154 
155 
156     /***
157      * <p>Prefix path used to locate web application classes for this
158      * web application.</p>
159      */
160     private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/";
161 
162 
163     /***
164      * <p>Prefix path used to locate web application libraries for this
165      * web application.</p>
166      */
167     private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
168 
169 
170     // ------------------------------------------------------ Instance Variables
171 
172 
173     /***
174      * <p>Parameter values array that passes no parameters.</p>
175      */
176     private static final Object[] PARAMETERS = new Object[0];
177 
178 
179     /***
180      * <p>The <code>ServletContext</code> instance for this application.</p>
181      */
182     private transient ServletContext servletContext = null;
183 
184 
185     // ------------------------------------------ ServletContextListener Methods
186 
187 
188     /***
189      * <p>Respond to a context initialized event.  Forcibly replace
190      * the managed bean services that are different when the Tiger
191      * extensions are loaded.  Then, process the <code>faces-config.xml</code>
192      * resources for this application in order to record the configuration
193      * of managed beans.</p>
194      *
195      * @param event Event to be processed
196      */
197     public void contextInitialized(ServletContextEvent event) {
198 
199         if (log().isInfoEnabled()) {
200             log().info(messages().getMessage("lifecycle.initialized"));
201         }
202 
203         // Replace the ViewControllerCallbacks handler
204         servletContext = event.getServletContext();
205         servletContext.setAttribute(FacesConstants.VIEW_CALLBACKS,
206                                     new ViewControllerCallbacks2());
207 
208         // Create an empty FacesConfigConfig instance and stash it as
209         // an application scope attribute
210         FacesConfigConfig config = facesConfigConfig();
211         servletContext.setAttribute(FACES_CONFIG_CONFIG, config);
212 
213         // Determine if the JSF implementation has been initialized yet.
214         // If it has, we will be able to register interesting classes
215         // directly.  Otherwise, they will have to be queued up for
216         // later processing.
217         Application application = null;
218         try {
219             application = application();
220         } catch (Exception e) {
221             ; // Null means it is not initialized yet
222         }
223 
224         List<Class> classes;
225 
226         String scanPackages = servletContext.getInitParameter(SCAN_PACKAGES);
227         if (scanPackages != null) {
228             // Scan the classes configured by the scan_packages context parameter
229             try {
230                 classes = packageClasses(servletContext, scanPackages);
231             } catch (ClassNotFoundException e) {
232                 throw new FacesException(e);
233             } catch (IOException e) {
234                 throw new FacesException(e);
235             }
236         }
237         else {
238             // Scan the classes in /WEB-INF/classes for interesting annotations
239             try {
240                 classes = webClasses(servletContext);
241             } catch (ClassNotFoundException e) {
242                 throw new FacesException(e);
243             }
244         }
245 
246         try {
247             for (Class clazz : classes) {
248                 if (application != null) {
249                     registerClass(clazz, application);
250                 } else {
251                     queueClass(clazz);
252                 }
253                 scanClass(clazz, config);
254             }
255         } catch (Exception e) {
256             throw new FacesException(e);
257         }
258 
259         if (scanPackages == null) {
260             // Scan the classes in /WEB-INF/lib for interesting annotations
261             List<JarFile> archives = null;
262             try {
263                 archives = webArchives(servletContext);
264                 for (JarFile archive : archives) {
265                     classes = archiveClasses(servletContext, archive);
266                     for (Class clazz : classes) {
267                         if (application != null) {
268                             registerClass(clazz, application);
269                         } else {
270                             queueClass(clazz);
271                         }
272                         scanClass(clazz, config);
273                     }
274                 }
275             } catch (Exception e) {
276                 throw new FacesException(e);
277             }
278         }
279 
280         // Create a parser instance used to parse faces-config.xml resources
281         FacesConfigParser parser = facesConfigParser(config);
282         List<URL> resources = null;
283         URL url = null;
284 
285         // Parse the implicit faces-config.xml resources for this application
286         try {
287             resources = implicitResources(servletContext);
288             for (URL resource : resources) {
289                 url = resource;
290                 parseResource(parser, resource);
291             }
292         } catch (Exception e) {
293             throw new FacesException(messages().getMessage("lifecycle.exception",
294                                                            new Object[] { url.toExternalForm() }), e);
295         }
296 
297         // Parse the configured faces-config.xml resource(s) for this application
298         try {
299             resources = explicitResources(servletContext);
300             for (URL resource : resources) {
301                 url = resource;
302                 parseResource(parser, resource);
303             }
304         } catch (Exception e) {
305             throw new FacesException(messages().getMessage("lifecycle.exception",
306                                                            new Object[] { url.toExternalForm() }), e);
307         }
308 
309         // If not already processed, and if it actually exists, parse
310         // the default faces-config.xml resource for this application
311         try {
312             url = servletContext.getResource(FACES_CONFIG_DEFAULT);
313             if ((url != null) && !resources.contains(url)) {
314                 parseResource(parser, url);
315             }
316         } catch (Exception e) {
317             throw new FacesException(messages().getMessage("lifecycle.exception",
318                                                            new Object[] { url.toExternalForm() }), e);
319         }
320 
321         if (log().isInfoEnabled()) {
322             log().info(messages().getMessage("lifecycle.completed"));
323         }
324 
325     }
326 
327     /***
328      * <p>Return a list of the classes defined within the given packages
329      * If there are no such classes, a zero-length list will be returned.</p>
330      *
331      * @param scanPackages the package configuration
332      *
333      * @exception ClassNotFoundException if a located class cannot be loaded
334      * @exception IOException if an input/output error occurs
335      */
336     private List<Class> packageClasses(ServletContext servletContext, String scanPackages)
337       throws ClassNotFoundException, IOException {
338 
339         List<Class> list = new ArrayList<Class>();
340 
341         String[] scanPackageTokens =  scanPackages.split(",");
342         for (String scanPackageToken : scanPackageTokens)
343         {
344             if (scanPackageToken.toLowerCase().endsWith(".jar"))
345             {
346                 URL jarResource = servletContext.getResource(WEB_LIB_PREFIX + scanPackageToken);
347                 String jarURLString = "jar:" + jarResource.toString() + "!/";
348                 URL url = new URL(jarURLString);
349                 JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
350 
351                 list.addAll(archiveClasses(servletContext, jarFile));
352             }
353             else
354             {
355                 PackageInfo.getInstance().getClasses(list, scanPackageToken);
356             }
357         }
358         return list;
359     }
360 
361 
362     /***
363      * <p>Respond to a context destroyed event.  Clean up our allocated
364      * application scope attributes.</p>
365      *
366      * @param event Event to be processed
367      */
368     public void contextDestroyed(ServletContextEvent event) {
369 
370         if (log().isInfoEnabled()) {
371             log().info(messages().getMessage("lifecycle.destroyed"));
372         }
373 
374         // Clean up our allocated application scope attributes
375         event.getServletContext().removeAttribute(FACES_CONFIG_CONFIG);
376 
377     }
378 
379 
380     // --------------------------------- ServletContextAttributeListener Methods
381 
382 
383     /***
384      * <p>Respond to an application scope attribute being added.  If the
385      * value is an {@link AbstractApplicationBean}, call its
386      * <code>init()</code> method.</p>
387      *
388      * @param event Event to be processed
389      */
390     public void attributeAdded(ServletContextAttributeEvent event) {
391 
392         Object value = event.getValue();
393         if (value != null) {
394             fireApplicationInit(value);
395         }
396 
397     }
398 
399 
400     /***
401      * <p>Respond to an application scope attribute being replaced.
402      * If the old value was an {@link AbstractApplicationBean}, call
403      * its <code>destroy()</code> method.  If the new value is an
404      * {@link AbstractApplicationBean}, call its <code>init()</code>
405      * method.</p>
406      *
407      * @param event Event to be processed
408      */
409     public void attributeReplaced(ServletContextAttributeEvent event) {
410 
411         Object value = event.getValue();
412         if (value != null) {
413             fireApplicationDestroy(value);
414         }
415 
416         value = event.getServletContext().getAttribute(event.getName());
417         if (value != null) {
418             fireApplicationInit(value);
419         }
420 
421     }
422 
423 
424     /***
425      * <p>Respond to an application scope attribute being removed.
426      * If the old value was an {@link AbstractApplicationBean}, call
427      * its <code>destroy()</code> method.</p>
428      *
429      * @param event Event to be processed
430      */
431     public void attributeRemoved(ServletContextAttributeEvent event) {
432 
433         Object value = event.getValue();
434         if (value != null) {
435             fireApplicationDestroy(value);
436         }
437 
438     }
439 
440 
441     // --------------------------------------------- HttpSessionListener Methods
442 
443 
444     /***
445      * <p>Respond to a session created event.  No special processing
446      * is required.</p>
447      *
448      * @param event Event to be processed
449      */
450     public void sessionCreated(HttpSessionEvent event) {
451 
452         // No special processing is required
453 
454     }
455 
456 
457     /***
458      * <p>Respond to a session destroyed event.  No special
459      * processing is required</p>
460      *
461      * @param event Event to be processed
462      */
463     public void sessionDestroyed(HttpSessionEvent event) {
464 
465         // No special processing is required
466 
467     }
468 
469 
470     // ----------------------------------- HttpSessionActivationListener Methods
471 
472 
473     /***
474      * <p>Respond to a "session will passivate" event.  Notify all session
475      * scope attributes that are {@link AbstractSessionBean}s.</p>
476      *
477      * @param event Event to be processed
478      */
479     public void sessionWillPassivate(HttpSessionEvent event) {
480 
481         Enumeration names = event.getSession().getAttributeNames();
482         while (names.hasMoreElements()) {
483             String name = (String) names.nextElement();
484             Object value = event.getSession().getAttribute(name);
485             if (value != null) {
486                 fireSessionPassivate(value);
487             }
488         }
489 
490     }
491 
492 
493     /***
494      * <p>Respond to a "session did activate" event.  Notify all session
495      * scope attributes that are {@link AbstractSessionBean}s.</p>
496      *
497      * @param event Event to be processed
498      */
499     public void sessionDidActivate(HttpSessionEvent event) {
500 
501         Enumeration names = event.getSession().getAttributeNames();
502         while (names.hasMoreElements()) {
503             String name = (String) names.nextElement();
504             Object value = event.getSession().getAttribute(name);
505             if (value != null) {
506                 fireSessionActivate(value);
507             }
508         }
509 
510     }
511 
512 
513     // ------------------------------------ HttpSessionAttributeListener Methods
514 
515 
516     /***
517      * <p>Respond to a session scope attribute being added.  If the
518      * value is an {@link AbstractSessionBean}, call its
519      * <code>init()</code> method.</p>
520      *
521      * @param event Event to be processed
522      */
523     public void attributeAdded(HttpSessionBindingEvent event) {
524 
525         Object value = event.getValue();
526         if (value != null) {
527             fireSessionInit(value);
528         }
529 
530     }
531 
532 
533     /***
534      * <p>Respond to a session scope attribute being replaced.
535      * If the old value was an {@link AbstractSessionBean}, call
536      * its <code>destroy()</code> method.  If the new value is an
537      * {@link AbstractSessionBean}, call its <code>init()</code>
538      * method.</p>
539      *
540      * @param event Event to be processed
541      */
542     public void attributeReplaced(HttpSessionBindingEvent event) {
543 
544         Object value = event.getValue();
545         if (value != null) {
546             fireSessionDestroy(value);
547         }
548 
549         value = event.getSession().getAttribute(event.getName());
550         if (value != null) {
551             fireSessionInit(value);
552         }
553 
554     }
555 
556 
557     /***
558      * <p>Respond to a session scope attribute being removed.
559      * If the old value was an {@link AbstractSessionBean}, call
560      * its <code>destroy()</code> method.</p>
561      *
562      * @param event Event to be processed
563      */
564     public void attributeRemoved(HttpSessionBindingEvent event) {
565 
566         Object value = event.getValue();
567         if (value != null) {
568             fireSessionDestroy(value);
569         }
570 
571     }
572 
573 
574     // ------------------------------------------ ServletRequestListener Methods
575 
576 
577     /***
578      * <p>Respond to a request created event.  If we have accumulated any
579      * classes to register with our JSF implementation (but could not initially
580      * because it was not initialized before we were), register them now.</p>
581      *
582      * @param event Event to be processed
583      */
584     public void requestInitialized(ServletRequestEvent event) {
585 
586         queueRegister();
587 
588     }
589 
590 
591     /***
592      * <p>Respond to a request destroyed event.  Cause any instance of
593      * ViewController or AbstractRequestBean, plus any bean whose class
594      * contains the appropriate annotations, to be removed (which will trigger
595      * an attribute removed event).</p>
596      *
597      * @param event Event to be processed
598      */
599     public void requestDestroyed(ServletRequestEvent event) {
600 
601         // FIXME - may be more performant to just cause all request attributes
602         // to be removed, because events will only be fired on relevant ones
603         // --------------------------------------------------------------------
604         // Remove any AbstractRequestBean or ViewController attributes,
605         // as well as beans whose class contains the relevant annotations
606         // which will trigger an attributeRemoved event
607         List list = new ArrayList();
608         ServletRequest request = event.getServletRequest();
609         Enumeration names = request.getAttributeNames();
610         while (names.hasMoreElements()) {
611             String name = (String) names.nextElement();
612             Object value = request.getAttribute(name);
613             // Direct implementations of the relevant APIs are included
614             if ((value instanceof AbstractRequestBean) || (value instanceof ViewController)) {
615                 list.add(name);
616                 continue;
617             }
618             // So are beans whose class implements the relevant annotations
619             Class clazz = value.getClass();
620             if ((clazz.getAnnotation(Request.class) != null)
621              || (clazz.getAnnotation(View.class) != null)) {
622                  list.add(name);
623                  continue;
624              }
625         }
626         Iterator keys = list.iterator();
627         while (keys.hasNext()) {
628             String key = (String) keys.next();
629             event.getServletRequest().removeAttribute(key);
630         }
631 
632         // No special processing is required
633 
634     }
635 
636 
637     // --------------------------------- ServletRequestAttributeListener Methods
638 
639 
640     /***
641      * <p>Respond to a request scope attribute being added.  If the
642      * value is an {@link AbstractRequestBean}, call its <code>init()</code> method.
643      * </p>
644      *
645      * @param event Event to be processed
646      */
647     public void attributeAdded(ServletRequestAttributeEvent event) {
648 
649         Object value = event.getValue();
650         if (value != null) {
651             fireRequestInit(value);
652         }
653 
654     }
655 
656 
657     /***
658      * <p>Respond to a request scope attribute being replaced.
659      * If the old value was an {@link AbstractRequestBean},
660      * call its <code>destroy()</code> method.  If the new value is an
661      * {@link AbstractRequestBean}, call its <code>init()</code> method.</p>
662      *
663      * @param event Event to be processed
664      */
665     public void attributeReplaced(ServletRequestAttributeEvent event) {
666 
667         Object value = event.getValue();
668         if (value != null) {
669             fireRequestDestroy(value);
670         }
671 
672         value = event.getServletRequest().getAttribute(event.getName());
673         if (value != null) {
674             fireRequestInit(value);
675         }
676 
677     }
678 
679 
680     /***
681      * <p>Respond to a request scope attribute being removed.
682      * If the old value was an {@link AbstractRequestBean},
683      * call its <code>destroy()</code> method.</p>
684      *
685      * @param event Event to be processed
686      */
687     public void attributeRemoved(ServletRequestAttributeEvent event) {
688 
689         Object value = event.getValue();
690         if (value != null) {
691             fireRequestDestroy(value);
692         }
693 
694     }
695 
696 
697     // ------------------------------------------------------- Protected Methods
698 
699 
700     /***
701      * <p>Fire a destroy event on an @{link AbstractApplicationBean}.</p>
702      *
703      * @param bean {@link AbstractApplicationBean} to fire event on
704      */
705     protected void fireApplicationDestroy(Object bean) {
706 
707         if (bean instanceof AbstractApplicationBean) {
708             super.fireApplicationDestroy(bean);
709             return;
710         }
711 
712         try {
713             Method method = method(bean, Destroy.class);
714             if (method != null) {
715                 method.invoke(bean, PARAMETERS);
716             }
717         } catch (InvocationTargetException e) {
718             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
719         } catch (Exception e) {
720             handleException(FacesContext.getCurrentInstance(), e);
721         }
722 
723     }
724 
725 
726     /***
727      * <p>Fire an init event on an {@link AbstractApplicationBean}.</p>
728      *
729      * @param bean {@link AbstractApplicationBean} to fire event on
730      */
731     protected void fireApplicationInit(Object bean) {
732 
733         if (bean instanceof AbstractApplicationBean) {
734             super.fireApplicationInit(bean);
735             return;
736         }
737 
738         try {
739             Method method = method(bean, Init.class);
740             if (method != null) {
741                 method.invoke(bean, PARAMETERS);
742             }
743         } catch (InvocationTargetException e) {
744             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
745         } catch (Exception e) {
746             handleException(FacesContext.getCurrentInstance(), e);
747         }
748 
749     }
750 
751 
752     /***
753      * <p>Fire a destroy event on an @{link AbstractRequestBean}.</p>
754      *
755      * @param bean {@link AbstractRequestBean} to fire event on
756      */
757     protected void fireRequestDestroy(Object bean) {
758 
759         if ((bean instanceof AbstractRequestBean)
760             || (bean instanceof ViewController)) {
761             super.fireRequestDestroy(bean);
762             return;
763         }
764 
765         try {
766             Method method = method(bean, Destroy.class);
767             if (method != null) {
768                 method.invoke(bean, PARAMETERS);
769             }
770         } catch (InvocationTargetException e) {
771             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
772         } catch (Exception e) {
773             handleException(FacesContext.getCurrentInstance(), e);
774         }
775 
776     }
777 
778 
779     /***
780      * <p>Fire an init event on an {@link AbstractRequestBean}.</p>
781      *
782      * @param bean {@link AbstractRequestBean} to fire event on
783      */
784     protected void fireRequestInit(Object bean) {
785 
786         if ((bean instanceof AbstractRequestBean)
787             || (bean instanceof ViewController)) {
788             super.fireRequestInit(bean);
789             return;
790         }
791 
792         try {
793             Method method = method(bean, Init.class);
794             if (method != null) {
795                 method.invoke(bean, PARAMETERS);
796             }
797         } catch (InvocationTargetException e) {
798             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
799         } catch (Exception e) {
800             handleException(FacesContext.getCurrentInstance(), e);
801         }
802 
803     }
804 
805 
806     /***
807      * <p>Fire an activate event on an @{link AbstractSessionBean}.</p>
808      *
809      * @param bean {@link AbstractSessionBean} to fire event on
810      */
811     protected void fireSessionActivate(Object bean) {
812 
813         if (bean instanceof AbstractSessionBean) {
814             super.fireSessionActivate(bean);
815             return;
816         }
817 
818         try {
819             Method method = method(bean, Activate.class);
820             if (method != null) {
821                 method.invoke(bean, PARAMETERS);
822             }
823         } catch (InvocationTargetException e) {
824             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
825         } catch (Exception e) {
826             handleException(FacesContext.getCurrentInstance(), e);
827         }
828 
829     }
830 
831 
832     /***
833      * <p>Fire a destroy event on an @{link AbstractSessionBean}.</p>
834      *
835      * @param bean {@link AbstractSessionBean} to fire event on
836      */
837     protected void fireSessionDestroy(Object bean) {
838 
839         if (bean instanceof AbstractSessionBean) {
840             super.fireSessionDestroy(bean);
841             return;
842         }
843 
844         try {
845             Method method = method(bean, Destroy.class);
846             if (method != null) {
847                 method.invoke(bean, PARAMETERS);
848             }
849         } catch (InvocationTargetException e) {
850             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
851         } catch (Exception e) {
852             handleException(FacesContext.getCurrentInstance(), e);
853         }
854 
855     }
856 
857 
858     /***
859      * <p>Fire an init event on an {@link AbstractSessionBean}.</p>
860      *
861      * @param bean {@link AbstractSessionBean} to fire event on
862      */
863     protected void fireSessionInit(Object bean) {
864 
865         if (bean instanceof AbstractSessionBean) {
866             super.fireSessionInit(bean);
867             return;
868         }
869 
870         try {
871             Method method = method(bean, Init.class);
872             if (method != null) {
873                 method.invoke(bean, PARAMETERS);
874             }
875         } catch (InvocationTargetException e) {
876             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
877         } catch (Exception e) {
878             handleException(FacesContext.getCurrentInstance(), e);
879         }
880 
881     }
882 
883 
884     /***
885      * <p>Fire an passivate event on an @{link AbstractSessionBean}.</p>
886      *
887      * @param bean {@link AbstractSessionBean} to fire event on
888      */
889     protected void fireSessionPassivate(Object bean) {
890 
891         if (bean instanceof AbstractSessionBean) {
892             super.fireSessionPassivate(bean);
893             return;
894         }
895 
896         try {
897             Method method = method(bean, Passivate.class);
898             if (method != null) {
899                 method.invoke(bean, PARAMETERS);
900             }
901         } catch (InvocationTargetException e) {
902             handleException(FacesContext.getCurrentInstance(), (Exception) e.getCause());
903         } catch (Exception e) {
904             handleException(FacesContext.getCurrentInstance(), e);
905         }
906 
907     }
908 
909 
910     // --------------------------------------------------------- Private Methods
911 
912 
913     /***
914      * <p>The Application instance for this application.</p>
915      */
916     private Application application = null;
917 
918 
919     /***
920      * <p>Return the <code>Application</code> for this application.</p>
921      */
922     private Application application() {
923 
924         if (application == null) {
925             application = ((ApplicationFactory) FactoryFinder.
926               getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
927         }
928         return application;
929 
930     }
931 
932 
933     /***
934      * <p>Return a list of classes to examine from the specified JAR archive.
935      * If this archive has no classes in it, a zero-length list is returned.</p>
936      *
937      * @param context <code>ServletContext</code> instance for
938      *  this application
939      * @param jar <code>JarFile</code> for the archive to be scanned
940      *
941      * @exception ClassNotFoundException if a located class cannot be loaded
942      */
943     private List<Class> archiveClasses(ServletContext context, JarFile jar)
944         throws ClassNotFoundException {
945 
946         // Accumulate and return a list of classes in this JAR file
947         List<Class> list = new ArrayList<Class>();
948         ClassLoader loader = Thread.currentThread().getContextClassLoader();
949         if (loader == null) {
950             loader = this.getClass().getClassLoader();
951         }
952         Enumeration<JarEntry> entries = jar.entries();
953         while (entries.hasMoreElements()) {
954             JarEntry entry = entries.nextElement();
955             if (entry.isDirectory()) {
956                 continue;                         // This is a directory
957             }
958             String name = entry.getName();
959             if (name.startsWith("META-INF/")) {
960                 continue;                         // Attribute files
961             }
962             if (!name.endsWith(".class")) {
963                 continue;                         // This is not a class
964             }
965             name = name.substring(0, name.length() - 6); // Trim ".class"
966             Class clazz = null;
967             try {
968                 clazz = loader.loadClass(name.replace('/', '.'));
969             } catch (NoClassDefFoundError e) {
970                 ;  // Skip this class - we cannot analyze classes we cannot load
971             } catch (Exception e) {
972                 ;  // Skip this class - we cannot analyze classes we cannot load
973             }
974             if (clazz != null) {
975                 list.add(clazz);
976             }
977         }
978         return list;
979 
980     }
981 
982 
983     /***
984      * <p>Return a list of URLs of <code>faces-config.xml</code>
985      * resources for this web application that have been explicitly listed
986      * in the appropriate context initialization parameter.  If there are no
987      * such resources, a zero-length list is returned.</p>
988      *
989      * @param servletContext <code>ServletContext</code> instance for this
990      *  application
991      *
992      * @exception MalformedURLException if a context relative resource path
993      *  has incorrect syntax
994      */
995     private List<URL> explicitResources(ServletContext servletContext)
996         throws MalformedURLException {
997 
998         // Create an empty list to contain our results
999         List<URL> list = new ArrayList<URL>();
1000 
1001         // If no initialization parameter was listed, return an empty list
1002         String resources = servletContext.getInitParameter("javax.faces.CONFIG_FILES");
1003         if (resources == null) {
1004             return list;
1005         }
1006 
1007         // Parse the comma-delimited list of context-relative resource paths
1008         while (resources.length() > 0) {
1009             int comma = resources.indexOf(',');
1010             if (comma < 0) {
1011                 resources = resources.trim();
1012                 if (resources.length() > 0) {
1013                     URL url = servletContext.getResource(resources);
1014                     if (url != null) {
1015                         list.add(url);
1016                     }
1017                 }
1018                 resources = "";
1019             } else {
1020                 URL url = servletContext.getResource(resources.substring(0, comma).trim());
1021                 if (url != null) {
1022                     list.add(url);
1023                 }
1024                 resources = resources.substring(comma + 1);
1025             }
1026         }
1027 
1028         // Return the completed list
1029         return list;
1030 
1031     }
1032 
1033     /***
1034      * <p>Create and return an empty {@link FacesConfigConfig} bean that will
1035      * be filled with information later on.</p>
1036      */
1037     private FacesConfigConfig facesConfigConfig() {
1038 
1039         return new FacesConfigConfig();
1040 
1041     }
1042 
1043 
1044     /***
1045      * <p>Create and return a configured {@link FacesConfigParser} instance
1046      * to be used for parsing <code>faces-config.xml</code> resources.  The
1047      * caller will need to set the <code>resource</code> property on this
1048      * instance before calling the <code>parse()</code> method.</p>
1049      *
1050      * @param config <code>FacesConfigBean</code> used to store the
1051      *  information gathered while parsing configuration resources
1052      */
1053     private FacesConfigParser facesConfigParser(FacesConfigConfig config) {
1054 
1055         FacesConfigParser parser = new FacesConfigParser();
1056         parser.setFacesConfig(config);
1057         parser.setValidating(true);
1058         return parser;
1059 
1060     }
1061 
1062 
1063     /***
1064      * <p>Return an array of all <code>Field</code>s reflecting declared
1065      * fields in this class, or in any superclass other than
1066      * <code>java.lang.Object</code>.</p>
1067      *
1068      * @param clazz Class to be analyzed
1069      */
1070     private Field[] fields(Class clazz) {
1071 
1072         Map<String,Field> fields = new HashMap<String,Field>();
1073         do {
1074             for (Field field : clazz.getDeclaredFields()) {
1075                 if (!fields.containsKey(field.getName())) {
1076                     fields.put(field.getName(), field);
1077                 }
1078             }
1079         } while ((clazz = clazz.getSuperclass()) != Object.class);
1080         return (Field[]) fields.values().toArray(new Field[fields.size()]);
1081 
1082     }
1083 
1084 
1085 
1086     /***
1087      * <p>Return a list of URLs to implicit configuration resources
1088      * embedded in this application.</p>
1089      *
1090      * @param servletContext <code>ServletContext</code> instance for this
1091      *  application
1092      *
1093      * @exception IOException if an input/output error occurs
1094      */
1095     private List<URL> implicitResources(ServletContext servletContext)
1096         throws IOException {
1097 
1098         ClassLoader loader = Thread.currentThread().getContextClassLoader();
1099         if (loader == null) {
1100             loader = this.getClass().getClassLoader();
1101         }
1102         Enumeration items = loader.getResources(FACES_CONFIG_IMPLICIT);
1103         List<URL> list = new ArrayList<URL>();
1104         while (items.hasMoreElements()) {
1105             list.add((URL) items.nextElement());
1106         }
1107         return list;
1108 
1109     }
1110 
1111 
1112     /***
1113      * <p>The lifecycle instance for this application.</p>
1114      */
1115     private Lifecycle lifecycle = null;
1116 
1117 
1118     /***
1119      * <p>Return the <code>Lifecycle</code> for this application.</p>
1120      */
1121     private Lifecycle lifecycle() {
1122 
1123         if (lifecycle == null) {
1124             String lifecycleId = servletContext.getInitParameter("javax.faces.LIFECYCLE_ID");
1125             if (lifecycleId == null) {
1126                 lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
1127             }
1128             lifecycle = ((LifecycleFactory) FactoryFinder.
1129               getFactory(FactoryFinder.LIFECYCLE_FACTORY)).getLifecycle(lifecycleId);
1130         }
1131         return lifecycle;
1132 
1133     }
1134 
1135 
1136     /***
1137      * <p>The <code>Log</code> instance we will be using.</p>
1138      */
1139     private transient Log log = null;
1140 
1141 
1142     /***
1143      * <p>Return the <code>Log</code> instance to be used for this class,
1144      * instantiating a new one if necessary.</p>
1145      */
1146     private Log log() {
1147 
1148         if (log == null) {
1149             log = LogFactory.getLog(LifecycleListener2.class);
1150         }
1151         return log;
1152 
1153     }
1154 
1155 
1156     /***
1157      * <p>The <code>Messages</code> instance we will be using.</p>
1158      */
1159     private transient Messages messages = null;
1160 
1161 
1162     /***
1163      * <p>Return the <code>Messages</code> instance to be used for this class,
1164      * instantiating a new one if necessary.</p>
1165      */
1166     private Messages messages() {
1167 
1168         if (messages == null) {
1169             messages = new Messages("org.apache.shale.tiger.faces.Bundle",
1170                                     Thread.currentThread().getContextClassLoader());
1171         }
1172         return messages;
1173 
1174     }
1175 
1176 
1177     /***
1178      * <p>The set of method annotations for callbacks of interest.</p>
1179      */
1180     private static final Class[] annotations =
1181     { Init.class, Preprocess.class, Prerender.class, Destroy.class,
1182       Activate.class, Passivate.class };
1183 
1184 
1185 
1186     /***
1187      * <p>The set of class annotations for classes of interest.</p>
1188      */
1189     private static final Class[] markers =
1190     { View.class, Request.class, Session.class,
1191       org.apache.shale.tiger.view.Application.class };
1192 
1193 
1194 
1195     /***
1196      * <p>Data structure to maintain information about annotated
1197      * methods.  In this map, the key is the Class being analyzed,
1198      * and the value is an inner map.  In the inner map, the key
1199      * is an Annotation class, and the value is the corresponding
1200      * Method instance.</p>
1201      */
1202     private transient Map<Class,Map<Class,Method>> maps =
1203       new HashMap<Class,Map<Class,Method>>();
1204 
1205 
1206     /***
1207      * <p>Return the <code>Method</code> to be called for the specified
1208      * annotation on the specified instance, if any.  If there is no such
1209      * method, return <code>null</code>.</p>
1210      *
1211      * @param instance Instance on which callbacks will be performed
1212      * @param annotation Annotation for which to return a method
1213      */
1214     private Method method(Object instance, Class annotation) {
1215 
1216 
1217         // Does the underlying class implement a relevant class annotation?
1218         // If not, exit early
1219         Class clazz = instance.getClass();
1220         boolean found = false;
1221         for (Class marker : markers) {
1222             if (clazz.getAnnotation(marker) != null) {
1223                 found = true;
1224                 break;
1225             }
1226         }
1227         if (!found) {
1228             return null;
1229         }
1230 
1231         synchronized (maps) {
1232 
1233             // If we have seen this Class already, simply return the
1234             // previously located Method (if any)
1235             Map<Class,Method> map = maps.get(clazz);
1236             if (map != null) {
1237                 return map.get(annotation);
1238             }
1239 
1240 
1241             // Construct and cache a new Map identifying the
1242             // methods of interest for these callbacks
1243             map = new HashMap<Class,Method>();
1244             Method[] methods = clazz.getMethods();
1245             for (Method method : methods) {
1246                 if (method.getParameterTypes().length > 0) {
1247                     continue;
1248                 }
1249                 for (Class anno : annotations) {
1250                     if (method.getAnnotation(anno) != null) {
1251                         map.put(anno, method);
1252                     }
1253                 }
1254             }
1255             maps.put(clazz, map);
1256             return map.get(annotation);
1257 
1258         }
1259 
1260     }
1261 
1262 
1263     /***
1264      * <p>Use the specified parser to parse the resource at the specified
1265      * URL, which will accumulate additional information into the
1266      * <code>FacesConfigConfig</code> instance configured on the parser.</p>
1267      *
1268      * @param parser FacesConfigParser instance to be used
1269      * @param resource URL of the resource to be parsed
1270      *
1271      * @exception IOException if an input/output error occurs
1272      * @exception SAXException if an XML parsing error occurs
1273      */
1274     private void parseResource(FacesConfigParser parser, URL resource)
1275         throws IOException, SAXException {
1276 
1277         if (log().isDebugEnabled()) {
1278             log().debug("Parsing faces-config.xml resource '"
1279                         + resource.toExternalForm() + "'");
1280         }
1281         parser.setResource(resource);
1282         parser.parse();
1283 
1284     }
1285 
1286 
1287 
1288     /***
1289      * <p>A list of classes that need to be registered with the JSF implementation
1290      * after it has been started.</p>
1291      */
1292     private List<Class> queue = new ArrayList<Class>();
1293 
1294 
1295     /***
1296      * <p>Queue the specified class to be registered (via <code>registerClass()</code>)
1297      * at a later time.</p>
1298      *
1299      * @param clazz Class instance to be queued
1300      */
1301     private void queueClass(Class clazz) {
1302         queue.add(clazz);
1303     }
1304 
1305 
1306     /***
1307      * <p>Register any classes that have been queued.  This method is synchronzied
1308      * because it is called from a request listener, and may therefore be subject
1309      * to race conditions if multiple requests are received simultaneously.</p>
1310      */
1311     private synchronized void queueRegister() {
1312 
1313         if (queue == null) {
1314             return;
1315         }
1316         for (Class clazz : queue) {
1317             registerClass(clazz, application());
1318         }
1319         queue = null;
1320 
1321     }
1322 
1323 
1324     /***
1325      * <p>The render kit factory for this application.</p>
1326      */
1327     private RenderKitFactory rkFactory = null;
1328 
1329 
1330     /***
1331      * <p>Return the <code>RenderKitFactory</code> for this application.</p>
1332      */
1333     private RenderKitFactory renderKitFactory() {
1334 
1335         if (rkFactory == null) {
1336             rkFactory = (RenderKitFactory)
1337               FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
1338         }
1339         return rkFactory;
1340 
1341     }
1342 
1343 
1344     /***
1345      * <p>Register the specified class with the specified JavaServer Faces
1346      * <code>Application</code> instance, based on annotations that the
1347      * class is annotated with.</p>
1348      *
1349      * @param clazz Class to be registered
1350      * @param application <code>Application</code> instance with which to
1351      *  register this class
1352      */
1353     private void registerClass(Class clazz, Application application) {
1354 
1355         if (log().isTraceEnabled()) {
1356             log().trace("registerClass(" + clazz.getName() + ")");
1357         }
1358         FacesComponent comp = (FacesComponent) clazz.getAnnotation(FacesComponent.class);
1359         if (comp != null) {
1360             if (log().isTraceEnabled()) {
1361                 log().trace("addComponent(" + comp.value() + "," + clazz.getName() + ")");
1362             }
1363             application().addComponent(comp.value(), clazz.getName());
1364         }
1365 
1366         FacesConverter conv = (FacesConverter) clazz.getAnnotation(FacesConverter.class);
1367         if (conv != null) {
1368             if (log().isTraceEnabled()) {
1369                 log().trace("addConverter(" + conv.value() + "," + clazz.getName() + ")");
1370             }
1371             application().addConverter(conv.value(), clazz.getName());
1372         }
1373 
1374         FacesPhaseListener list = (FacesPhaseListener) clazz.getAnnotation(FacesPhaseListener.class);
1375         if (list != null) {
1376             try {
1377                 Lifecycle lifecycle = lifecycle();
1378                 Object instance = clazz.newInstance();
1379                 if (instance instanceof PhaseListener) {
1380                     lifecycle.addPhaseListener((PhaseListener) instance);
1381                 } else {
1382                     lifecycle.addPhaseListener(new PhaseListenerAdapter(instance));
1383                 }
1384             } catch (FacesException e) {
1385                 throw e;
1386             } catch (Exception e) {
1387                 throw new FacesException(e);
1388             }
1389         }
1390 
1391         FacesRenderer rend = (FacesRenderer) clazz.getAnnotation(FacesRenderer.class);
1392         if (rend != null) {
1393             String renderKitId = rend.renderKitId();
1394             if (renderKitId == null) {
1395                 renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
1396             }
1397             if (log().isTraceEnabled()) {
1398                 log().trace("addRenderer(" + renderKitId + ", " + rend.componentFamily()
1399                   + ", " + rend.rendererType() + ", " + clazz.getName() + ")");
1400             }
1401             try {
1402                 RenderKit rk = renderKitFactory().getRenderKit(null, renderKitId);
1403                 rk.addRenderer(rend.componentFamily(), rend.rendererType(),
1404                                (Renderer) clazz.newInstance());
1405             } catch (Exception e) {
1406                 throw new FacesException(e);
1407             }
1408         }
1409 
1410         FacesValidator val = (FacesValidator) clazz.getAnnotation(FacesValidator.class);
1411         if (val != null) {
1412             if (log().isTraceEnabled()) {
1413                 log().trace("addValidator(" + val.value() + "," + clazz.getName() + ")");
1414             }
1415             application().addValidator(val.value(), clazz.getName());
1416         }
1417 
1418     }
1419 
1420 
1421     /***
1422      * <p>Scan the specified class for those that have annotations
1423      * of interest, and construct appropriate configuration metadata attached
1424      * to the specified {@link FacesConfigConfig} bean.</p>
1425      *
1426      * @param clazz Class to be scanned
1427      * @param config {@link FacesConfigConfig} to be updated
1428      */
1429     private void scanClass(Class clazz, FacesConfigConfig config) {
1430 
1431         if (log().isTraceEnabled()) {
1432             log().trace("Scanning class '" + clazz.getName() + "'");
1433         }
1434         Bean bean = (Bean) clazz.getAnnotation(Bean.class);
1435         if (bean != null) {
1436             if (log().isDebugEnabled()) {
1437                 log().debug("Class '" + clazz.getName() + "' has an @Bean annotation");
1438             }
1439             ManagedBeanConfig mbc = new ManagedBeanConfig();
1440             mbc.setName(bean.name());
1441             mbc.setType(clazz.getName());
1442             switch (bean.scope()) {
1443                 case APPLICATION:
1444                     mbc.setScope("application");
1445                     break;
1446                 case REQUEST:
1447                     mbc.setScope("request");
1448                     break;
1449                 case SESSION:
1450                     mbc.setScope("session");
1451                     break;
1452                 default:
1453                     break;
1454             }
1455             Field[] fields = fields(clazz);
1456             for (Field field : fields) {
1457                 if (log().isTraceEnabled()) {
1458                     log().trace("  Scanning field '" + field.getName() + "'");
1459                 }
1460                 Property property = (Property) field.getAnnotation(Property.class);
1461                 if (property != null) {
1462                     if (log().isDebugEnabled()) {
1463                         log().debug("  Field '" + field.getName() + "' has a @Property annotation");
1464                     }
1465                     ManagedPropertyConfig mpc = new ManagedPropertyConfig();
1466                     String name = property.name();
1467                     if ((name == null) || "".equals(name)) {
1468                         name = field.getName();
1469                     }
1470                     mpc.setName(name);
1471                     mpc.setType(field.getType().getName()); // FIXME - primitives, arrays, etc.
1472                     mpc.setValue(property.value());
1473                     mbc.addProperty(mpc);
1474                     continue;
1475                 }
1476                 // Support deprecated @Value annotation as well
1477                 Value value = (Value) field.getAnnotation(Value.class);
1478                 if (value != null) {
1479                     if (log().isDebugEnabled()) {
1480                         log().debug("  Field '" + field.getName() + "' has a @Value annotation");
1481                     }
1482                     ManagedPropertyConfig mpc = new ManagedPropertyConfig();
1483                     mpc.setName(field.getName());
1484                     mpc.setType(field.getType().getName()); // FIXME - primitives, arrays, etc.
1485                     mpc.setValue(value.value());
1486                     mbc.addProperty(mpc);
1487                     continue;
1488                 }
1489             }
1490             config.addManagedBean(mbc);
1491         }
1492 
1493     }
1494 
1495 
1496     /***
1497      * <p>Return a list of the JAR archives defined under the
1498      * <code>/WEB-INF/lib</code> directory of this web application
1499      * that contain a <code>META-INF/faces-config.xml</code> resource
1500      * (even if that resource is empty).  If there are no such JAR archives,
1501      * a zero-length list will be returned.</p>
1502      *
1503      * @param servletContext <code>ServletContext</code> instance for
1504      *  this application
1505      *
1506      * @exception IOException if an input/output error occurs
1507      */
1508     private List<JarFile> webArchives(ServletContext servletContext)
1509         throws IOException {
1510 
1511         List<JarFile> list = new ArrayList<JarFile>();
1512         Set<Object> paths = servletContext.getResourcePaths(WEB_LIB_PREFIX);
1513         for (Object pathObject : paths) {
1514             String path = (String) pathObject;
1515             if (!path.endsWith(".jar")) {
1516                 continue;
1517             }
1518             URL url = servletContext.getResource(path);
1519             String jarURLString = "jar:" + url.toString() + "!/";
1520             url = new URL(jarURLString);
1521             JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
1522             // Skip this JAR file if it does not have a META-INF/faces-config.xml
1523             // resource (even if that resource is empty)
1524             JarEntry signal = jarFile.getJarEntry(FACES_CONFIG_IMPLICIT);
1525             if (signal == null) {
1526                 if (log().isTraceEnabled()) {
1527                     log().trace("Skip JAR file " + path + " because it has no META-INF/faces-config.xml resource");
1528                 }
1529                 continue;
1530             }
1531             list.add(jarFile);
1532         }
1533         return list;
1534 
1535     }
1536 
1537 
1538     /***
1539      * <p>Return a list of the classes defined under the
1540      * <code>/WEB-INF/classes</code> directory of this web
1541      * application.  If there are no such classes, a zero-length list
1542      * will be returned.</p>
1543      *
1544      * @param servletContext <code>ServletContext</code> instance for
1545      *  this application
1546      *
1547      * @exception ClassNotFoundException if a located class cannot be loaded
1548      */
1549     private List<Class> webClasses(ServletContext servletContext)
1550         throws ClassNotFoundException {
1551 
1552         List<Class> list = new ArrayList<Class>();
1553         webClasses(servletContext, WEB_CLASSES_PREFIX, list);
1554         return list;
1555 
1556     }
1557 
1558 
1559     /***
1560      * <p>Add classes found in the specified directory to the specified
1561      * list, recursively calling this method when a directory is encountered.</p>
1562      *
1563      * @param servletContext <code>ServletContext</code> instance for
1564      *  this application
1565      * @param prefix Prefix specifying the "directory path" to be searched
1566      * @param list List to be appended to
1567      *
1568      * @exception ClassNotFoundException if a located class cannot be loaded
1569      */
1570     private void webClasses(ServletContext servletContext, String prefix, List<Class> list)
1571         throws ClassNotFoundException {
1572 
1573         ClassLoader loader = Thread.currentThread().getContextClassLoader();
1574         if (loader == null) {
1575             loader = this.getClass().getClassLoader();
1576         }
1577         Set<Object> paths = servletContext.getResourcePaths(prefix);
1578         if (log().isTraceEnabled()) {
1579             log().trace("webClasses(" + prefix + ") - Received " + paths.size() + " paths to check");
1580         }
1581         String path = null;
1582         for (Object pathObject : paths) {
1583             path = (String) pathObject;
1584             if (path.endsWith("/")) {
1585                 webClasses(servletContext, path, list);
1586             } else if (path.endsWith(".class")) {
1587                 path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix
1588                 path = path.substring(0, path.length() - 6);        // Strip suffix
1589                 path = path.replace('/', '.'); // Convert to FQCN
1590                 Class clazz = null;
1591                 try {
1592                     clazz = loader.loadClass(path);
1593                 } catch (NoClassDefFoundError e) {
1594                     ; // Skip this class - we cannot analyze classes we cannot load
1595                 } catch (Exception e) {
1596                     ; // Skip this class - we cannot analyze classes we cannot load
1597                 }
1598                 if (clazz != null) {
1599                     list.add(clazz);
1600                 }
1601             }
1602         }
1603 
1604     }
1605 
1606 
1607 
1608 }