The PostRestoreStateEvent
occurs when the source of this event instance is in a tree that has just had
its state restored.
Unlike the rest of JSF component system events, the PostRestoreStateEvent
is "active" out-of-the-box for all JSF UI components. Even if we don't
"see" it or "feel" its effect, by default, the PostRestoreStateEvent
is there at each request (initial (by default, at initial request only view
root will fire this event) or postback) and is emitted (fired) for each UIComponent
in the component tree after Restore View phase. This event is emitted even if the
application had explicitly set the tree in the context (advanced case). In addition, by
default, every UI component acts as a listener for PostRestoreStateEvent event
emitted by instances of that component. So, pay attention that this is a
component specific event (per-component action), but it acts as a flag that
signals that the entire component tree was restored. Practically, this is the
earliest moment when you can perform something after the component tree was
restored.
System
events that are dedicated to UI components (known as component system events) extends
the ComponentSystemEvent
class, exactly as PostRestoreStateEvent do:
// javax.faces.event.PostRestoreStateEvent
source code
public class
PostRestoreStateEvent extends ComponentSystemEvent {
static final long serialVersionUID =
-1007196479122154347L;
public PostRestoreStateEvent(UIComponent
component) {
super(component);
}
public void setComponent(UIComponent
newComponent) {
this.source = newComponent;
}
}
Every
component (including custom components) extends the JSF UIComponent class,
which is the base class for all UI components in JSF. Since UIComponent
implements the ComponentSystemEventListener
it will listen for system events emitted by components. The emitted events are
listen via ComponentSystemEventListener#processEvent()
method. By default, UIComponent provides the following implementation
(notice that it explicitly treat only
the PostRestoreStateEvent
event):
//javax.faces.component.UIComponent#processEvent()
source code
public void
processEvent(ComponentSystemEvent event) throws AbortProcessingException {
if (event instanceof PostRestoreStateEvent) {
assert(this == event.getComponent());
// if this component has a component value
reference expression,
//
make sure to populate the ValueExpression for it.
ValueExpression valueExpression;
if (null != (valueExpression =
this.getValueExpression("binding"))) {
valueExpression.setValue(FacesContext.getCurrentInstance().getELContext(),
this);
}
isCompositeComponent = null;
}
}
So, the
default implementation does exactly what the JSF API documentation says: If the argument event is an instance of PostRestoreStateEvent, call this.getValueExpression(java.lang.String) passing the literal string “binding”,
without the quotes, as the argument. If the result is non-null, set the value of the ValueExpression to be this.
Usually,
when we write custom components that listen for component system events, we will
override the processEvent()
method. But, in a dummy scenario, if we have a custom component that registers
as a listener for certain component system events but it doesn't override this
method, then, when the listen events are emitted, they will "hit" the
UIComponent#processEvent()
method, and nothing will happen. This is why UIComponent#processEvent() explicitly isolates the PostRestoreStateEvent.
For example, let's suppose the below custom component:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
public class
TomComponent extends UIComponentBase {
...
}
This will
not cause any error, but it will not work as expected because the PostAddToViewEvent
will "hit" the UIComponent#processEvent(). So, pay attention
to provide the TomComponent#processEvent()
(JSF novices usually forget about this).
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
@ListenerFor(systemEventClass
= PostAddToViewEvent.class)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener { //
optional implementation
...
@Override // override the
UIComponent#processEvent()
public void processEvent(ComponentSystemEvent
event) throws AbortProcessingException {
System.out.println("EVENT EMITTED: "
+ event);
}
...
}
In the above
example, at each postback, will take into account the PostRestoreStateEvent.
You will see something like this:
EVENT
EMITTED: javax.faces.event.PostAddToViewEvent[source=custom.TomComponent@53c8d26e]
EVENT
EMITTED:
javax.faces.event.PostRestoreStateEvent[source=custom.TomComponent@53c8d26e]
Since PostRestoreStateEvent
is emitted by all UIComponents,
you have to explicitly indicate that you are interested only in PostAddToViewEvent:
@Override
public void
processEvent(ComponentSystemEvent event) throws AbortProcessingException {
if (event instanceof PostAddToViewEvent) {
System.out.println("EVENT
EMITTED: " + event);
}
}
Now, let's
focus on the moment when the PostRestoreStateEvent is published. Of course, we already know that this happens at the end of Restore View phase, but, for more details,
we have to take a closer look in the RestoreViewPhase implementation - in
this post, we will focus on Mojarra implementation, com.sun.faces.lifecycle.RestoreViewPhase.
More precisely, we have to look into the RestoreViewPhase#execute()
method. Practically, this method encapsulates the code that represents the
Restore View phase. This is a pretty big method, so we will list here only the
parts that are relevant for PostRestoreStateEvent. Mainly, we have to
cases here:
·
If an application had explicitly set the tree in
the context (advanced and rare case):
public void
execute(FacesContext facesContext) throws FacesException {
...
UIViewRoot viewRoot =
facesContext.getViewRoot();
if (viewRoot != null) {
...
deliverPostRestoreStateEvent(facesContext);
if
(!facesContext.isPostback()) {
facesContext.renderResponse();
}
...
}
...
}
·
If an application did not explicitly set the
tree in the context (obviously, most probably case):
public void
execute(FacesContext facesContext) throws FacesException {
...
try {
// Reconstitute or create the request tree
(initial and postback request are treated here)
} catch (Throwable fe) {
...
} finally {
...
deliverPostRestoreStateEvent(facesContext);
...
}
}
The deliverPostRestoreStateEvent()
represents the centerpiece of emitting PostRestoreStateEvent. This is a private
method that:
·
Publish the PostRestoreStateEvent for
the view root. If FacesContext#isProcessingEvents() is true and there are
one or more listeners for events of the type PostRestoreStateEvent, call
those listeners, passing source as the source of the event.
·
Visit the component tree and set each component
as a source of PostRestoreStateEvent.
Moreover, invoke each component processEvent() method and pass to it the PostRestoreStateEvent.
This uses the JSF Visit Tree API, as below:
// Mojarra
implementation,
// com.sun.faces.lifecycle.RestoreViewPhase#deliverPostRestoreStateEvent()
private void
deliverPostRestoreStateEvent(FacesContext facesContext) throws FacesException {
UIViewRoot root = facesContext.getViewRoot();
final PostRestoreStateEvent
postRestoreStateEvent = new PostRestoreStateEvent(root);
try {
facesContext.getAttributes().put(SKIP_ITERATION_HINT, true);
facesContext.getApplication().publishEvent(facesContext, PostRestoreStateEvent.class,
root);
Set<VisitHint> hints =
EnumSet.of(VisitHint.SKIP_ITERATION);
VisitContext visitContext =
VisitContext.createVisitContext(facesContext, null, hints);
root.visitTree(visitContext, new VisitCallback()
{
public
VisitResult visit(VisitContext context, UIComponent target) {
postRestoreStateEvent.setComponent(target);
target.processEvent(postRestoreStateEvent);
return VisitResult.ACCEPT;
}
});
} catch (AbortProcessingException e) {
...
}
}
Now, you
know how PostRestoreStateEvent
works!
Read
further: JSF Events Tutorial
Niciun comentariu :
Trimiteți un comentariu