My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

sâmbătă, 10 ianuarie 2015

JSF-Working with @ListenerFor, ComponentSystemEventListener and SystemEventListener - part I

If you don't know exactly how @ListenerForComponentSystemEventListener 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

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

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

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

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors