Very early Apache Shale came up with the idea of providing a view controller that allowed to execute dedicated methods (annotated, etc.) during various JSF life-cycle (phase) events for doing initialization work upfront for example. As Shale was hibernated, Apache Orchestra stepped in and added many more useful features. Both frameworks are based on the idea of having a dedicated page bean (a JSF managed bean per view) that would control important processing steps for the entire view (page). It requires following naming conventions in order to associate a page bean to a view, optionally a view controller mapper provided the ability of explicitely associating page beans and views.

In many projects I found the situation that the client didn’t want to pull in another framework just for that little feature. Also for distributing responsibilities, I usually have multiple managed beans per view. So another “artificial” page bean is required just for controlling the others. So I came up with a fairly simple solution that allows to directly invoke those life-cycle callbacks on any managed bean used on a single page.

The <f:phaseListener> tag allows to bind a JSF PhaseListener. You can even bind multiple phase listeners (one for each managed bean; you get the idea).

1
2
<f:phaseListener binding="#{bean1.viewControllerPhaseListener}" />
<f:phaseListener binding="#{bean2.viewControllerPhaseListener}" />

So each of the beans would provide a reference to a phase listener that would take care to perform the proper processing for the bean itself. Every bean then would use a set of annotations (like @PreRenderView, @PostRestoreView, @PreInvokeApplication, etc.) to mark those methods that should be called during a specific phase.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EntityListController<T> {
  private volatile ViewControllerPhaseListener viewControllerPhaseListener;

  public ViewControllerPhaseListener getViewControllerPhaseListener() {
      if (viewControllerPhaseListener == null) {
          viewControllerPhaseListener = new ViewControllerPhaseListener(this);
      }
      return viewControllerPhaseListener;
  }

  @PreRenderView
  public void updateModel() {
      ...
  }

  ...

The code above is partially showing a base class from which many of my managed bean are subclassed. Key is the ViewControllerPhaseListener which hides all of the details of doing the reflection job for finding (and then caching) the annotated methods to invoke during a specific phase. For brevity I’m showing only the key elements of this class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class ViewControllerPhaseListener implements PhaseListener {
  private final Object target;

  public ViewControllerPhaseListener(Object target) {
      super();
      this.target = target;
  }

  private final Class<Annotation>[] LIFECYCLE_ANNOTATIONS = new Class[] { PreRenderView.class, PostRestoreView.class,
      PreInvokeApplication.class };

  private final static Map<Class<?>, Map<Class<? extends Annotation>, Set<Method>>> controllerClasses = new ConcurrentHashMap<Class<?>, Map<Class<? extends Annotation>, Set<Method>>>();

  static Map<Class<?>, Map<Class<? extends Annotation>, Set<Method>>> getControllerClasses() {
      return controllerClasses;
  }

  @Override
  public void beforePhase(PhaseEvent event) {
      if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) {
          invokeAnnotatedMethod(PreRenderView.class, target);
      } else if (PhaseId.INVOKE_APPLICATION.equals(event.getPhaseId())) {
          invokeAnnotatedMethod(PreInvokeApplication.class, target);
      }
  }

  @Override
  public void afterPhase(PhaseEvent event) {
      if (PhaseId.RESTORE_VIEW.equals(event.getPhaseId())) {
          invokeAnnotatedMethod(PostRestoreView.class, target);
      }
  }

  @Override
  public PhaseId getPhaseId() {
      return PhaseId.ANY_PHASE;
  }

  void invokeAnnotatedMethod(Class<? extends Annotation> annotation, Object targetObject) {
      if (!controllerClasses.containsKey(targetObject.getClass())) {
          registerController(targetObject.getClass());
      }
      for (Method method : controllerClasses.get(targetObject.getClass()).get(annotation)) {
          try {
              method.invoke(targetObject);
          } catch (Exception e) {
              LOGGER.error("Error invoking @{} annotated method {} on {}", new Object[] { annotation.getSimpleName(),
                      method, targetObject });
              throw new RuntimeException(e);
          }
      }
  }

  public void registerController(Class<?> controllerClass) {
      // do the reflection work
  }
}

In a few projects this has proven to be quite versatile. Currently I have a couple of interesting phase listener components, from the simple DebugPhaseListener to the OpenTransactionInViewPhaseListener. I can just throw them into any view as they are required.

Comments