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

duminică, 11 ianuarie 2015

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

Check first:
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:
Per example:
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

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors