2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
110
111
112 /***
113 * <p>Create a new lifecycle listener.</p>
114 */
115 public LifecycleListener2() {
116 }
117
118
119
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
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
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
204 servletContext = event.getServletContext();
205 servletContext.setAttribute(FacesConstants.VIEW_CALLBACKS,
206 new ViewControllerCallbacks2());
207
208
209
210 FacesConfigConfig config = facesConfigConfig();
211 servletContext.setAttribute(FACES_CONFIG_CONFIG, config);
212
213
214
215
216
217 Application application = null;
218 try {
219 application = application();
220 } catch (Exception e) {
221 ;
222 }
223
224 List<Class> classes;
225
226 String scanPackages = servletContext.getInitParameter(SCAN_PACKAGES);
227 if (scanPackages != null) {
228
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
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
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
281 FacesConfigParser parser = facesConfigParser(config);
282 List<URL> resources = null;
283 URL url = null;
284
285
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
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
310
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
375 event.getServletContext().removeAttribute(FACES_CONFIG_CONFIG);
376
377 }
378
379
380
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
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
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
466
467 }
468
469
470
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
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
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
602
603
604
605
606
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
614 if ((value instanceof AbstractRequestBean) || (value instanceof ViewController)) {
615 list.add(name);
616 continue;
617 }
618
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
633
634 }
635
636
637
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
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
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
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;
957 }
958 String name = entry.getName();
959 if (name.startsWith("META-INF/")) {
960 continue;
961 }
962 if (!name.endsWith(".class")) {
963 continue;
964 }
965 name = name.substring(0, name.length() - 6);
966 Class clazz = null;
967 try {
968 clazz = loader.loadClass(name.replace('/', '.'));
969 } catch (NoClassDefFoundError e) {
970 ;
971 } catch (Exception e) {
972 ;
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
999 List<URL> list = new ArrayList<URL>();
1000
1001
1002 String resources = servletContext.getInitParameter("javax.faces.CONFIG_FILES");
1003 if (resources == null) {
1004 return list;
1005 }
1006
1007
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
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
1218
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
1234
1235 Map<Class,Method> map = maps.get(clazz);
1236 if (map != null) {
1237 return map.get(annotation);
1238 }
1239
1240
1241
1242
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());
1472 mpc.setValue(property.value());
1473 mbc.addProperty(mpc);
1474 continue;
1475 }
1476
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());
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
1523
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());
1588 path = path.substring(0, path.length() - 6);
1589 path = path.replace('/', '.');
1590 Class clazz = null;
1591 try {
1592 clazz = loader.loadClass(path);
1593 } catch (NoClassDefFoundError e) {
1594 ;
1595 } catch (Exception e) {
1596 ;
1597 }
1598 if (clazz != null) {
1599 list.add(clazz);
1600 }
1601 }
1602 }
1603
1604 }
1605
1606
1607
1608 }