Editor's note: O'Reilly's JavaServer Faces offers developers a guide to learning how to use the JSF framework to build web applications. In this excerpt from the book, author Hans Bergsten looks at the JSF event model, using examples to help explain what's going on "under the hood." Next week, in part two of this excerpt, Hans implements event handling for parts of the sample application discussed here.
|
Related Reading
JavaServer Faces |
When the user clicks a button or link, changes a value in a field, or makes a selection in a list, the application may need to react. JSF user interface components signal user actions by firing an event handled by application code that has registered itself to be notified of the event. It's a model borrowed from traditional GUI frameworks, making it easy to develop and maintain the code for each specific user action in a separate code module. You can even use multiple event handling modules for different aspects of the processing, such as one that logs the action and another that acts on it.
On the surface, the JSF model looks the same as the event model used for standalone applications, but there's a twist: with JSF, the user actions take place in a client (e.g., a browser) that has no permanent connection to the server, so the delivery of some types of event is delayed until a new connection is established (e.g., when the user submits a form). To deal with this difference, JSF defines a strict request processing lifecycle, where events are generated and handled in different phases.
In this chapter, we first look at the event model and how it relates to the request processing lifecycle to understand what's going on. We then implement event handling for parts of the sample application.
The JSF event model is based on the event model defined by the JavaBeans specification. In this model, an event is represented by an instance of an event class. An event source object fires an event by calling an event notification method on event listener objects registered to receive the event, passing a reference to the event object as a notification method argument.
Let's look at what this means in more detail. All
JSF event classes extend the
javax.faces.event.FacesEvent class:
package javax.faces.event;
import java.util.EventObject;
import javax.faces.component.UIComponent;
...
public abstract class FacesEvent extends EventObject {
public FacesEvent(UIComponent component) {
super(component);
}
public UIComponent getComponent( ) {
return ((UIComponent) getSource( ));
}
...
}
The FacesEvent class extends
the standard Java event superclass
java.util.EventObject and has a constructor that
takes the UIComponent event source object as an
argument. It also implements a type-safe accessor method for the
event source object.
When a user clicks a button, it triggers an event represented by the
javax.faces.event.ActionEvent class:
package javax.faces.event;
import javax.faces.component.UIComponent;
public class ActionEvent extends FacesEvent {
public ActionEvent(UIComponent component) {
super(component);
}
...
}
Other events are represented by similar concrete subclasses, such as
the javax.faces.event.ValueChangeEvent, which
signals a value change.
Along with the event classes, there are listener interfaces declaring
the methods that the event source calls to notify listeners of the
event. A listener interface can contain methods for many related
events, but for the JSF component events, there's a
separate interface per event. Here's the
javax.faces.event.ActionListener interface:
package javax.faces.event;
import javax.faces.component.UIComponent;
public interface ActionListener extends FacesListener {
public void processAction(ActionEvent event)
throws AbortProcessingException;
}
The ActionListener interface
extends the
javax.faces.event.FacesListener interface and
defines one method, taking an ActionEvent instance
as the single argument.
Classes that want to be informed about events are called
event listeners. They declare which events they
are interested in by implementing the corresponding
listener interfaces. Hence,
an event listener that wants to deal with the
ActionEvent fired by a command component declares
its intent like this:
package com.mycompany.expense.ui;
import javax.faces.event.ActionListener;
public class ReportHandler implements ActionListener {
...
public void processAction(ActionEvent e)
throws AbortProcessingException {
...
}
}
To prevent other listeners from seeing an event, all JSF
event-processing methods can throw a
javax.faces.event.AbortProcessingException. This
is rarely needed, but can come in handy when serious problems occur
while processing the event. If the event notification method throws
this exception, JSF stops the event processing immediately.
Event source classes, like UICommand, declare
the type of events they can fire by
providing methods for registering and deregistering the corresponding
event listeners:
public void addActionListener(ActionListener listener) {
addFacesListener(listener);
}
public void removeActionListener(ActionListener listener) {
removeFacesListener(listener);
}
The methods follow the JavaBeans conventions: the method names are
made from the words add and
remove followed by the listener interface name,
and both methods take an instance of the listener as the single
argument.
The addFacesListener() and
removeFacesListener() methods called by the
registration and deregistration methods are protected methods
implemented by UIComponentBase, so that the task
of maintaining the listener list doesn't have to be
implemented by all subclasses.
When a component notices that a user event has happened, it creates an instance of the corresponding event class and adds it to an event list. Eventually, JSF tells the component to fire the event, i.e., loop through the list of listeners for that event and call the event notification method on each one.
|
So far, so good. If you've programmed with GUI frameworks or in other event-driven environments, the JSF event model should look familiar. But as I mentioned in the introduction, the fact that JSF operates with a disconnected client that only occasionally communicates with the server where the application runs requires a few twists to the model.
First of all, the application may declare that the component instances should not be saved on the server between requests, due to memory usage concerns. JSF must then save enough information somewhere (e.g., in the response) to be able to reconstruct the component tree and restore all component state when it receives the next request. In other words, component instances may come and go in a JSF application, as opposed to a GUI application where they remain in memory as long as the application runs.
JSF must also deal with the fact that value changes happen in the client and that the server can't detect the changes until it receives a new request with the new values. One possible work-around is to add client-side scripting to the mix, so that value changes causes an immediate request; unfortunately, that solution can make the application feel sluggish, and it doesn't work at all if the client doesn't support scripting.
Button and link clicks are a bit easier to deal with, because they cause a new request always to be sent. Events corresponding to these user actions can, however, be classified further into events that affect only the user interface (e.g., show the next set of rows in a table or change from a brief to a detailed display) and events that must be processed by backend code (e.g., permanently save the values entered in a form or finalize a reservation request). For user interface events, the backend typically shouldn't be bothered at all. An event that involves the backend, on the other hand, must not be processed until all model properties have been updated with the new values received with the request.
JSF deals with all these concerns by defining a request processing lifecycle with well-defined phases (shown in Figure 8-1), where different activities take place and events fire in an orderly manner at the appropriate time.

Figure 8-1. Request processing lifecycle phases
The phases are described in detail in Appendix C, but here's a quick rundown:
Next, in the Apply Request Values phase, each component in the view looks for its own value in the request and saves it.
When the model properties have been populated with the latest values, event listeners may call backend code to process the new data in the Invoke Application phase.
Finally, a response to the request is sent, using the same view or a new one. This happens in the Render Response.
If the user triggered the request by clicking a button or a link, the
corresponding UICommand component discovers this
in the Apply Request Values phase when it finds a request parameter
with a name that matches its ID. It creates an
ActionEvent to represent the event, but
doesn't notify the listeners immediately, because
the rest of the components in the tree may not have picked up their
values yet. Instead it adds it to a queue by
calling its queueEvent()
method.
At the end of the Apply Request Values phase, when all components
know about their new values, it's safe to fire an
ActionEvent that affects the user interface. JSF
asks the UICommand that queued it to notify all
listeners by calling its broadcast() method. But
as you'll see soon, that's not the
whole story: sometimes this event must not be broadcast until the
Invoke Application phase.
Value change event handling happens in the Process Validations phase
in a similar fashion, by default. A component that discovers that a
value in the request is both valid and different from its previous
value queues a ValueChangeEvent. Because the user
may have changed a number of values since the previous request, it is
likely that more than one event is queued in this phase. At the end
of the phase, JSF scans the queue and calls the broadcast(
) method on the component that queued a
ValueChangeEvent.
A listener processing an event may in turn do things that cause other events to be queued, so JSF continues to scan the queue at the end of a phase until the event queue is empty before moving on to the next phase.
Separate the queuing from the firing of the event is the easy part.
Dealing with the two subcategories of ActionEvent
requires one more twist. From what I've said so far,
it looks like an ActionEvent is always processed
at the end of the Apply Request Values phase, but
that's only appropriate for user interface events.
As you may recall, an ActionEvent that requires
backend processing must not be handled until the model properties are
updated, i.e., after the Update Model Values phase at the earliest.
Let's look at some real examples of action handling using the report entry form in the sample application. We start with events that invoke backend logic in this section and look at user interface events in the next.
As you may recall, the report entry form has three fields for entering a date, an expense type and an amount for a report entry, and an Add button. Figure 8-2 shows the form produced by the version of the page we use in this section.

Figure 8-2. The report entry form area with a few entries
The Add button is a typical example of the most common type of event handling, namely a button that fires an event that requires processing by the application backend. When the user clicks this button, a new entry with the values entered in the fields is added to the current report.
Example 8-1 shows a version of the JSP page where the Add button is bound to an event handling method that invokes the backend code for adding the entry.
Example 8-1. Entry form area JSP page with an Add button action reference (expense/stage2/entryFormArea.jsp)
<%@ page contentType="text/html" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view>
<h:form>
Title:
<h:inputText id="title" size="30" required="true"
value="#{reportHandler.currentReport.title}" />
<h:message for="title" />
<br>
Entry:
<br>
Date:
<h:inputText id="date" size="8" required="true"
value="#{entryHandler.currentEntry.date}">
<f:convertDateTime dateStyle="short" />
</h:inputText>
<h:message for="date" />
<br>
Type:
<h:selectOneMenu id="type" required="true"
value="#{entryHandler.currentEntry.type}">
<f:selectItems value="#{entryHandler.expenseTypeChoices}"/>
</h:selectOneMenu>
<h:message for="type" />
<br>
Amount:
<h:inputText id="amount" size="8" required="true"
value="#{entryHandler.currentEntry.amount}">
<f:convertNumber pattern="#,##0.00" />
<f:validateDoubleRange minimum="1"/>
</h:inputText>
<h:message for="amount" />
<br>
<h:commandButton value="Add"
disabled="#{reportHandler.addEntryDisabled}"
action="#{entryHandler.add}" />
</h:form>
<h:messages globalOnly="true" />
<%-- Loop to verify that it works --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<ol>
<c:forEach items="${reportHandler.currentReportEntries}" var="e">
<li>Date: ${e.date}, Type: ${e.type}, Amount: ${e.amount}</li>
</c:forEach>
</ol>
</f:view>
The only real difference compared to the version of the page we used
in Chapter 7 is the action
attribute for the Add button
<h:commandButton> action element. Of less
interest at this point is the JSTL
<c:forEach> action that lists all entries in
the current report at the end of the page. I added the loop just to
verify that the Add button's event handler really
does what it's supposed to do. This loop gets the
report entries from the report handler we looked at in Chapter 6. I'm not showing you the
details here, but I promise to return to them when we replace the
plain list with a real report entries table in Chapter 10.
The action attribute contains a
method binding expression. The method binding
expression syntax is similar to that of a value binding expression.
It's a subset of the JSF EL that evaluates to a bean
method with a certain signature. In Example 8-1, the
method binding expression points to a method named
add in the EntryHandler
instance available through the entryHandler
variable. Before we look at the add() method,
let's see how it relates to the event listeners and
the request processing lifecycle we discussed earlier.
|
The UICommand component supports
method bindings for two types
of methods: action methods and action
listener methods. Either type can be used to process an
ActionEvent, but the action method type is the
most commonly used. An action method has no parameters and returns a
String value called the action
outcome. The outcome is typically
"success" if everything went as
planned, and "error" or
"failure" if there was a problem.
The outcome value can affect which view is displayed next, which we
get back to in Chapter 9. In a JSP page, you
can use the action attribute with a method binding
expression to bind a UICommand component to an
action method, as shown in Example 8-1.
As you know from the earlier discussions, an
ActionEvent is handled by a listener that
implements the ActionListener interface, so there
must be something else going on to invoke the action method. The
missing piece is called the default
ActionListener. This is a listener that is
provided by the JSF implementation
[1]
to make it easier to handle events. When a
UICommand component is asked to fire an
ActionEvent, it first notifies all regular
listeners attached to the component, if any. Then it checks if
it's been configured with an action method binding.
If so, it creates an instance of the default
ActionListener and asks it to handle the event.
The default ActionListener evaluates the action
method binding and invokes the method. As hinted at earlier, the
default ActionListener uses the action outcome
value to decide which view to use for the response. All of this
happens behind the scenes, so you just have to write the action
method and bind it to the component.
Example 8-2 shows the add()
action method bound to the Add button in Example 8-1.
Example 8-2. The EntryHandler add action method
package com.mycompany.expense;
...
public class EntryHandler {
private ReportHandler reportHandler;
private ReportEntry currentEntry;
...
public String add( ) {
return reportHandler.addEntry(currentEntry);
}
...
}
The add() method is very simple: it just relays
the call to the ReportHandler, which is in charge
of accessing the current report, passing on the current entry as an
argument to the addEntry() method.
So the meat is in the
ReportHandleraddEntry()
method, shown in Example 8-3.
Example 8-3. The ReportHandler addEntry( ) method
package com.mycompany.expense;
import javax.faces.application.Application;
import javax.faces.application.MessageResources;
import javax.faces.application.Message;
import javax.faces.context.FacesContext;
...
public class ReportHandler {
private ReportRegistry registry;
private Rules rules;
private Report currentReport;
private String currentUser;
private boolean isManager;
...
public String addEntry(ReportEntry entry) {
try {
refreshCache( );
}
catch (RegistryException e) {
addMessage("registry_error", e.getMessage( ));
return "error";
}
if (!rules.canEdit(currentUser, isManager, currentReport)) {
addMessage("report_no_edit_access", null);
return "error";
}
String outcome = "success";
currentReport.addEntry(entry);
try {
saveReport( );
}
catch (RegistryException e) {
currentReport.removeEntry(entry.getId( ));
addMessage("registry_error", e.getMessage( ));
outcome = "error";
}
return outcome;
}
The addEntry() method first calls
a method named refreshCache(
) to ensure it has the most recent copy of the current
report, in case some other user has modified it:
private void refreshCache( ) throws RegistryException {
if (!isReportNew( )) {
setCurrentReport(registry.getReport(currentReport.getId( )));
}
}
private void setCurrentReport(Report report) {
currentReport = report;
entriesModel = null;
}
If the refresh fails, the addEntry() method adds
an error message to the JSF message list and returns
"error" as the outcome. Next, it
checks that the current user is allowed to edit the current report by
calling the canEdit() method implemented by the
Rules class we looked at in Chapter 6. If the answer is
"no", it adds another error message
to the list.
If the user has write access, the entry is added to the current
report by calling the addEntry() method on the
current Report instance and the updated report is
saved in the registry by calling saveReport():
private void saveReport( ) throws RegistryException {
if (isReportNew( )) {
currentReport.setStatus(Report.STATUS_OPEN);
registry.addReport(currentReport);
}
else {
registry.updateReport(currentReport);
}
entriesModel = null;
}
The saveReport() method adds the report to the
registry if it's new (i.e., it
hasn't been saved yet) or updates the registry
version of the report if it's already in the
registry.
Saving the report may fail, and if it does, the addEntry(
) method restores the local copy of the current report to
its previous state by removing the entry it just added, and adds an
error message to the list as before.
All error messages are added to the JSF message list by the
addMessage() method:
private void addMessage(String messageKey, Object param) {
FacesContext context = FacesContext.getCurrentInstance( );
Application application = context.getApplication( );
String messageBundleName = application.getMessageBundle( );
Locale locale = context.getViewRoot( ).getLocale( );
ResourceBundle rb =
ResourceBundle.getBundle(messageBundleName, locale);
String msgPattern = rb.getString(messageKey);
String msg = msgPattern;
if (param != null) {
Object[] params = {param};
msg = MessageFormat.format(msgPattern, params);
}
FacesMessage facesMsg =
new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg);
context.addMessage(null, facesMsg);
}
The only difference compared to the code we used in Chapter 7 is that instead of throwing an exception
that contains the FacesMessage instance, and
relying on the component to add it to the message queue, I use the
FacesContextaddMessage()
method to queue the message myself. The first addMessage(
) argument takes a component's client ID.
This must be used for a message that is associated with a specific
component instance. Here I use a null value
instead, which means that the message is an application-level
message. If you look at Example 8-1,
there's an <h:messages>
action element at the bottom of the page, just before the entries
loop, with a globalOnly attribute set to
true. Configured this way, the action displays
only application-level error messages. The component-specific
messages are displayed by <h:message>
actions for each component, just as before.
In most cases, you don't need
access to the actual
ActionEvent in order to do the right thing,
because each component can be bound to a separate action method that
implements component-specific behavior. The exception is when one
method needs to handle events fired by multiple components, but that
turns out to be a very rare case. In fact, there's
nothing in the sample application that requires this, and
I'm hard pressed to come up with any good use case
for it. Even so, the JSF specification supports two alternatives to
the action method that provides access to the
ActionEvent, in case you do need it.
The first alternative is the action listener method binding, defined
by the actionListener attribute in a JSP page:
<h:commandButton value="Add"
actionListener="#{entryHandler.handleAdd}" />
An action listener method has the exact same signature as the
ActionListenerprocessAction()
method we looked at earlier; it takes an
ActionEvent argument and has a
void return type:
public void processAction(ActionEvent e) throws AbortProcessingException {
UIComponent myCommand = e.getComponent( );
// Do whatever you need to do depending on which component fired
// the event
...
}
Any object available as a JSF EL expression variable can implement
the action listener method, but you can bind a component only to one
action listener method. If you need to use more than one listener to
handle the same event—maybe one that logs the event and another
that really acts on it—you can attach as many
ActionListener instances as you want to the
component. The JSF core tag library includes an action element that
makes this easy to do in a JSP page:
<h:commandButton value="Add">
<f:actionListener type="com.mycompany.LogEventListener" />
<f:actionListener type="com.mycompany.HandleAddListener" />
</h:commandButton>
The <f:actionListener> action creates an
instance of the class specified by the type attribute and calls the
addActionListener() method on the
UICommand component.
So far, I've told you how to bind a
component to an event handling method,
but I haven't let you in yet on the secret of how an
ActionEvent can be made to fire either at the end
of the Apply Request Value phase or in the Invoke Application phase.
There are different ways to solve this problem, but the specification
group decided to go with an approach in which the source component
decides when the event should be processed, with a little bit of help
from the application developer. Here's how it works.
The FacesEvent class—which all JSF events
must extend either directly or through one of the standard
subclasses, such as ActionEvent—defines a
property named phaseId:
package javax.faces.event;
import java.util.EventObject;
...
public abstract class FacesEvent extends EventObject {
private PhaseId phaseId = PhaseId.ANY_PHASE;
public PhaseId getPhaseId( ) {
return phaseId;
}
public void setPhaseId(PhaseId phaseId) {
this.phaseId = phaseId;
}
...
}
The phaseId property data type is
PhaseId, which is a type-safe enumeration
containing one value per request processing lifecycle phase:
PhaseId.APPLY_REQUEST_VALUES,
PhaseId.PROCESS_VALIDATIONS,
PhaseId.UPDATE_MODEL_VALUES,
PhaseId.INVOKE_APPLICATION,
PhaseId.RENDER_RESPONSE, or
PhaseId.ANY_PHASE. The
PhaseId.ANY_PHASE value means
"process the event in the phase where it was
queued," and it's the default value
for the phaseId property.
So even though UICommand always queues an
ActionEvent in the Apply Request Values phase, it
sets the phaseId to
PhaseId.INVOKE_APPLICATION to delay the event
handling unless you tell it that you want to process it immediately.
You do so through a UICommand property called
immediate. If immediate is
true, the phaseId is left
unchanged so the event is processed in the Apply Request Values
phase. The default ActionListener also tells JSF
to proceed directly to the Render Response phase after invoking the
action method. The default value for immediate is
false, because the ActionEvent
is typically used to invoke backend code.
Most of the logic for keeping track of the phase in which an event is
to be processed is implemented by the UIViewRoot
component that sits at the top of the component tree. At the end of
each phase, UIViewRoot goes through the event
queue and calls the broadcast() method on all
event source components. It starts with all events with
phaseId set to
PhaseId.ANY_PHASE and then the events queued for
the current phase. It continues until there are no more events with
these phaseId values in the queue, so if
processing one event leads to a new event, the new event is also
processed, as long as it's for a matching phase.
[1] As is true for pretty much everything in JSF, an application can define a customized default
ActionListenerif needed.
Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.
View catalog information for JavaServer Faces
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.