Read also:
JSF and Observer design pattern - part I (plain code)
JSF and Observer design pattern - part II (Java EE)
JSF and Observer design pattern - part I (plain code)
JSF and Observer design pattern - part II (Java EE)
JSF and Observer design pattern - part III (JSF implementation)
Well, if you
read the previous three parts of this article then you are pretty familiar with
the observer pattern. In this final part we will discuss about the event
broadcasting feature provided by the Apache
DeltaSpike. More precisely, we will discuss about DeltaSpike support for:
·
Observe Faces-Requests
·
BeforePhase / AfterPhase
·
JSF SystemEvents
Observe Faces-Requests
DeltaSpike
allows developers to observe the JSF requests (subject). More precisely, it allows developers to observe when a
JSF request is initialized (before Restore View phase) and when the JSF request
is destroyed (after Render Response phase). Technically speaking this can be
accomplished via @Observes in combination with @org.apache.deltaspike.core.api.lifecycle.Initialized
(shortly, @Initialized)
or @org.apache.deltaspike.core.api.lifecycle.Destroyed
(shortly, @Destroyed)
as qualifier for javax.faces.context.FacesContext.
public void
onBeforeFacesRequest(@Observes @Initialized FacesContext facesContext)
{
//...
}
public void
onAfterFacesRequest(@Observes @Destroyed FacesContext facesContext)
{
//...
}
Note "In case of POST-Redirect-GET @Destroyed FacesContext is "different". The context gets destroyed twice (after: POST and GET)." - thanks to DeltaSpike team for this explanation!
Note "In case of POST-Redirect-GET @Destroyed FacesContext is "different". The context gets destroyed twice (after: POST and GET)." - thanks to DeltaSpike team for this explanation!
Let's
suppose that we have a JSF application that represents a system used by fire
stations to keep tracking of current fires. Each request to this system corresponds
to a reported fire at a specific address. The time between a request-response
lifespan may represent a complex business logic needed to designate the fire
stations that will extinguish the reported fire (can be marked as extinguished
fire). So, if we keep tracking of the initialized
and destroyed JSF requests then we can keep tracking of the current fires. This
can be shaped via an application scoped bean, as below:
@Named
@ApplicationScoped
public class
FiresTrackingBean implements Serializable {
private final List<String> fires;
public FiresTrackingBean(){
fires = new ArrayList();
}
public void fireStarted(@Observes @Initialized FacesContext
facesContext) {
String address =
facesContext.getExternalContext().getRequestParameterMap().get(address_placeholder);
if (address != null) {
System.out.println("Fire
started at " + address);
fires.add(address);
}
}
public void fireExtinguished(@Observes
@Destroyed FacesContext facesContext) {
String address =
facesContext.getExternalContext().getRequestParameterMap().get(address_placeholder);
if (address != null) {
System.out.println("Fire extinguished at
" + address);
fires.remove(address);
}
}
public List<String> getFires() {
return
fires;
}
}
Since the fireStarted()
is invoked before JSF lifecycle begins, we need to fetch the address
value directly from the request parameter
map. For example, if you use <f:param name="address".../>
then you can replace address_placeholder
with address,
but if you submit the address via an input component (e.g. input text) then you
need to replace the address_placeholder
with the corresponding clientId.
So,
pay attention on this aspect!
@Initialized is a good choice for
initializations stuff that are needed for the current request, but if those
initializations needs data provided at current request via forms, view
parameters, <f:param>, etc,
you have to keep in mind that you cannot rely on data model at this moment,
since the data model was not updated yet.
Well, there
is a problem with the fireExtinguished()method. Practically, after a fire was extinguished,
this method will remove the corresponding entry from the fires
list. But, this method is invoked after the Render Response phase, when the
markup was already prepared. This means that it is possible that the fire that
follows to be deleted to be already rendered in the response.
So,
pay attention on this aspect also!
@Destroyed is a good choice for cleaning/destroying
stuff after the current request is over, but pay attention that when this take
place the markup (response) was already generated and may contain values that
follows to be cleaned/destroyed. This is happening because the cleaning process
take place after the Render Response phase.
BeforePhase / AfterPhase
Cases as
above can be fine tuned via DeltaSpike feature that allows developers to
observe JSF request-lifecycle phase-events via @Observes in combination
with @org.apache.deltaspike.jsf.api.listener.phase.BeforePhase
(shortly @BeforePhase)
or @org.apache.deltaspike.jsf.api.listener.phase.AfterPhase
(shortly @AfterPhase)
as qualifier for javax.faces.event.PhaseEvent.
Obviously,
you can rely on JSF default API for writing phase listeners, but the DeltaSpike
approach save us for configurations in faces-config.xml and is more elegant:
public void onPhaseStart(@Observes
@BeforePhase(JsfPhaseId.ANY_PHASE) PhaseEvent event) {
//...
}
public void
onPhaseEnd(@Observes
@AfterPhase(JsfPhaseId.ANY_PHASE) PhaseEvent event) {
//...
}
So, we can
easily shape our fire tracking system to take advantage of the data model and
to ensure that a fire is removed before the markup is prepared, as below:
@Inject
// inject
data from model
public void
fireStarted(@Observes
@AfterPhase(JsfPhaseId.UPDATE_MODEL_VALUES) PhaseEvent event) {
if (injected_address!=
null) {
System.out.println("Fire started at
" + injected_address);
fires.add(injected_address);
}
}
public void
fireExtinguished(@Observes @BeforePhase(JsfPhaseId.RENDER_RESPONSE) PhaseEvent event)
{
if (injected_address!=
null) {
System.out.println("Fire extinguished
at " + injected_address);
fires.remove(injected_address);
}
}
Note "In case of POST-Redirect-GET @Destroyed FacesContext is "different". The context gets destroyed twice (after: POST and GET). Therefore only @AfterPhase(RENDER_RESPONSE) and @PostRenderView always fit for post-render logic." - thanks to DeltaSpike team for this explanation!
Note "In case of POST-Redirect-GET @Destroyed FacesContext is "different". The context gets destroyed twice (after: POST and GET). Therefore only @AfterPhase(RENDER_RESPONSE) and @PostRenderView always fit for post-render logic." - thanks to DeltaSpike team for this explanation!
JSF SystemEvents
Beside JSF
requests and JSF phases, we can observe the following JSF system events via
CDI:
·
javax.faces.event.PostConstructApplicationEvent
·
javax.faces.event.PreDestroyApplicationEvent
·
javax.faces.event.ExceptionQueuedEvent
The below example
is provided in DeltaSpike documentation:
@ApplicationScoped
public class
ApplicationConfig {
public void init(@Observes PostConstructApplicationEvent
event) {
// ...
}
}
As you
probably know, exceptions that occurs during AJAX request are not treated as
exceptions that occurs during non‐AJAX requests. By default, most of them are
invisible to the client. Since the user does not receive a feedback about its
AJAX request success, he will act as any confused user ‐ restart the
application, resend the request, etc. Well, next to the OmniFaces FullAjaxExceptionHandler,
the DeltaSpike @Observes
ExceptionQueuedEvent
can be also used as a starting point in developing a system to handle AJAX
exceptions. Basically, you can rely on the fact that AJAX exceptions are queued
(as unhandled, see ExceptionHandler#getUnhandledExceptionQueuedEvents()) and
it is very easy to programmatically distinguish between AJAX and non-AJAX
requests (see Faces#isAjaxRequest()).
Niciun comentariu :
Trimiteți un comentariu