JSF-Working with @ListenerFor, ComponentSystemEventListener and SystemEventListener - part I
Sometimes, you need to subscribe to an event programmatically
(conditionally). While @ListenerFor doesn't allows that (being declaratively),
the Application.subscribeToEvent() and UIComponent.subscribeToEvent() methods,
allows us to drop the @ListenerFor and subscribe to an events programmatically.
Below you can see the Application.subscribeToEvent() provided by JSF and some alternatives provided
by OmniFaces 2.1 and 2.0:
UIComponent.subscribeToEvent() used with an UIComponent that implements
the ComponentSystemEventListener
interface
§ the UIComponent is registered as the
listener in a @PostConstruct
method for PostAddToViewEvent
event
§ the
listen emitters can be only instances of this UIComponent
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"jsf.uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
subscribeToEvent(PostAddToViewEvent.class, this);
}
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT EMITTED: "
+ event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return
COMPONENT_FAMILY;
}
}
Output (at initial request without PostRestoreStateEvent):
EVENT
EMITTED: javax.faces.event.PostAddToViewEvent[source=jsf.uicomponentwithsubscribetoevent.TomComponent]
Application.subscribeToEvent() used with an UIComponent that implements
the SystemEventListener
interface
§
the UIComponent is registered as the
listener in a @PostConstruct
method for PostAddToViewEvent
event
§
the listen all emitters of this event
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements SystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"jsf.uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
FacesContext.getCurrentInstance().getApplication().
subscribeToEvent(PostAddToViewEvent.class,
this);
}
@Override
public
void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("EVENT EMITTED:
" + event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
@Override
public
boolean isListenerForSource(Object source) {
System.out.println("EVENT SOURCE: "
+ source);
return
true;
}
}
Output:
EVENT
SOURCE:
javax.faces.component.html.HtmlBody
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=javax.faces.component.html.HtmlBody]
EVENT
SOURCE:
jsf.listenerforanduicomponentwithsubscribetoevent.TomComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.uicomponentwithsubscribetoevent.TomComponent]
EVENT
SOURCE:
javax.faces.component.html.HtmlForm
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=javax.faces.component.html.HtmlForm]
In order to restrict the listen emitters, you can use the Application.subscribeToEvent()
that gets the source class as second argument:
Application.subscribeToEvent() used with an UIComponent that implements
the SystemEventListener
interface
§
the UIComponent is registered as the
listener in a @PostConstruct
method for PostAddToViewEvent
event
§
the listen only events emitted by TomComponent
the relevant change to the later example above:
...
@PostConstruct
public void
tomSubscribeToEvent() {
FacesContext.getCurrentInstance().getApplication().
subscribeToEvent(PostAddToViewEvent.class, TomComponent.class, this);
}
...
Output:
EVENT
SOURCE:
jsf.listenerforanduicomponentwithsubscribetoevent.TomComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.
uicomponentwithsubscribetoevent.TomComponent]
In order, to limit the number of listen emitters to more
than one, you need to repeat the subscription. Below, you can see how we added
the UIViewRoot
emitter next to the TomComponent:
...
@PostConstruct
public void
tomSubscribeToEvent() {
FacesContext.getCurrentInstance().getApplication().
subscribeToEvent(PostAddToViewEvent.class, TomComponent.class, this);
FacesContext.getCurrentInstance().getApplication().
subscribeToEvent(PostAddToViewEvent.class,
UIViewRoot.class, this);
}
...
Output:
EVENT
SOURCE:
jsf.uicomponentwithsubscribetoevent.TomComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.uicomponentwithsubscribetoevent.TomComponent]
EVENT
SOURCE:
javax.faces.component.UIViewRoot
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=javax.faces.component.UIViewRoot]
Of course, you can obtain this by using the Application.subscribeToEvent()
without the source class argument, and limit the accepted emitters in isListenerForSource(Object
source).
Application.subscribeToEvent() used with an UIComponent that implements
the SystemEventListener
interface
§
the UIComponent is registered as the
listener in a @PostConstruct
method for PostAddToViewEvent
event
§
the listen only events emitted by TomComponent
and UIViewRoot
§
the restriction is applied via isListenerForSource(Object
source)
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements SystemEventListener {
public static final String COMPONENT_FAMILY = "uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
FacesContext.getCurrentInstance().getApplication().
subscribeToEvent(PostAddToViewEvent.class, this);
}
@Override
public
void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("EVENT EMITTED: "
+ event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
@Override
public
boolean isListenerForSource(Object source) {
System.out.println("EVENT SOURCE: "
+ source);
return
source instanceof TomComponent || source instanceof UIViewRoot;
}
}
Output:
EVENT
SOURCE:
javax.faces.component.html.HtmlBody
EVENT
SOURCE:
jsf.uicomponentwithsubscribetoevent.TomComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.uicomponentwithsubscribetoevent.TomComponent]
EVENT
SOURCE:
javax.faces.component.html.HtmlForm
EVENT
SOURCE:
javax.faces.component.html.HtmlCommandButton
EVENT
SOURCE:
javax.faces.component.UIViewRoot
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=javax.faces.component.UIViewRoot]
This time, notice that through the isListenerForSource(Object
source) method passes all
instances that emitted the PostAddToViewEvent event, not only the needed
emitters, but only the needed ones hit the processEvent(SystemEvent event)
method.
Note The Application.subscribeToEvent() methods can
be used in Renderers
also.
START
BAD PRACTICE 2
A bad practice is to not associate correctly the JSF
lifecycle phases with the listen events types; you have to know which events
takes place in which JSF phase. Per example, the below component registers
itself as a listener for the PreValidateViewEvent event, and by default to
the PostRestoreStateEvent
event:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
subscribeToEvent(PreValidateEvent.class, this);
}
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT EMITTED:
" + event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
Well, at initial request JSF executed only the Restore View
(there is nothing to restore now) and RenderResponse phases, which means that
the TomComponent
doesn't emit any of PreValidateViewEvent and PostRestoreStateEvent events
(actually, at initial request TomComponent subscribe to PostRestoreStateEvent
and PreValidateViewEvent).
If you didn't know that, then you may think that the application is not working
correctly. At postbacks instead, the Restore View (which restore the component
tree now) and the Process Validations phase are executed, so both events are
emitted and the output will be like this:
EVENT
EMITTED:
javax.faces.event.PostRestoreStateEvent[source=uicomponentwithsubscribetoevent.TomComponent]
EVENT
EMITTED:
javax.faces.event.PreValidateEvent[source=uicomponentwithsubscribetoevent.TomComponent]
Obviously, the PostRestoreStateEvent first!
END BAD
PRACTICE 2
I registered to one event, but I get another one !?!
START
BAD PRACTICE 3
Based on the above idea, novices makes another common
mistake. Per example, they register their component to an event like PreRenderViewEvent,
and, at initial request they got nothing, and at postback, they get PostRestoreStateEvent
reported. This sounds confusing, but it has a clear explanation. Check out the
code:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"jsf.uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
subscribeToEvent(PreRenderViewEvent.class, this);
}
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT EMITTED:
" + event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
First, we already know that an UIComponent listen by
default the PostRestoreStateEvent,
and that this event is emitted only at postback, so this is why is not present
at initial request. But, how about the PreRenderViewEvent event ? Well, the PreRenderViewEvent
can be emitted only by instances of UIViewRoot, while we are listening only
for events emitted by instances of TomComponent. So, the PreRenderViewEvent
will never "appear", while at postback, we will have the default PostRestoreStateEvent
reported and no PreRenderViewEvent.
As a conclusion, do not confuse PreRenderViewEvent with PreRenderComponentEvent!
END BAD
PRACTICE 3
I listen for an event, but it doesn't show up in time !?!
START
BAD PRACTICE 4
You know that an event like PreRenderComponentEvent
should "appear" on initial request and at postbacks also, since the
source of this event instance is an UIComponent instance that is about to be
rendered. But, check out this code:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"jsf.uicomponentwithsubscribetoevent.TomComponent";
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT EMITTED:
" + event);
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
subscribeToEvent(PreRenderComponentEvent.class, this);
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
Well, at initial request the TomComponent will not listen
the PreRenderComponentEvent,
while at postbacks is does so. This is happening because we subscribe to the PreRenderComponentEvent
in the Render Response phase, which is too late for this request (initial request), because this event cannot
occur anymore. But, when JSF hits the encodeEnd() it takes into account the
subscription, so, at postbacks, TomComponent instance listen for PreRenderComponentEvent
emitted by TomComponent
instances. Obviously, if you place the subscription in a proper place, like a @PostConstruct
method, that you will get the PreRenderComponentEvent at initial request
also. As a general rule, if you subscribe to an event "too late",
then it will "appear" at next postback.
A similar example is below, were we intentionally reverse
the natural order of subscription to the PreValidateEvent and PostValidateEvent
events
.
Check out this code:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.uicomponentwithsubscribetoevent";
public static final String COMPONENT_TYPE =
"jsf.uicomponentwithsubscribetoevent.TomComponent";
@PostConstruct
public void tomSubscribeToEvent() {
subscribeToEvent(PostValidateEvent.class,
this);
}
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT
EMITTED: " + event);
if
(event instanceof PostValidateEvent) {
if
(getListenersForEventClass(PreValidateEvent.class) == null) {
System.out.println("REGISTERING TO PreValidateEvent ...");
subscribeToEvent(PreValidateEvent.class, this);
}
}
}
@Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the
cat!");
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
In the @PostConstruct method we subscribe to PostValidateEvent,
which is ok because the Process Validations phase did not take place yet.
Further, when the instance of the TomComponent emit a PostValidateEvent,
we subscribe to PreValidateEvent.
Obviously, when the PostValidateEvent take place, the PreValidateEvent took
place in the past, so this TomComponent instance will not emit this
event at this request. But, at postback, you will notice that the PreValidateEvent
is reported correctly, before the PostValidateEvent, so the "next"
instance of TomComponent
is capable to listen the desired events in the correct order:
Output:
EVENT
EMITTED:
javax.faces.event.PostRestoreStateEvent[source=jsf.listenerforanduicomponent.TomComponent]
EVENT
EMITTED: javax.faces.event.PostValidateEvent[source=jsf.listenerforanduicomponent.TomComponent]
REGISTERING
TO PreValidateEvent ...
EVENT
EMITTED:
javax.faces.event.PostRestoreStateEvent[source=jsf.listenerforanduicomponent.TomComponent]
EVENT
EMITTED: javax.faces.event.PreValidateEvent[source=jsf.listenerforanduicomponent.TomComponent]
EVENT
EMITTED:
javax.faces.event.PostValidateEvent[source=jsf.listenerforanduicomponent.TomComponent]
END BAD
PRACTICE 4
Niciun comentariu :
Trimiteți un comentariu