JSF Techniques - Phase Listeners

- Damodar Chetty

- Jan 23, 2009

 

Most developers are already aware that Phase Listeners in JavaServer Faces offer an interesting way of inserting custom processing logic into the JSF lifecycle. 

You register a listener with the JSF implementation, and the implementation will then notify your listener both before and after (i.e., around) the phase that your listener is interested in.

However, what is really interesting is that PhaseListeners can also be leveraged to function as an invaluable debugging tool.

In this article, I explore the principles behind phase listeners, and their use in debugging applications.

So let's begin.

1.1. Phase Listeners

A Phase Listener is a class that implements javax.faces.event.PhaseListener, and registers itself with the JSF implementation, to be invoked before and after each phase in the lifecycle that the phase listener wants to be notified about.

Inserting your phase listener is a 2 step process:

1.      You write a class that implements PhaseListener.

2.      You register it with JSF in faces-config.

 

1.1.1. Writing your Phase Listener

The javax.faces.event.PhaseListener interface defines 3 methods that must be implemented.

 

Of these the most critical is getPhaseId() which lets JSF determine the phases around which a given listener should be called.

Each lifecycle phase is uniquely identified using a javax.faces.event.PhaseId instance, which is a type safe enumeration pattern implementation. There are currently 7 such instances, one for each lifecycle phase (such as PhaseId.RESTORE_VIEW or PhaseId.RENDER_RESPONSE), and a wildcard that stands for any of the phases (ANY_PHASE).

 

Here, we define two PhaseListeners. Returning PhaseId.ANY_PHASE from getPhaseId() ensures that both are called before and after each life cycle phase.

Within your phase listener methods, you can use the current FacesContext instance to initialize components, or perform some zany complex logic.

PhaseListeners are especially powerful since you can go so far as to terminate processing early, by calling one of the response completion methods on the FacesContext, i.e., renderResponse() or responseComplete().

Our examples simply provide diagnostic information about a phase.

PhaseTracker also prints out the names of the view being restored, and the view being rendered.

package com.swengsol;

 

import javax.faces.event.PhaseListener;

import javax.faces.event.PhaseId;

import javax.faces.event.PhaseEvent;

 

public class PhaseTracker implements PhaseListener {

  private static final long serialVersionUID = -1L;

  public void beforePhase(PhaseEvent event) {

   if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {

     System.out.println("*PhaseTracker1: Before Phase: " + event.getPhaseId() + ". <--- +
        event.getFacesContext().getExternalContext().getRequestServletPath());

   } else {

     System.out.println("*PhaseTracker1: Before Phase: " + event.getPhaseId());

   }

  }

  public void afterPhase(PhaseEvent event)  {

   if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {

     System.out.println("*PhaseTracker1: After Phase: " + event.getPhaseId() + "--->" +
         event.getFacesContext().getViewRoot().getViewId());

     System.out.println("-------------------------------------------------------------");

   } else {

     System.out.println("*PhaseTracker1: After Phase: " + event.getPhaseId());

   }

  }

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

}

 

 

PhaseTracker2 is provided simply to demonstrate the chaining of listeners.

package com.swengsol;

 

import javax.faces.event.PhaseListener;

import javax.faces.event.PhaseId;

import javax.faces.event.PhaseEvent;

 

public class PhaseTracker2 implements PhaseListener {

  private static final long serialVersionUID = -1L;

  public void beforePhase(PhaseEvent event) {

    System.out.println("***PhaseTracker2: Before Phase: " + event.getPhaseId());

  }

  public void afterPhase(PhaseEvent event)  {

   System.out.println("***PhaseTracker2: After Phase: " + event.getPhaseId());

  }

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

}

 

 

 

1.1.2. Register your phase listener

To register your phase listener, you simply add the following element to your faces-config.xml:

<faces-config>

  <application> ... </application>

  <lifecycle>

   <phase-listener>com.swengsol.PhaseTracker</phase-listener>

   <phase-listener>com.swengsol.PhaseTracker2</phase-listener>

  </lifecycle>

You can have any number of <phase-listener> child elements, and should provide the fully qualified class name of your PhaseListener implementation class.

The order of registration of listeners determines the order in which these listeners will be called during the lifecycle.

 

1.1.3. The Phase Workflow

According to the JSF specification, the typical process flow for each phase in the JSF implementation is as follows:

1.      JSF calls getPhaseId() on each registered PhaseListener, to determine which ones should be notified about the current phase. Only those PhaseListener's whose getPhaseId() returns either the phase id of the current phase, or the wildcard PhaseId.ANY_PHASE will be notified.

2.      JSF then invokes each listener's beforePhase() method, in the order that the listeners are registered.

JSF instantiates a PhaseEvent and passes this into the method. A PhaseEvent carries the current phase's identifier as well as the FacesContext instance for the current request.

3.      A beforePhase() implementation could choose to abort the current phase, by calling one of FacesContext's response completion methods. In which case, the functionality associated with that particular phase is not executed, and the remainder of this processing is skipped.

4.      The functionality associated with the current phase is executed.

5.      JSF then invokes each listener's afterPhase() method in the reverse order of their registration.

6.      If FacesContext.responseComplete() was called, or if we have just completed the RENDER_RESPONSE phase, then abort the lifecycle - we are done here.

7.      If FacesContext.renderResponse() was called, skip to the RENDER_RESPONSE phase (if it has not yet been executed.)

 

1.2. Example Code

Consider this snipped of code from login.xhtml, which shows 2 required input components, a button that submits the form, and 2 cancellation options - one of which uses an immediate attribute of true.

...

<h:inputText id="userName" value="#{loginBean.username}" required="true" />

<h:inputSecret id="password" value="#{loginBean.password}" required="true" />

<h:commandButton id="submitForm" action="#{loginBean.login}" value="Login"/>

<h:commandLink id="cancelRegular" action="cancel" value="Cancel" />

<h:commandLink id="cancelImmediate" action="cancel" value="Cancel Immediately" immediate="true"/>

...

 

The managed bean for this template is as follows:

package com.swengsol;

 

import javax.faces.context.FacesContext;

import javax.faces.application.FacesMessage;

import java.util.Locale;

import java.util.ResourceBundle;

import java.text.MessageFormat;

 

public class LoginBean extends PageBean {

  private String username;

  private String password;

  public String login() {

   final boolean authenticated = authenticate(username, password);

   System.out.println("Calling login() for user:" + username +
     " with password:" + password + ". Authenticated: " + authenticated);

   if (authenticated)      return "homepage";

   else return null;

  }

  public String getUsername() { return username; }

  public void setUsername(String username) { this.username = username; }

  public String getPassword() { return password; }

  public void setPassword(String password) { this.password = password; }

  public boolean authenticat(String username, String password) {

   ... do something smart ...

  }

}

 

The key method here is login() which authenticates the user's credentials and either returns them to the login page, or displays the home page of the application.

 

Running this application gives you console output that looks like the following:

---------------------------------------------------------------------

*PhaseTracker1: Before Phase: RESTORE_VIEW(1). <--- /Login.jsf

***PhaseTracker2: Before Phase: RESTORE_VIEW(1)

***PhaseTracker2: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: Before Phase: PROCESS_VALIDATIONS(3)

***PhaseTracker2: Before Phase: PROCESS_VALIDATIONS(3)

***PhaseTracker2: After Phase: PROCESS_VALIDATIONS(3)

*PhaseTracker1: After Phase: PROCESS_VALIDATIONS(3)

*PhaseTracker1: Before Phase: UPDATE_MODEL_VALUES(4)

***PhaseTracker2: Before Phase: UPDATE_MODEL_VALUES(4)

***PhaseTracker2: After Phase: UPDATE_MODEL_VALUES(4)

*PhaseTracker1: After Phase: UPDATE_MODEL_VALUES(4)

*PhaseTracker1: Before Phase: INVOKE_APPLICATION(5)

***PhaseTracker2: Before Phase: INVOKE_APPLICATION(5)

Calling login() for user:damodar with password:myPassword. Authenticated: true

***PhaseTracker2: After Phase: INVOKE_APPLICATION(5)

*PhaseTracker1: After Phase: INVOKE_APPLICATION(5)

*PhaseTracker1: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: Before Phase: RENDER_RESPONSE(6)

Must go over the wire to build list of contacts

***PhaseTracker2: After Phase: RENDER_RESPONSE(6)

*PhaseTracker1: After Phase: RENDER_RESPONSE(6)--->/HomePage.xhtml

---------------------------------------------------------------------        

This is what is displayed when we click the Login button on the /Login.jsf page.

As shown, this gives you a clear indication of the phases invoked for the incoming request. The first line indicates a postback to the Login.jsf page. The next few lines show how PhaseTracker and PhaseTracker2 are invoked for each phase. In particular, note the invocation of the login() action method in the INVOKE_APPLICATION phase. Finally, you can see that /HomePage.xhtml page is being rendered in the RENDER_RESPONSE phase.

 

Now, lets look at how this output changes, when we deal with clicking the Cancel buttons on the page instead. Both Cancel links use static navigation which points to a plain landing page, /Hello.xhtml (not shown here).

<h:commandLink id="cancelRegular" action="cancel" value="Cancel" />

 

Lets' try clicking this non-immediate Cancel button, with no values in the user name and password fields.

As expected, clicking Cancel forces validation of the required fields on the page, and returns the user to the /Login.xhtml page to enter the user name and password.

 

Cancel (Regular):

*PhaseTracker1: Before Phase: RESTORE_VIEW(1). <--- /Login.jsf

***PhaseTracker2: Before Phase: RESTORE_VIEW(1)

***PhaseTracker2: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: Before Phase: PROCESS_VALIDATIONS(3)

***PhaseTracker2: Before Phase: PROCESS_VALIDATIONS(3)

***PhaseTracker2: After Phase: PROCESS_VALIDATIONS(3)

*PhaseTracker1: After Phase: PROCESS_VALIDATIONS(3)

*PhaseTracker1: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: After Phase: RENDER_RESPONSE(6)

*PhaseTracker1: After Phase: RENDER_RESPONSE(6)--->/Login.xhtml

---------------------------------------------------------------------

 

Next, lets' try clicking the immediate Cancel button, again with no values in the user name and password fields.

<h:commandLink id="cancelImmediate" action="cancel" value="Cancel Immediately" immediate="true"/>

 

As you can see, validation is now completely skipped, and the landing page is displayed to the user.

Cancel (Immediate):

*PhaseTracker1: Before Phase: RESTORE_VIEW(1). <--- /Login.jsf

***PhaseTracker2: Before Phase: RESTORE_VIEW(1)

***PhaseTracker2: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: After Phase: RENDER_RESPONSE(6)

*PhaseTracker1: After Phase: RENDER_RESPONSE(6)--->/Hello.xhtml

---------------------------------------------------------------------

 

1.3. Advanced Concepts

 

1.3.1. Exceptions

JSF guarantees that if a listener's beforePhase() completes successfully, then its afterPhase() will also be called, even if a subsequent beforePhase() throws an exception.

In this next snippet, I've modified PhaseTracker2.beforePhase() to throw a runtime exception.

public class PhaseTracker2 implements PhaseListener {

  public void beforePhase(PhaseEvent event) {

   System.out.println("***PhaseTracker2: Before Phase: " + event.getPhaseId());

   throw new RuntimeException("Arghhhhh!");

  }

  ...

 

Now, if you were to try clicking the immediate Cancel button, you would notice the following changed output:

Cancel (Immediate with exception):

*PhaseTracker1: Before Phase: RESTORE_VIEW(1). <--- /Login.jsf

***PhaseTracker2: Before Phase: RESTORE_VIEW(1)

Jan 23, 2009 9:33:29 AM org.apache.myfaces.lifecycle.PhaseListenerManager informPhaseListenersBefore

SEVERE: Exception in PhaseListener RESTORE_VIEW(1) beforePhase.

java.lang.RuntimeException: Arghhhhh!

  at com.swengsol.PhaseTracker2.beforePhase(PhaseTracker2.java:11)

... lines deleted ...

*PhaseTracker1: After Phase: RESTORE_VIEW(1)

*PhaseTracker1: Before Phase: APPLY_REQUEST_VALUES(2)

***PhaseTracker2: Before Phase: APPLY_REQUEST_VALUES(2)

Jan 23, 2009 9:33:29 AM org.apache.myfaces.lifecycle.PhaseListenerManager informPhaseListenersBefore

SEVERE: Exception in PhaseListener APPLY_REQUEST_VALUES(2) beforePhase.

java.lang.RuntimeException: Arghhhhh!

  at com.swengsol.PhaseTracker2.beforePhase(PhaseTracker2.java:11)

... lines deleted ...

*PhaseTracker1: After Phase: APPLY_REQUEST_VALUES(2)

*PhaseTracker1: Before Phase: RENDER_RESPONSE(6)

***PhaseTracker2: Before Phase: RENDER_RESPONSE(6)

Jan 23, 2009 9:33:29 AM org.apache.myfaces.lifecycle.PhaseListenerManager informPhaseListenersBefore

SEVERE: Exception in PhaseListener RENDER_RESPONSE(6) beforePhase.

java.lang.RuntimeException: Arghhhhh!

  at com.swengsol.PhaseTracker2.beforePhase(PhaseTracker2.java:11)

... lines deleted ...

*PhaseTracker1: After Phase: RENDER_RESPONSE(6)--->/Hello.xhtml

---------------------------------------------------------------------

As you can see, the phase behavior is unchanged. Also, while both PhaseTracker.beforePhase()and PhaseTracker.afterPhase()are invoked around each phase, only PhaseTracker2.beforePhase() is invoked. Because of the exception thrown, the PhaseTracker2.afterPhase() is never called.

Any exceptions thrown during the processing of the actual phase itself will be treated as normal.

1.3.2. Response Completion

The JSF specification states if a beforePhase() implementation aborts the current phase, then the remainder of this processing is skipped. Lets' verify this behavior by modifying our PhaseTracker2 class:

public class PhaseTracker2 implements PhaseListener {

  public void beforePhase(PhaseEvent event) {

   System.out.println("***PhaseTracker2: Before Phase: " + event.getPhaseId());

   try {

     event.getFacesContext().getExternalContext().redirect("http://www.google.com");

   } catch (IOException e) { //do nothing; }

   event.getFacesContext().responseComplete();

  }

  public void afterPhase(PhaseEvent event)  {

   System.out.println("***PhaseTracker2: After Phase: " + event.getPhaseId() +
     ". Render Response is:" + event.getFacesContext().getResponseComplete());

  }

  ...

 

Requesting any page now will result in the following console output:

*PhaseTracker1: Before Phase: RESTORE_VIEW(1). <--- /Login.jsf

***PhaseTracker2: Before Phase: RESTORE_VIEW(1)

***PhaseTracker2: After Phase: RESTORE_VIEW(1). Response Complete is:true

*PhaseTracker1: After Phase: RESTORE_VIEW(1)

 

You will notice that the page being requested, /Login.jsf, is not rendered. Instead, right after the RESTORE_VIEW phase, the request processing is terminated, because of the responseComplete() call made by PhaseTracker2.beforePhase().

 

1.4. Final Comments

It is important to note that your phase listener is likely to add some overhead to the lifecycle processing, so keeping it terse and invoking it only when necessary (by specifying a restrictive phase Id rather than the wildcard PhaseId.ANY_PHASE) are both recommended best practices.

Also note that if your targeted phase is skipped (by invoking responseComplete() or renderResponse() earlier in the lifecycle), then your phase listener may never be called.

Using a phase listener can be a worthwhile tool during development as it gives you ready feedback as to what is going on within the innards of JSF. Just remember to turn this diagnostic tool off in production!