If you don't know exactly how @ListenerFor, ComponentSystemEventListener and SystemEventListener
works, then this post is for you. First, let's have a quick list of the JSF 2.2 system events:
I believe in the power of example, so I will try to point
out some useful examples that should clarify these JSF notions. Maybe the most
important examples are the ones marked as START BAD PRACTICE n - END BAD PRACTICE n!
Note The
@ListenerFor
accepts two attributes. The first one, systemEventClass is required and, as you
will see next, is used to indicate the type of events that should be listen.
The second one is optional, sourceClass, and is used to indicate the
emitters that should be listen. The latest will be demonstrated later.
So, let's try to obtain some golden rules:
- @ListenerFor used with an UIComponent
that implements the ComponentSystemEventListener
- the UIComponent is registered as the listener
- the listen emitters can be only instances of this UIComponent
- the UIComponent is registered as the listener
- the listen emitters can be only instances of this UIComponent
Below,
you can see a custom component named, TomComponent, which register via @ListenerFor as
a listener for the PostAddToViewEvent event. This means
that each time an instance of the TomComponent (not other UIComponent)
was just added to the view, the processEvent(ComponentSystemEventListener) is
called.
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
public static final String COMPONENT_FAMILY =
"jsf.listenerforanduicomponent";
public static final String COMPONENT_TYPE =
"jsf.listenerforanduicomponent.TomComponent";
@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:
EVENT EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforanduicomponent.TomComponent]
Output at postback request:
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforanduicomponent.TomComponent]
EVENT
EMITTED:
javax.faces.event.PostRestoreStateEvent[source=jsf.listenerforanduicomponent.TomComponent]
Note: In case of custom components (which extends UIComponent),
you don't need to implement the ComponentSystemEventListener explicitly (as
above), since the UIComponent does that :
public
abstract class UIComponent implements
PartialStateHolder,TransientStateHolder,SystemEventListenerHolder,
ComponentSystemEventListener {
...
public void processEvent(ComponentSystemEvent
event) throws AbortProcessingException {
if (event instanceof PostRestoreStateEvent) {
...
}
}
...
}
Moreover, if you check this code, you notice that each UIComponent
listen the PostRestoreStateEvent
event by default. This event occurs on postback, not on initial request (actually, at initial request, by default, is emitted by view root only)! Per
example, the below component will listen only the PostRestoreStateEvent event emitted by instances of TomComponent
- notice that there is no @ListenerFor:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase {
public static final String COMPONENT_FAMILY =
"jsf.listenerforanduicomponent";
public static final String COMPONENT_TYPE =
"jsf.listenerforanduicomponent.TomComponent";
@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;
}
}
- when you need to listen more than one kind
of event, you can use the @ListenersFor. In this annotations we can nest
more @ListenerFor.
- the UIComponent is registered as the listener
- the listen emitters can be only instances of this UIComponent
- the UIComponent is registered as the listener
- the listen emitters can be only instances of this UIComponent
Below, you can see a custom component named, TomComponent,
which register via @ListenerFor as a listener for the PostAddToViewEvent
and PreRenderComponentEvent
events. This means that each time an instance of the TomComponent (not
other UIComponent)
was just added to the view or is about to be rendered, the processEvent(ComponentSystemEventListener)
is called.
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
@ListenersFor({
@ListenerFor(systemEventClass=PostAddToViewEvent.class),
@ListenerFor(systemEventClass=PreRenderComponentEvent.class)
})
public class
TomComponent extends UIComponentBase implements ComponentSystemEventListener {
//remains the same as in the above example
}
Output (at initial request, without the PostRestoreStateEvent):
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforanduicomponent.TomComponent]
EVENT
EMITTED: javax.faces.event.PreRenderComponentEvent[source=jsf.listenerforanduicomponent.TomComponent]
- @ListenerFor used with a Renderer that
implements the ComponentSystemEventListener
- the Renderer is registered as the listener
- the listen emitters can be only instances of the UIComponent rendered by this Renderer
- the Renderer is registered as the listener
- the listen emitters can be only instances of the UIComponent rendered by this Renderer
In order to exemplify this case, we need at least two
components (of course, it can be only one, but we want to show how the renderer
acts for more than one). First, let's adjust the TomComponent to instruct JSF
that it has a separate Renderer:
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
public class
TomComponent extends UIComponentBase {
public static final String COMPONENT_FAMILY =
"jsf.listenerforandrenderer";
public static final String COMPONENT_TYPE =
"jsf.listenerforandrenderer.TomComponent";
public TomComponent() {
setRendererType(TomAndJerryRenderer.RENDERER_TYPE);
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
And what is Tom without Jerry ? So, let's create the JerryComponent
also (pretty the same):
@FacesComponent(value
= JerryComponent.COMPONENT_TYPE, createTag = true)
public class
JerryComponent extends UIComponentBase {
public static final String COMPONENT_FAMILY =
"jsf.listenerforandrenderer";
public static final String COMPONENT_TYPE =
"jsf.listenerforandrenderer.JerryComponent";
public JerryComponent() {
setRendererType(TomAndJerryRenderer.RENDERER_TYPE);
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
}
Now, the Renderer that uses the @ListenerFor
to register the renderer as a listener for the PostAddToViewEvent event
emitted by "its" components:
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
@FacesRenderer(componentFamily
= TomComponent.COMPONENT_FAMILY,
rendererType =
TomAndJerryRenderer.RENDERER_TYPE)
public class
TomAndJerryRenderer extends Renderer implements ComponentSystemEventListener {
public static final String RENDERER_TYPE =
"jsf.listenerforandrenderer.TomAndJerryRenderer";
@Override
public
void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
System.out.println("EVENT EMITTED:
" + event);
}
@Override
public void encodeEnd(FacesContext context,
UIComponent component) throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the cat or
Jerry the mice !");
}
}
Output:
EVENT
EMITTED : javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.TomComponent]
EVENT
EMITTED :
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.JerryComponent]
Note A Renderer
who needs the ComponentSystemEventListener
interface must explicitly implement it.
Note A Renderer
doesn't listen the PostRestoreStateEvent by default, as UIComponent.
Note When
you are using the @ListenersFor and ComponentSystemEventListener
interface, do not use sourceClass attribute. Is useless in both cases, UIComponent
and Renderer.
Note If
the class to which this annotation is attached implements ComponentSystemEventListener
and is neither an instance of Renderer nor UIComponent, the action
taken is unspecified.
Until now,
you saw that when @ListenerFor is used with ComponentSystemEventListener,
JSF knows that the emitters that should be listen are instances of the UIComponent/Renderer
annotated with @ListenerFor.
Further, let's see several cases where we are using the SystemEventListener.
Well, when SystemEventListener
is used, we need to override two methods. First, we have the processEvent(SystemEvent
event), which plays the same role as processEvent(ComponentSystemEventListener),
and the isListenerForSource(Object
source) which allows to "pass" in processEvent(SystemEvent event)
only the needed emitters, usually by checking the source instance type -
through the isListenerForSource(Object
source) will pass all the instances of JSF artifacts capable to emit the
listen event(s), not just instances of the current UIComponent. Another way to
indicate the allowed events emitters consist in using the sourceClass,
which is the optional attribute of the @ListenerFor annotation.
START
BAD PRACTICE 1
We start
with a bad practice. Conforming to
documentation, "if the class to
which this annotation is attached implements SystemEventListener and does not implement ComponentSystemEventListener,
"target" is the Application instance.".
Based on the
above affirmation, is possible to believe that this will work and will register
the TomComponent
as a listener for all emitters capable to emit the PostAddToViewEvent event
(not just instances of TomComponent):
@FacesComponent(value
= TomComponent.COMPONENT_TYPE, createTag = true)
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
public class
TomComponent extends UIComponentBase implements SystemEventListener {
public static final String COMPONENT_FAMILY = "jsf.listenerforanduicomponent";
public static final String COMPONENT_TYPE =
"jsf.listenerforanduicomponent.TomComponent";
@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;
}
}
But,
remember that an custom component implements the ComponentSystemEventListener
interface by inheritance from UIComponent! So, the output of the above case
will be:
EVENT SOURCE:
jsf.listenerforanduicomponent.TomComponent
And the processEvent(SystemEvent
event) is not called! Since ComponentSystemEventListener is
inherited (so implemented), and we have the @ListenerFor, the TomComponent
will be registered as the listener for PostAddToViewEvent event emitted only by
instances of TomComponent.
But, as you notice from the above output, the flow passes through the isListenerForSource(Object
source), which means that, in this particular case, we need to return true;,
or return
source instanceof TomComponent;, otherwise we will block the call of the
processEvent(ComponentSystemEventListener
event) method.
So,
combining @ListenerFor
with SystemEventListener and
UIComponents
is not a good thing, only if you really know what you are doing.
END BAD
PRACTICE 1
Well, the
things changes in case of combining the @ListenerFor with SystemEventListener
and Renderer.
Since Renderer
doesn't implement the ComponentSystemEventListener, we are in the case from the
documentation quoted above. Now, the reasoning is going further and "If "target" is the Application instance, inspect the value
of the sourceClass() annotation attribute
value. If the value is Void.class, call Application.subscribeToEvent(Class,
SystemEventListener), passing the value of systemEventClass()
as the first argument and the instance of the class to which this annotation is
attached (which must implement SystemEventListener)
as the second argument.".
Let's see some
examples:
- use @ListenerFor and SystemEventListener to
register the TomAndJerryRenderer
renderer as the listener for PostAddToViewEvent event.
- the Renderer is registered as the listener
- the listen emitters are all JSF artifacts
capable to emit this kind of event (not only the UIComponents that it
renders)
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
@FacesRenderer(componentFamily
= TomComponent.COMPONENT_FAMILY,
rendererType =
TomAndJerryRenderer.RENDERER_TYPE)
public class
TomAndJerryRenderer extends Renderer implements SystemEventListener {
public static final String RENDERER_TYPE =
"jsf.listenerforandrenderer.TomAndJerryRenderer";
@Override
public
void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("EVENT EMITTED :
" + event);
}
@Override
public void encodeEnd(FacesContext context,
UIComponent component) throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the cat or
Jerry the mice !");
}
@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.listenerforandrenderer.TomComponent
EVENT
EMITTED: javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.TomComponent]
EVENT
SOURCE:
jsf.listenerforandrenderer.JerryComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.JerryComponent]
EVENT
SOURCE:
javax.faces.component.UIViewRoot
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=javax.faces.component.UIViewRoot]
Among other artifacts, you can see the TomComponent
and JerryComponent
also.
The documentation says further that if the sourceClass
is present "call Application.subscribeToEvent(Class, Class,
SystemEventListener), passing the value of systemEventClass()
as the first argument, the value of sourceClass()
as the second argument, and the instance of the class to which this annotation
is attached (which must implement SystemEventListener)
as the third argument.".
- use @ListenerFor and SystemEventListener to
register the TomAndJerryRenderer
renderer as the listener for PostAddToViewEvent event.
- the Renderer is registered as the listener
- the listen emitters will be limited to instances
of TomComponent
using the sourceClass
attribute of @ListenerFor
@ListenerFor(systemEventClass =
PostAddToViewEvent.class, sourceClass=TomComponent.class)
@FacesRenderer(componentFamily
= TomComponent.COMPONENT_FAMILY,
rendererType =
TomAndJerryRenderer.RENDERER_TYPE)
public class
TomAndJerryRenderer extends Renderer implements SystemEventListener {
public
static final String RENDERER_TYPE =
"jsf.listenerforandrenderer.TomAndJerryRenderer";
@Override
public void processEvent(SystemEvent event)
throws AbortProcessingException {
System.out.println("EVENT EMITTED :
" + event);
}
@Override
public
void encodeEnd(FacesContext context, UIComponent component) throws IOException
{
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the cat or
Jerry the mice !");
}
@Override
public boolean isListenerForSource(Object
source) {
System.out.println("EVENT SOURCE:
" + source);
return
true;
}
}
Output:
EVENT
SOURCE:
jsf.listenerforandrenderer.TomComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.TomComponent]
Notice that even through the isListenerForSource(Object source)
method passes only the instances of TomComponent.
If you want to listen more emitters then you need to use the
@ListenersFor.
Per example, if you want to listen the JerryComponent instances also, then you
need this:
@ListenersFor({
@ListenerFor(systemEventClass = PostAddToViewEvent.class,
sourceClass=TomComponent.class),
@ListenerFor(systemEventClass = PostAddToViewEvent.class,
sourceClass=JerryComponent.class)
})
@FacesRenderer(componentFamily
= TomComponent.COMPONENT_FAMILY,
rendererType =
TomAndJerryRenderer.RENDERER_TYPE)
public class
TomAndJerryRenderer extends Renderer implements SystemEventListener {
//remains the same as the later example above
}
Output:
EVENT
SOURCE:
jsf.listenerforandrenderer.TomComponent
EVENT EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.TomComponent]
EVENT
SOURCE:
jsf.listenerforandrenderer.JerryComponent
EVENT
EMITTED: javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.JerryComponent]
Again, notice that even through the isListenerForSource(Object
source) method passes only the
instances of TomComponent
and JerryComponent.
Repeating the @ListenerFor with different sourceClass
can be a little bit annoying. But, we can use the isListenerForSource(Object source) and have a single @ListenerFor
annotation.
- use @ListenerFor and SystemEventListener to
register the TomAndJerryRenderer
renderer as the listener for PostAddToViewEvent event.
- the Renderer is registered as the listener
- the listen emitters will be limited to instances
of TomComponent
and JerryComponent
instances using the isListenerForSource(Object source)
@ListenerFor(systemEventClass =
PostAddToViewEvent.class)
@FacesRenderer(componentFamily
= TomComponent.COMPONENT_FAMILY,
rendererType =
TomAndJerryRenderer.RENDERER_TYPE)
public class
TomAndJerryRenderer extends Renderer implements SystemEventListener {
public static final String RENDERER_TYPE =
"jsf.listenerforandrenderer.TomAndJerryRenderer";
@Override
public
void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("EVENT EMITTED :
" + event);
}
@Override
public void encodeEnd(FacesContext context,
UIComponent component) throws IOException {
ResponseWriter responseWriter =
context.getResponseWriter();
responseWriter.write("I'm Tom the cat or
Jerry the mice !");
}
@Override
public
boolean isListenerForSource(Object source) {
System.out.println("EVENT SOURCE: "
+ source);
return
source instanceof TomComponent || source instanceof JerryComponent;
}
}
Output:
EVENT
SOURCE:
javax.faces.component.html.HtmlBody
EVENT
SOURCE:
jsf.listenerforandrenderer.TomComponent
EVENT
EMITTED: javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.TomComponent]
EVENT
SOURCE:
jsf.listenerforandrenderer.JerryComponent
EVENT
EMITTED:
javax.faces.event.PostAddToViewEvent[source=jsf.listenerforandrenderer.JerryComponent]
EVENT 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 TomComponent
and JerryComponent
instances.
See you in part II
See you in part II
Niciun comentariu :
Trimiteți un comentariu