12.5 Using a PhaseListener
There's an event type we haven't
talked about so far, namely the
javax.faces.event.PhaseEvent. This is an event
that fires before and after each request processing lifecycle phase,
invoking all javax.faces.event.PhaseListener
instances registered with the
javax.faces.lifecycle.Lifecycle infrastructure
object responsible for coordinating the request processing.
A very simple application probably doesn't have any
use for this event, but it can come in handy in some scenarios.
For
instance, say that your application relies on an external resource
(such as a database). Instead of dealing with the (hopefully rare)
situation that the database is unavailable in all component event
handlers, you can use a PhaseListener that checks
the resource status and navigates to an error page if
it's not available. Here's an
outline for such a listener:
package com.mycompany.event;
import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
public class CheckResourceListener implements PhaseListener {
public PhaseId getPhaseId( ) {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
}
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext( );
if (!isEverythingOkay(context)) {
NavigationHandler nh =
context.getApplication( ).getNavigationHandler( );
nh.handleNavigation(context, null, "unavailable");
}
}
private boolean isEverythingOkay(FacesContext context) {
...
return result;
}
}
The PhaseListener interface has three methods. The
getPhaseId() method returns the
PhaseId value for the phase the listener wants to
be notified in or the PhaseId.ANY_PHASE value in
order to be notified in all phases. Here I return
PhaseId.RESTORE_VIEW to be notified as early as
possible.
The listener is notified by a call to
the beforePhase() method
before the regular processing for the phase begins and by a call to
the afterPhase() method when the regular phase
processing is completed. I've chosen to use another
JSF infrastructure class called the
javax.faces.application.NavigationHandler to
navigate to the error page and, because it relies on having access to
a view, I implement all logic in the afterPhase()
method. If I had implemented the navigation
by some other means, e.g., by calling the
FacesContext redirect() method
with a hardcoded path, I could have done it in the
beforePhase() method instead.
The NavigationHandler is a
pluggable
class. The default implementation reads the navigation rules in the
faces-config.xml file and acts accordingly when
the handleNavigation() method is called. In this
example, I call it with a null value for the
argument that holds the action method binding expression when the
navigation is initiated by the default
ActionListener class, because there is no action
binding in this case. The outcome argument is set
to "unavailable", matching this
navigation rule:
<navigation-rule>
<navigation-case>
<from-outcome>unavailable</from-outcome>
<to-view-id>/unavailable.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Because I've left out the
<from-view-id> element, the rule applies to
all views in the application, redirecting to a view represented by
the unavailable.jsp page. This page can display
an explanation of what's going on and ask the user
to come back later.
The easiest way to register your PhaseListener is
by adding a declaration in the faces-config.xml
file:
<faces-config>
...
<lifecycle>
<phase-listener>
com.mycompany.event.CheckResourceListener
</phase-listener>
</lifecycle>
...
</faces-config>
Checking the status of an external resource is just one example of
how you can use a PhaseListener. Another example
is letting a listener handle custom access control as an alternative
to the access control filter used for the billboard application we
looked at earlier. In the next section, I show you a
PhaseListener that captures state information that
can then be used for debugging.
|