Read also:
JSF and Observer design pattern - part I (plain code)
JSF and Observer design pattern - part II (Java EE)
As you will see further, the observer pattern is used by JSF in the UI components. JSF comes with three kinds of built-in events: ActionEvent, ValueChangeEvent and BehaviorEvent. Further, we will "dissect" each of these events implementation. Notice that this time the observers are called listeners. Moreover, the subject can be a button(link), an input component or Behavior component.
JSF and Observer design pattern - part I (plain code)
JSF and Observer design pattern - part II (Java EE)
As you will see further, the observer pattern is used by JSF in the UI components. JSF comes with three kinds of built-in events: ActionEvent, ValueChangeEvent and BehaviorEvent. Further, we will "dissect" each of these events implementation. Notice that this time the observers are called listeners. Moreover, the subject can be a button(link), an input component or Behavior component.
Let's start with the JSF event model.
So, whenever we interact with the application by clicking a button/link, make selections in a list, etc, we trigger an event and JSF must handle it - this is known asevent handling (event handler). Programmatically speaking, each event is an instance of a class, which is known as event class. JSF recognize an event class if it extends the javax.faces.event.FacesEvent class, which is the base class for user interface and application events that can be fired by UIComponents. This means that an event class is always related to an UIComponent, and the UIComponent represents the event source or event source object. The FacesEvent class extends the standard Java event superclass java.util.EventObject, which represents the root class from which all event state objects shall be derived (the JSF event model is based on the event model defined by the JavaBeans specification - also followed by Java Swing components). Among other things, the FacesEvent provides a constructor that takes the UIComponent event source object (UIComponent) as an argument, and implements a type-safe method for returning the event source object - they have been highlighted in red below:
// source code from Mojarra 2.2.9
package javax.faces.event;
import java.util.EventObject;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
public abstract class FacesEvent extends EventObject {
public FacesEvent(UIComponent component) {
super(component);
}
public UIComponent getComponent() {
return ((UIComponent) getSource());
}
private PhaseId phaseId = PhaseId.ANY_PHASE;
public PhaseId getPhaseId() {
return phaseId;
}
public void setPhaseId(PhaseId phaseId) {
if (null == phaseId) {
throw new IllegalArgumentException();
}
this.phaseId = phaseId;
}
public void queue() {
getComponent().queueEvent(this);
}
public abstract boolean isAppropriateListener(FacesListener listener);
public abstract void processListener(FacesListener listener);
}
JSF comes by default with three classes that extends the FacesEvent (ActionEvent, ValueChangeEvent and BehaviorEvent).
Beside these "exposed" event classes, JSF uses some private event classes also, like the IndexedEvent defined in UIRepeat, but these aren't in today topic. So, let's focus on these three!
ActionEvent - Represents the activation of a user interface component (such as a UICommand). For example, if you click a button (or a link), you will trigger an event represented by the javax.faces.event.ActionEvent class:
// source code from Mojarra 2.2.9
package javax.faces.event;
import javax.faces.component.UIComponent;
public class ActionEvent extends FacesEvent {
public ActionEvent(UIComponent component) {
super(component);
}
public boolean isAppropriateListener(FacesListener listener) {
return (listener instanceof ActionListener);
}
public void processListener(FacesListener listener) {
((ActionListener) listener).processAction(this);
}
}
In this case, the event is created in the Apply Request Values phase (second phase in JSF lifecycle). When JSF fits the request parameters map values with the components values, it finds a request parameter whose clientId correspond to the clicked button (link). See the below helper figure:
At this moment, JSF creates an instance of the ActionEvent (in Mojarra, for a button, this is happening in ButtonRenderer#decode(), while for a link, inCommandLinkRenderer#decode() method), but it doesn't process it immediately. Actually, it will queue the event, because is possible that not all components in the tree have their values attached yet.
// source code from Mojarra 2.2.9 - method ButtonRenderer#decode()
@Override
public void decode(FacesContext context, UIComponent component) {
rendererParamsNotNull(context, component);
if (!shouldDecode(component)) {
return;
}
String clientId = decodeBehaviors(context, component);
if (wasClicked(context, component, clientId) && !isReset(component)) {
component.queueEvent(new ActionEvent(component));
...
}
}
The queueEvent() invoked above belongs to the UICommand. Below, you can see the relevant code from the UICommand class:
// source code from Mojarra 2.2.9 - method UIICommand#queueEvent()
public void queueEvent(FacesEvent e) {
UIComponent c = e.getComponent();
if (e instanceof ActionEvent && c instanceof ActionSource) {
if (((ActionSource) c).isImmediate()) {
e.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
} else {
e.setPhaseId(PhaseId.INVOKE_APPLICATION);
}
}
super.queueEvent(e);
}
The above method intercepts the invocation of UICommandBase#queueEvent() (see super.queueEvent(e); which refers to UICommandBase#queueEvent() method, and indirectly, to the UIViewRoot#queueEvent() method), and decide, based on the immediate attribute value, if the event will be processed in the Apply Request Values (current phase) or it must wait until Invoke Application phase (fifth phase in JSF lifecycle). The idea is to wait until the data model properties are updated - after the Update Model Values phase. Setting the phase when this event will be processed is accomplished via FacesEvent#setPhaseId() - see the blue highlighted methods inFacesEvent class listed earlier. Moreover, notice that, at this point, only events of type ActionEvent whose sources are of type ActionSource benefit from this treatment. The ActionSource (and ActionSource2) are interface that may be implemented by any concrete UIComponent that wishes to be a source of ActionEvents(e.g. UICommand).
At the end of Apply Request Values phase, JSF will scan the queue to process events that have been "prepared" for this phase (if exist such events). This takes place in,UIViewRoot#processDecodes() method. The events "prepared" for Invoke Application phase, will be process later, via UIViewRoot#processApplication() method. Both of these methods uses the UIViewRoot#broadcastEvents().
BehaviorEvent - abstract class that represents event that can be generated from component Behavior. For example, the AjaxBehaviorEvent represents the component behavior specific to Ajax (you should know that <f:ajax> register an AjaxBehavior instance on one or more UIComponents implementing theClientBehaviorHolder interface). The source code for BehaviorEvent is:
// source code from Mojarra 2.2.9
package javax.faces.event;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.Behavior;
public abstract class BehaviorEvent extends FacesEvent {
private final Behavior behavior;
public BehaviorEvent(UIComponent component, Behavior behavior) {
super(component);
if (null == behavior) {
throw new IllegalArgumentException("Behavior agrument cannot be null");
}
this.behavior = behavior;
}
public Behavior getBehavior() {
return behavior;
}
}
And, the AjaxBehaviorEvent source code is:
// source code from Mojarra 2.2.9
package javax.faces.event;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.Behavior;
public class AjaxBehaviorEvent extends BehaviorEvent {
public AjaxBehaviorEvent(UIComponent component, Behavior behavior) {
super(component, behavior);
}
public boolean isAppropriateListener(FacesListener listener) {
return (listener instanceof AjaxBehaviorListener);
}
public void processListener(FacesListener listener) {
((AjaxBehaviorListener) listener).processAjaxBehavior(this);
}
}
In this case, the event is created in the Apply Request Values phase (second phase in JSF lifecycle). JSF creates an instance of AjaxBehaviorEvent inAjaxBehaviorRenderer#decode() method (as a quick tip, not related to events: this class is responsible for generating the client side script for AJAX behavior,mojarra.ab(...);). The decode() method is helped by two private methods, createEvent() and isImmediate().
Note AjaxBehaviorRenderer renders Ajax behavior for a component.
All three are listed below:
// source code from Mojarra 2.2.9 - method AjaxBehaviorRenderer#decode()
@Override
public void decode(FacesContext context, UIComponent component, ClientBehavior behavior) {
if (null == context || null == component || null == behavior) {
throw new NullPointerException();
}
if (!(behavior instanceof AjaxBehavior)) {
throw new IllegalArgumentException(
"Instance of javax.faces.component.behavior.AjaxBehavior required: " + behavior);
}
AjaxBehavior ajaxBehavior = (AjaxBehavior)behavior;
if (ajaxBehavior.isDisabled()) {
return;
}
component.queueEvent(createEvent(component, ajaxBehavior));
if (logger.isLoggable(Level.FINE)) {
logger.fine("This command resulted in form submission " + " AjaxBehaviorEvent queued.");
logger.log(Level.FINE, "End decoding component {0}", component.getId());
}
}
// source code from Mojarra 2.2.9 - method AjaxBehaviorRenderer#createEvent()
private static AjaxBehaviorEvent createEvent(UIComponent component,
AjaxBehavior ajaxBehavior) {
AjaxBehaviorEvent event = new AjaxBehaviorEvent(component, ajaxBehavior);
PhaseId phaseId = isImmediate(component, ajaxBehavior) ?
PhaseId.APPLY_REQUEST_VALUES :
PhaseId.INVOKE_APPLICATION;
event.setPhaseId(phaseId);
return event;
}
// source code from Mojarra 2.2.9 - method AjaxBehaviorRenderer#isImmediate()
private static boolean isImmediate(UIComponent component,
AjaxBehavior ajaxBehavior) {
boolean immediate = false;
if (ajaxBehavior.isImmediateSet()) {
immediate = ajaxBehavior.isImmediate();
} else if (component instanceof EditableValueHolder) {
immediate = ((EditableValueHolder)component).isImmediate();
} else if (component instanceof ActionSource) {
immediate = ((ActionSource)component).isImmediate();
}
return immediate;
}
An AJAX behavior is attached to an UIComponent that implements the ClientBehaviorHolder interface, represented in AjaxBehaviorRenderer#decode() as theUIComponent component argument. For example, if we attach an AJAX behavior (via <f:ajax>) to an UICommand then, when an AJAX request is fired (submit behavior), the clientId of this component (UICommand) is the value of the request parameter, javax.faces.source. This component is the event source.
The event is queued with respect to the value of the immediate attribute. If the immediate attribute is not specified on the behavior then it is inherited from its parent (the event source, which can be an instance of EditableValueHolder (e.g. UIInput) or an instance of ActionSource (e.g. UICommand)). Depending on immediate value, the queued event will be process in Apply Request Values phase or in Invoke Application phase (default phase, because immediate is false by default).
Note JSF distinguish between an AJAX submitting behavior (<f:ajax> - behavior action event) and a jsf.ajax.request() (not a Behavior-related request) by inspecting the presence of javax.faces.behavior.event request parameter in the request parameters map. This is not present in case of jsf.ajax.request().
So, when <f:ajax> is nested in <h:commandButton>/<h:commandLink>, JSF will queue two events for that button/link, an AjaxBehaviorEvent instance and anActionEvent instance. In Apply Request Values phase, before queuing any ActionEvent, the ButtonRenderer#decode()/CommandLinkRenderer#decode() is responsible to decode Behaviors, if any match the behavior source/event. For this, in Mojarra, it invokes the HtmlBasicRenderer#decodeBehaviors() method:
// source code from Mojarra 2.2.9 - method ButtonRenderer#decode()
@Override
public void decode(FacesContext context, UIComponent component) {
...
String clientId = decodeBehaviors(context, component);
if (wasClicked(context, component, clientId) && !isReset(component)) {
component.queueEvent(new ActionEvent(component));
...
}
}
Further, from HtmlBasicRenderer#decodeBehaviors() method, the flow reaches the AjaxBehaviorRenderer#decode() method, and the AjaxBehaviorEvent is created and queued for the proper phase (after it determines the phase, it simply calls UICommand#queueEvent() with an event of type AjaxBehaviorEvent - this will finally reach UIComponentBase#queueEvent() and UIViewRoot#queueEvent()). When the flow comes back inButtonRenderer#decode()/CommandLinkRenderer#decode(), the corresponding ActionEvent is created and queued, via the sameUICommand#queueEvent()/UIViewRoot#queueEvent().
Summary figure (pretty obvious, but good to point is the fact that when the AJAX behavior is attached to an EditableValueHolder, the UICommand#queueEvent() is not invoked!):
When the AJAX behavior is attached to an EditableValueHolder, we usually use an explicit listener. That listener will be invoked in Apply Request Values phase or Invoke Application phase, depending for which phase was the event queued.
ValueChangeEvent - Signals a value change. The source code of ValueChangeEvent is:
package javax.faces.event;
import javax.faces.component.UIComponent;
public class ValueChangeEvent extends FacesEvent {
public ValueChangeEvent(UIComponent component,
Object oldValue, Object newValue) {
super(component);
this.oldValue = oldValue;
this.newValue = newValue;
}
private Object oldValue = null;
public Object getOldValue() {
return (this.oldValue);
}
private Object newValue = null;
public Object getNewValue() {
return (this.newValue);
}
public boolean isAppropriateListener(FacesListener listener) {
return (listener instanceof ValueChangeListener);
}
public void processListener(FacesListener listener) {
((ValueChangeListener) listener).processValueChange(this);
}
}
In this case, the event is created in the Process Validations phase. If the previous value is different from the current new valid value, then a ValueChangeEvent is queued, via queueEvent() method, which gets the event class instance as argument. For example, the UIInput reveals this code in the validate() method:
// source code from Mojarra 2.2.9 - method UIInput#validate()
public void validate(FacesContext context) {
...
if (isValid()) {
Object previous = getValue();
setValue(newValue);
setSubmittedValue(null);
if (compareValues(previous, newValue)) {
queueEvent(new ValueChangeEvent(this, previous, newValue));
}
}
}
Most probably that more than one ValueChangeEvent is queued in this phase, because the user may have changed multiple values. The UIInput class doesn't have aqueueEvent() method; the one invoked above is UIComponentBase#queueEvent() method.
At the end of the Process Validations phase, after all ValueChangeEvents have been queued, JSF will try to process the ValueChangeEvents by scanning the queue. This is happening in UIViewRoot#processValidators()/UIViewRoot#broadcastEvents() methods.
But, if the event source (e.g. UIInput) has the immediate attribute set to true, then the event will be created in the Apply Request Values phase, and it will be process at the end of this phase, via UIViewRoot#processDecodes()/UIViewRoot#broadcastEvents() methods.
But, why the event handling implies event classes instances to be queued ? Why they are not processed immediately ? Well, let's suppose that we click on a button and we submit a bulk of data. Further, in the Apply Request Values phase, JSF will create the needed ActionEvent for our button, and will process it immediately. If this event affects only the user interface, then this is perfect, but let's suppose further that our event was meant for saving that bulk of data into a database. Now, we have a problem, because this event should be processed when all model properties have been updated with the values from our bulk of data, otherwise we will not save the latest submitted values. We are in Apply Request Values phase, while the model is updated in Update Model Values phase. So, the earliest moment when the action should be processed is in the Invoke Application phase, after Update Model phase was executed.
Until now, we discussed only about creating and queuing events. Further, we will discuss about listening events, which implies special interfaces, named listeners, that declares the methods that the event source should invoke for notifying listeners of the event. JSF provides a generic such interface, named FacesListener (extension ofjava.util.EventListener). For each type of event, JSF provides a sub-interface of FacesListener interface. In the context of our discussion, we are especially interested in ActionListener (listener interface for receiving ActionEvents), BehaviorListener (generic base interface for event listeners for various types ofBehaviorEvents), AjaxBehaviorListener (listener for one or more kinds of BehaviorEvents) and ValueChangeListener (listener interface for receivingValueChangeEvents).
Note ActionListener is the most "famous" of these interfaces, and starting with JSF 2.2, it even has a wrapper, named ActionListenerWrapper (simple implementation of ActionListener that can be extended).
In the below source codes, you can identify the methods that should be implemented for each of these interfaces:
// source code from Mojarra 2.2.9 - FacesListener
package javax.faces.event;
import java.util.EventListener;
public interface FacesListener extends EventListener {}
// source code from Mojarra 2.2.9 - BehaviorListener
package javax.faces.event;
public interface BehaviorListener extends FacesListener {}
// source code from Mojarra 2.2.9 - ActionListener
package javax.faces.event;
import javax.faces.component.UIComponent;
public interface ActionListener extends FacesListener {
public static final String TO_FLOW_DOCUMENT_ID_ATTR_NAME = "to-flow-document-id";
public void processAction(ActionEvent event)
throws AbortProcessingException;
}
// source code from Mojarra 2.2.9 - ValueChangeListener
package javax.faces.event;
import javax.faces.component.UIComponent;
public interface ValueChangeListener extends FacesListener {
public void processValueChange(ValueChangeEvent event)
throws AbortProcessingException;
}
// source code from Mojarra 2.2.9 - AjaxBehaviorListener
package javax.faces.event;
public interface AjaxBehaviorListener extends BehaviorListener {
public void processAjaxBehavior(AjaxBehaviorEvent event)
throws AbortProcessingException;
}
Methods defined in these interfaces are further implemented by classes that wants to be informed about specific events. These classes are called event listeners.
They declare which events they are interested in by implementing the corresponding listener interfaces. For example, an event listener that wants to deal with theActionEvent will implement ActionListener, while an event listener that want to deal with ValueChangeEvent will implement ValueChangeListener.
For example:
import javax.faces.event.ActionListener;
public class MyHandler implements ActionListener {
...
public void processAction(ActionEvent e)
throws AbortProcessingException {
...
}
}
Typically, you will use <f:actionListener>, and indicate the MyHandler class via the type attribute. Or, you may be more familiar with the case when the listener is indicated as a MethodExpression via the actionListener attribute (for UICommands - it supports method bindings for two types of methods: action methods and action listener methods, and either of it can be used to process an ActionEvent, but the action method type is recommended for business logic and navigation) or listenerattribute (for <f:ajax>). Further, in managed bean, you declare a public method that takes an XxxEvent parameter, with a return type of void, or to a public method that takes no arguments with a return type of void.
Note The AbortProcessingException represents an exception that may be thrown by event listeners to terminate the processing of the current event.
Among others, one of the event source classes responsibility consist in indicating the type of event they can produce (emit). For this, event source classes defines, based on JavaBeans conventions, special methods for register/unregister event listeners. For example, UICommand defines the following two methods for register/unregister event of type, ActionEvent:
public void addActionListener(ActionListener listener) {
addFacesListener(listener);
}
public void removeActionListener(ActionListener listener) {
removeFacesListener(listener);
}
For example, if you use the <f:actionListener> with <h:commandButton> then the class indicated via type attribute will be instantiated, and the instance will be passed to the above addActionListener() method. Obviously, that class will implement ActionListener.
So, now we have the event source and the event listener. At the right moment (phase) event source may process the event by notifying listeners of the event. The process of notification is known as broadcasting (we can say that the component (event source) fires the event), and takes places in UIViewRoot#broadcastEvents()method. After each JSF phase, this method determines the events that should be broadcasted, and invoke the proper event source broadcast() method. This is a massive method, that begins with the events with phaseId set to PhaseId.ANY_PHASE, and continue with the events queued for the current phase. The process keep going until all the events with these phaseIds have been broadcasted. Sometimes, processing one events may cause other events to occur. In this case, if the occurred event matches a phaseId, it is also broadcasted.
// source code from Mojarra 2.2.9 - UIViewRoot
public void broadcastEvents(FacesContext context, PhaseId phaseId) {
...
List<FacesEvent> eventsForPhaseId =
events.get(PhaseId.ANY_PHASE.getOrdinal());
...
// broadcast the ANY_PHASE events first
if (null != eventsForPhaseId) {
while (!eventsForPhaseId.isEmpty()) {
...
UIComponent source = event.getComponent();
...
source.broadcast(event);
...
}
}
// then broadcast the events for this phase.
if (null != (eventsForPhaseId = events.get(phaseId.getOrdinal()))) {
while (!eventsForPhaseId.isEmpty()) {
...
UIComponent source = event.getComponent();
...
source.broadcast(event);
...
}
}
// true if we have any more ANY_PHASE events
...
}
For example, the UICommand#broadcast() method is responsible to broadcast the ActionEvents:
// source code from Mojarra 2.2.9 - UICommand
public void broadcast(FacesEvent event) throws AbortProcessingException {
// Perform standard superclass processing (including calling our ActionListeners)
super.broadcast(event);
if (event instanceof ActionEvent) {
FacesContext context = getFacesContext();
// Notify the specified action listener method (if any)
MethodBinding mb = getActionListener();
if (mb != null) {
mb.invoke(context, new Object[] { event });
}
// Invoke the default ActionListener
ActionListener listener = context.getApplication().getActionListener();
if (listener != null) {
listener.processAction((ActionEvent) event);
}
}
}
Check the above code and notice the comment, // Invoke the default ActionListener. What is the default ActionListener ? Well, the default ActionListener is an important wheel in the JSF event handling mechanism. When an UICommand must fire an ActionEvent, if follows two main steps:
· Notify the listeners attached to this component (of course, if there are any).
· Invoke the default ActionListener and delegate the event handling to it. The default ActionListener (in Mojarra,com.sun.faces.application.ActionListenerImpl) invokes the specified application action method, and uses the logical outcome value to invoke the default navigation handler mechanism to determine which view should be displayed next.
Niciun comentariu :
Trimiteți un comentariu