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

marți, 24 noiembrie 2015

JSF Scopes Tutorial - CDI Flow Scope

THIS POST WAS CROPPED FROM MASTERING JAVASERVER FACES 2.2 BOOK

The flow scope allows developers to group pages/views and demarcate
the group with entry/exit points.

Between the request scope and the session scope, we have the CDI flow scope. This scope exists for a while in Spring Web Flow or ADF flow, and now is available in JSF 2.2 as well. Basically, the flow scope allows us to demarcate a set of related pages/views (usually, logic related) with an entry point (known as start node) and an exit point (known as return node).

! The flow scope is a good choice for applications that contain wizards, such as multi-screen subscriptions/registrations, bookings, and shopping carts. Generally speaking, any chunk of an application that has a logical start point and an end point can be encapsulated into the flow scope.

In the same application, we can define multiple flows, which can be seen as modules that are reusable and capable to communicate. They can be called sequentially, can be encapsulated as Matrioska dolls or can create any custom design. Moreover, it is very easy to move, delete, or add a flow into such an application just by plugging in/out the entry and exit point.

To understand the benefits of using the flow scope, you have to identify some disadvantages of the applications that don't use it. They are listed as follows:

The flow scope allows us to define logical units of work.
Each application is a big flow, but usually pages do not follow any intuitive logical design. Apparently, a disordered order governs even when pages are logically related, such as pages of a wizard or of a shopping cart.

The flow scope provides reusability.
Reusing pages can be a difficult task to accomplish, since pages are so tied up to UI components and user interaction.

Flow better than CDI
CDI provides conversation scope capable of stretching over several pages, but the flow scope fits better for JSF.

Data in a flow is scoped to that flow alone; therefore, flows can be opened in multiple windows/tabs.
As the conversation scope, the flow scope covers a set of pages/views, but it has several main advantages, such as it is much more flexible, doesn't need that clumsy begin/end operation, flow scoped beans are created and destroyed automatically when the user enters or exists into/from a flow, provides easy-to-use support for inbound/outbound parameters, and pre-handlers and post-handlers. A normal flow cannot be opened in multiple windows/tabs because information travels between pages with the session scope.

The nodes define the entry and exit points of a flow and there are five types of nodes, which are listed as follows:

View: This represents any JSF page in the application that participates in the flow. It is known as a view node of the flow.

The method call: This indicates an invocation of a method using EL. The called method may return an outcome that indicates which node should be navigated next.

Switch: The switch case statements are a substitute for long if statements. The cases are represented by EL expressions and are evaluated to boolean values. Each case is accompanied by an outcome that will be used when the condition is evaluated to true. There is also a default outcome that will be used when all cases are evaluated to false.

 The flow call: This is used to call another flow in the current flow—these are transition points between flows. The called flow (known as inner or nested flow) is nested in the flow that calls it (known as calling flow or outer flow). When the nested flow finishes its tasks, it will return a view node from the calling flow, which means that the calling flow will have control only after the nested flow's lifespan
comes to an end.

 The flow return: This can be used for returning an outcome to the calling flow.

Flows can pass parameters from one to the other. Parameters sent by a flow to another flow are known as outbound parameters, while parameters received by a flow from another flow are known as inbound parameters.

Well, at this point, you should have enough information about the flow scope to develop some examples. But, before doing this, you need to be aware of some tags, annotations, and conventions.

The flow definition is based on a set of conventions over configuration. A flow has a name, a folder in the web root of the application reflecting the flow name, and a view representing the start node that also reflects the flow name. This folder groups the pages/views that belong to the same flow.

In order to use a flow, you need to accomplish some configuration tasks. These can be done through a configuration file or programmatically. If you choose the first approach, then the configuration file can be limited to one flow, which means that it is stored in the flow folder and is named in the format flowname-flow.xml, or you can use the faces-config.xml file for having all flows in a single place.

Since our first example uses the configuration file, we need to use tags. The main tags used for configuring a flow are as follows:

<flow-definition>: This tag contains an id attribute that uniquely identifies the flow. The value of this ID is the flow name used to reference the flow from JSF pages or beans.

<view>: It is nested in the <flow-definition> tag and indicates the JSF pages that represent the flow nodes; it associates an explicit ID to each page (Facelet) path (further, you can refer to each page by its ID). The page path is mapped in a <vdl-document> tag, nested in the <view> tag. The presence of this tag is optional, but as a convention, at least the <view> tag indicating the start node (start page) is present, especially if you want to set another start node besides the default one, which is represented by the page in the flow with the same name (ID) as the flow. Further, you can use the optional <start-node>ID</start-node> tag and indicate the ID of the <view> tag that maps the custom starting page. As an alternative, the start node of the flow can be indicated by setting the value of the id attribute of a <view> tag as the flow ID, and the content of the encapsulated <vdl-document> tag as the path of the custom starting page. When you refer to the flow ID, JSF will go to that page and automatically put you in the flow.

<flow-return>: It is nested in the <flow-definition> tag and returns an outcome to the calling flow. You can refer to it through the value of the id attribute. There are at least three ways of getting out of a flow: using <flow-return>, using <flow-call> (presented later), or by abandoning the flow.

! We just said that a flow is identified by an ID (by a name). But, when the same flow name is defined in multiple documents (like in big projects that use multiple packaged flows from different vendors), there is one more ID needed. This ID is known as the document ID. Thus, when you need to identify a flow whose name appears in different documents, we need the flow ID and the defining document ID. Most of the time the document ID is omitted; therefore, it is not demonstrated in this section. In this section, you will see just a few hints about it.

In order to define the simplest flow, you need to be aware of the following diagram:

The simple flow
With these three tags, <start-node> and/or <view>, <flow-return>, and <from-outcome>, you can configure a simple flow, like a peddling registration form. Let's suppose that a tennis player registers online to a tournament through a flow made up of two JSF pages (the flow name will be registration): a page containing a form used for collecting data and a confirmation page. Moreover, there will be two pages outside the flow, one for entering into the flow (like the first page of the website), and one that is called after confirmation.

In the following diagram, you can see an image of our flow:


Let's have a look at the code for the first page that is outside the flow and outside the registration folder (index.xhtml) as follows:

<h:body>
 In flow ?  #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 REGISTER NEW PLAYER
 <h:form>
  <h:commandButton value="Start Registration" action="registration" immediate="true"/>
 </h:form>
</h:body>

Two important things can be observed here. First, the following lines:

#{null != facesContext.application.flowHandler.currentFlow}
#{facesContext.application.flowHandler.currentFlow.id}

The first line returns a boolean value indicating whether the current page is or is not in a flow. Obviously, the index.xhtml page is not in a flow; therefore, false will be returned. You can use it for tests. The second line displays the ID of the current flow.

Further, you need to take a look at the value of the attribute action of the <h:commandButton> tag. This value is the name (ID) of our flow; after the window context is enabled, JSF will search the indicated flow and navigate to the start node of the flow. By default, the window context is disabled.

Therefore, when the button labeled Start Registration is clicked, the application steps in the registration flow and loads the start node page represented by the registration.xhtml page. The code for this page is as follows:

<h:body>
 First page in the 'registration' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 You are registered as:#{flowScope.value}
 <h:form>
  Name &amp; Surname:
  <h:inputText id="nameSurnameId" value="#{flowScope.value}" />
  <h:commandButton value="Register To Tournament" action="confirm"/>
  <h:commandButton value="Back(exit flow)" action="taskFlowReturnIndex"/>
 </h:form>
</h:body>

Since we are in the flow, currentFlow will return true.

It is more important to focus on the implicit object, flowScope - implicit object (which indicates the current flow) is used for sharing data through the entire flow and maps to facesContext.getApplication().getFlowHandler().getCurrentFlowScope(). For example, the value of the <h:inputText> tag can be put into the flowScope object and can be read from the flow scope in the next page, as follows:

#{flowScope.value}

The button labeled Register To Tournament navigates to the second page in the flow, confirm.xhtml; this is a usual navigation case, there is nothing to say here. But the other button navigates outside the flow (to index.xhtml) by indicating the ID of a flow return. In the configuration file, this flow return is as shown in the following code:

<flow-return id="taskFlowReturnIndex">
 <from-outcome>/index</from-outcome>
</flow-return>

The code of the confirm.xhtml page is as follows:

<h:body>
 Second page in the 'registration' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 You are registered as:#{flowScope.value}
 <h:form>
  <h:commandButton value="Back (still in flow)" action="registration"/>
  <h:commandButton value="Next (exit flow)" action="taskFlowReturnDone"/>
 </h:form>
</h:body>

This page displays the data that was entered and stored on the flow scope along with both the buttons. The first button navigates back to the registration.xhtml page, while the other one navigates to the done.xhtml page, which is outside the flow. The flow return is identified by the ID, as shown in the following code:

<flow-return id="taskFlowReturnDone">
 <from-outcome>/done</from-outcome>
</flow-return>

The done.xhtml page just checks to see if the page is in flow and displays a simple message, as shown in the following code:

<h:body>
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 REGISTER NEW PLAYER ENDED
</h:body>

The final step is to define the flow in a configuration file. Since you have a single flow, you can create a file registration-flow.xml in the registration folder. The following is the code of the registration-flow.xml file:

<faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
              http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
   
 <flow-definition id="registration">
  <view id="registration">
   <vdl-document>/registration/registration.xhtml</vdl-document>
  </view>
  <flow-return id="taskFlowReturnIndex">
   <from-outcome>/index</from-outcome>
  </flow-return>
  <flow-return id="taskFlowReturnDone">
   <from-outcome>/done</from-outcome>
  </flow-return>
 </flow-definition>   
</faces-config>

You can also place the following code inside the faces-config.xml file in the <faces-flow-definition> tag:

<faces-flow-definition>
 <flow-definition id="registration">
 ...
</faces-flow-definition>

The complete application is available here.

Flow with beans
Beside pages, a flow can contain beans. A bean defined in a flow is annotated with @FlowScoped; this is a CDI annotation that enables automatic activation (when the scope is entered) and passivation (when the scope is exited). The @FlowScoped bean requires an attribute named value that contains the flow ID. The data stored in such a bean is available in all pages that belong to that flow.

! The flow scope bean might get passivated by the container and should be capable of passivity by implementing the java.io.Serializable interface.

Adding a bean in the registration flow can modify the initial diagram, as shown in the following diagram:


As you can see, the bean will store the data collected from the registration form in the flow scope (in the previous example, this data was passed using the flowScope implicit object). The button labeled Register To Tournament will call the registrationAction() bean method, which will decide if the registration is possible and return the flow back to the registration.xhtml page or next to the confirm.xhtml page.

The registration.xhtml page's code is modified as follows:

<h:body>
 First page in the 'registration' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 Your registration last credentials: #{registrationBean.playerName} #{registrationBean.playerSurname}
 <h:form>
  Name: <h:inputText value="#{registrationBean.playerName}"/>
  Surname: <h:inputText value="#{registrationBean.playerSurname}"/>
  <h:commandButton value="Register To Tournament" action="#{registrationBean.registrationAction()}"/>
  <h:commandButton value="Back (exit flow)" action="taskFlowReturnIndex"/>
 </h:form>
</h:body>

The code of RegistrationBean is as follows:

@Named
@FlowScoped(value="registration")
public class RegistrationBean implements Serializable {

 private String playerName;
 private String playerSurname;

 //getters and setters

 public String getReturnValue() {
  return "/done";
 }

 public String registrationAction(){
  //simulate some registration conditions
  Random r= new Random();
  int nr = r.nextInt(10);
  if(nr < 5){
     playerName="";
     playerSurname="";
     FacesContext.getCurrentInstance().addMessage("password", new FacesMessage(FacesMessage.SEVERITY_ERROR, "Registration failed!",""));
     return "registration";
  } else {
     return "confirm";
  }
 }
}

The code is self explanatory, but what about the getReturnValue() method? Well, this is just an example of how a flow scoped bean can indicate the outcome of a flow return. Instead of using the following code:

<flow-return id="taskFlowReturnDone">
 <from-outcome>/done</from-outcome>
</flow-return>

You can use the following code:

<flow-return id="taskFlowReturnDone">
 <from-outcome>#{registrationBean.returnValue}</from-outcome>
</flow-return>

The complete application is available here.

Nested flows
Well, now let's complicate things by adding another flow under the existing one. Let's suppose that after the registration, the player has to indicate the day and the hour when he is available to play the first match. This can be accomplished in a new flow named schedule. The registration flow will call the schedule flow and will pass some parameters to it. The schedule flow will return in the registration flow, which will provide a simple button for navigation outside the registration flow.
! The nested flow returns only in the calling flow. You have to refer to a page of the calling flow in the <flow-return> tag of the nested flow, including the pages returned by the calling flow.

Passing parameters is a thing that requires more tags in the configuration tag. Therefore, you need to know the following tags:

<flow-call>: This calls another flow in the current flow. This tag requires the id attribute. The value of this attribute will be used to refer to this flow call.

<flow-reference>: This is nested in the <flow-call> tag and contains the ID of the flow that must be called.

<outbound-parameter>: This is nested in the <flow-call> tag and defines parameters that must be passed to the called flow.

<inbound-parameter>: This defines the parameters passed from another flow.

In order to see these tags at work, you need to take a look at the application flow. The diagram of the application will change as follows:


We resume our discussion from the confirm.xhtml page (defined in the registration flow). From this page, we want to navigate to the schedule.xhtml page, which is available in the schedule flow (the /schedule folder). For this, we can add a new button, labeled Schedule, as shown in the following code:

<h:form>
 <h:commandButton value="Back (still in flow)" action="registration"/>
 <h:commandButton id="Next" value="Schedule" action="callSchedule" />
 <h:commandButton value="Next (exit flow)" action="taskFlowReturnDone"/>
</h:form>

The button's action attribute value is the ID of the <flow-call> tag. When the button is clicked, JSF locates the corresponding <flow-call> tag and follows the flow with the ID indicated by the <flow-id> tag, as shown in the following code:

<flow-call id="callSchedule">
 <flow-reference>
  <flow-id>schedule</flow-id>
 </flow-reference>
 ...
</flow-call>

Moreover, we want to pass several parameters from the registration flow to the schedule flow: the player name and surname (stored in the flow scoped RegistrationBean bean) and a constant representing some registration code (it can also be generated based on certain rules). This can be accomplished by the <outbound-parameter> tag, as shown in the following code:

<flow-call id="callSchedule">
 <flow-reference>
  <flow-id>schedule</flow-id>
 </flow-reference>
 <outbound-parameter>
  <name>playernameparam</name>
  <value>#{registrationBean.playerName}</value>
 </outbound-parameter>
 <outbound-parameter>
  <name>playersurnameparam</name>
  <value>#{registrationBean.playerSurname}</value>
 </outbound-parameter>
 <outbound-parameter>
  <name>playerregistrationcode</name>
  <value>349CF0YO122</value>
 </outbound-parameter>
</flow-call>

The schedule.xhtml page displays a hello message based on the received parameters and a form that allows to the player to enter the day and hour when he is available for playing the first match, as shown in the following code:

<h:body>
 First page in the 'schedule' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 Hello, #{flowScope.name} #{flowScope.surname} (#{scheduleBean.regcode})
 <h:form>
  Day: <h:inputText value="#{scheduleBean.day}"/>
  Starting At Hour: <h:inputText value="#{scheduleBean.hourstart}"/>
  <h:commandButton value="Save" action="success"/>
 </h:form>
</h:body>

Notice that the name and surname are obtained from the flow scope using the flowScope object, while the registration code is obtained from the flow scoped ScheduleBean; this bean stores the day, hour (received from the player), and registration code (received from the registration flow). Each piece of
information received from the registration bean was guided to the place of storage using the <inbound-parameter> tag in the schedule-flow.xml file, as shown in the following code:

<flow-definition id="schedule">
 <view id="schedule">
  <vdl-document>/schedule/schedule.xhtml</vdl-document>
 </view>
 <inbound-parameter>
  <name>playernameparam</name>
  <value>#{flowScope.name}</value>
 </inbound-parameter>
 <inbound-parameter>
  <name>playersurnameparam</name>
  <value>#{flowScope.surname}</value>
 </inbound-parameter>
 <inbound-parameter>
  <name>playerregistrationcode</name>
  <value>#{scheduleBean.regcode}</value>
 </inbound-parameter>
</flow-definition>

After the day and hour are inserted, the button labeled Save should save the data and navigate to the success.xhtml page, which is a simple page that displays all data provided by the player. From this page, we can return to the calling flow, registration, via a simple button labeled Exit Registration, as shown in the following code:

<h:body>
 Second page in the 'schedule' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 You are registered as #{flowScope.name} #{flowScope.surname} (#{scheduleBean.regcode})
 You will play first match #{scheduleBean.day} after #{scheduleBean.hourstart}
 <h:button value="Exit Registration" outcome="taskFlowReturnThanks"/>
</h:body>

The outcome, taskFlowReturnThanks, is defined in the schedule-flow.xml file as follows:

<flow-return id="taskFlowReturnThanks">
 <from-outcome>/registration/thanks.xhtml</from-outcome>
</flow-return>

The thanks.xhtml page is just a final step before the user exists from the registration flow, as shown in the following code:

<h:body>
 Third page in the 'registration' flow
 In flow ? #{null != facesContext.application.flowHandler.currentFlow}
 Flow Id: #{facesContext.application.flowHandler.currentFlow.id}
 Thanks for your patience, Mr :#{registrationBean.playerName}
#{registrationBean.playerSurname}

 We wish you beautiful games!
 <h:button value="Bye Bye, #{registrationBean.playerSurname}" outcome="taskFlowReturnDone"/>
</h:body>

If you want to jump over the thanks.xhtml page, directly outside of both flows, then you can define the flow return, taskFlowReturnThanks, to point out the done.xhtml page, which is returned by the calling flow via the taskFlowReturnDone flow return. Therefore, we can use the following code:

<flow-return id="taskFlowReturnThanks">
 <from-outcome>taskFlowReturnDone</from-outcome>
</flow-return>

The complete application is available here.

Configuring flows programmatically
In all the previous examples, you saw how to configure a flow using the declarative approach. But, flows can be configured programmatically also. The steps for configuring a flow programmatically are as follows:

1. Create a class and name it as the flow. This is more like a convention, not a requirement!

2. In this class, write a method as shown in the following code; the @FlowDefinition annotation is a class-level annotation that allows the flow definition to be defined using the FlowBuilder API. The name of this method can be any valid name, but defineFlow is like a convention. So, the name defineFlow is not mandatory, and you can even define more flows in the same class as long as you have annotated them correctly.

@Produces
@FlowDefinition
public Flow defineFlow(@FlowBuilderParameter FlowBuilder flowBuilder) {
 ...
}

3. Use the FlowBuilder API to configure the flow.

Using the FlowBuilder API is pretty straightforward and intuitive. For example, you can write the registration-flow.xml file programmatically, as follows:

public class Registration implements Serializable {

 @Produces
 @FlowDefinition
 public Flow defineFlow(@FlowBuilderParameter FlowBuilder flowBuilder) {

  String flowId = "registration";
  flowBuilder.id("", flowId);

  flowBuilder.viewNode(flowId, "/" + flowId + "/" + flowId + ".xhtml").markAsStartNode();
  flowBuilder.viewNode("confirm-id", "/" + flowId + "/confirm.xhtml");
  flowBuilder.viewNode("thanks-id", "/" + flowId + "/thanks.xhtml");
  flowBuilder.returnNode("taskFlowReturnIndex").fromOutcome("/index");
  flowBuilder.returnNode("taskFlowReturnDone").fromOutcome("#{registrationBean.returnValue}");
  flowBuilder.flowCallNode("callSchedule").flowReference("", "schedule").
  outboundParameter("playernameparam","#{registrationBean.playerName}").
  outboundParameter("playersurnameparam","#{registrationBean.playerSurname}").
  outboundParameter("playerregistrationcode", "349CF0YO122");

  return flowBuilder.getFlow();
 }
}

As you can see, for each tag used in the declarative approach, there is a corresponding method in the FlowBuilder API. For example, the flowBuilder.id method accepts two arguments: the first one represents the document ID (usually, an empty space), and the second one represents the flow ID.

The schedule-flow.xml file can be programmatically translated as shown in the following code:

public class Schedule implements Serializable {

 @Produces
 @FlowDefinition
 public Flow defineFlow(@FlowBuilderParameter FlowBuilder flowBuilder) {

  String flowId = "schedule";
  flowBuilder.id("", flowId);

  flowBuilder.viewNode(flowId, "/" + flowId + "/" + flowId + ".xhtml").markAsStartNode();
  flowBuilder.viewNode("success-id", "/" + flowId + "/success.xhtml");
  flowBuilder.returnNode("taskFlowReturnThanks").fromOutcome("/registration/thanks.xhtml");
  flowBuilder.inboundParameter("playernameparam", "#{flowScope.name}");
  flowBuilder.inboundParameter("playersurnameparam", "#{flowScope.surname}");
  flowBuilder.inboundParameter("playerregistrationcode","#{scheduleBean.regcode}");

  return flowBuilder.getFlow();
 }
}

! A method annotated with @PostConstruct will be called when the application enters into the current flow and the flow scoped bean is instantiated, while subsequent requests will use this instance until the flow is dumped. This is repeated if the application enters in this flow again. So, initializations specific to the current flow can be placed here.

The complete application is available here.

! Declarative and programmatic configurations can be mixed in the same application. You can see such an application in Mastering JavaServer Faces 2.2 book.

Flows and navigation cases
Navigation cases can be used for navigating inside flows. At this moment, when you click on the button labeled Register To Tournament, the flow goes in the confirm.xhtml page based on implicit navigation. But we can easily exemplify an explicit navigation in the flow by replacing the value of the action attribute as follows:

<h:commandButton value="Register To Tournament" action="confirm_outcome"/>

Now, confirm_outcome cannot be automatically fetched to the confirm.xhtml page; therefore, in the registration-flow.xml file, we can add an explicit navigation case, as shown in the following code:

<navigation-rule>
 <from-view-id>/registration/registration.xhtml</from-view-id>
 <navigation-case>
  <from-outcome>confirm_outcome</from-outcome>
  <to-view-id>/registration/confirm.xhtml</to-view-id>
  <redirect/>
 </navigation-case>
</navigation-rule>

You can see such an application in Mastering JavaServer Faces 2.2 book.

! When you need to use a navigation case to enter in a flow, you will have to specify the <to-flow-document-id>document_ID</toflow-document-id> statement nested in the <navigation-case> tag. If there is no document ID, that uses <to-flow-document-id/>. Moreover a <h:button> (or <h:link>) can be used to enter in such a flow, as follows:

<h:button id="..." value="enter flow" outcome="flow">
 <f:attribute name="to-flow-document-id" value="unique"/>
</h:button>

If you choose to write a programmatic navigation case, then JSF 2.2 comes with a method named, getToFlowDocumentId(), which should be overridden for indicating the document ID.

In order to accomplish the same thing in a programmatic fashion, you need to use the NavigationCaseBuilder API, as shown in the following code; this is the same navigation case, so we have used only the needed methods:

flowBuilder.navigationCase().fromViewId("/registration/registration.xhtml").
 fromOutcome("confirm_outcome").toViewId("/registration/confirm.xhtml").redirect();

You can see such an application in Mastering JavaServer Faces 2.2 book.

Moreover, you can even use a custom navigation handler. The new NavigationHandlerWrapper class (added in JSF 2.2) provides a simple implementation of the NavigationHandler class. Therefore, we can easily extend it to prove a navigation case using a custom navigation handler, as shown in the following code:

public class CustomNavigationHandler extends NavigationHandlerWrapper {

 private NavigationHandler configurableNavigationHandler;

 public CustomNavigationHandler() {}

 public CustomNavigationHandler(NavigationHandler configurableNavigationHandler){
  this.configurableNavigationHandler =
  configurableNavigationHandler;
 }

 @Override
 public void handleNavigation(FacesContext context,String fromAction, String outcome){
  if (outcome.equals("confirm_outcome")) {
      outcome = "confirm";
  }
  getWrapped().handleNavigation(context, fromAction, outcome);
 }

 @Override
 public NavigationHandler getWrapped() {
  return configurableNavigationHandler;
 }
}

Finally, a quick configuration in the faces-config.xml file is as follows:

<application>
 <navigation-handler>
  beans.CustomNavigationHandler
 </navigation-handler>
</application>

When the flow has a document ID, you need to override the handleNavigation(FacesContext context, String fromAction, String outcome, String toFlowDocumentId) method.

You can see such an application in Mastering JavaServer Faces 2.2 book.

Inspecting flow navigation cases
Whatever approach you choose for using navigation cases inside flows, you can always inspect them via the ConfigurableNavigationHandler.inspectFlow() method. This method is invoked by the flow system to cause the flow to be inspected for navigation rules. You can easily override it to obtain information about navigation cases, by writing a custom configurable navigation handler. The easiest way to accomplish this is to extend the new ConfigurableNavigationHandlerWrapper class (introduced in JSF 2.2), which represents a simple implementation of ConfigurableNavigationHandler. For example, the following snippet of code sends in log information about each found navigation case:

public class CustomConfigurableNavigationHandler extends
ConfigurableNavigationHandlerWrapper {

 private final static Logger logger =
  Logger.getLogger(CustomConfigurableNavigationHandler.class.getName());
 private ConfigurableNavigationHandler configurableNavigationHandler;

 public CustomConfigurableNavigationHandler() {}

 public CustomConfigurableNavigationHandler (ConfigurableNavigationHandler configurableNavigationHandler){
  this.configurableNavigationHandler = configurableNavigationHandler;
 }

 @Override
 public void inspectFlow(FacesContext context, Flow flow) {
  getWrapped().inspectFlow(context, flow);

  if (flow.getNavigationCases().size() > 0) {
      Map<String, Set<NavigationCase>> navigationCases = flow.getNavigationCases();
      for (Map.Entry<String, Set<NavigationCase>> entry: navigationCases.entrySet()) {
           logger.log(Level.INFO, "Navigation case: {0}", entry.getKey());
           for (NavigationCase nc : entry.getValue()) {
               logger.log(Level.INFO, "From view id: {0}", nc.getFromViewId());
               logger.log(Level.INFO, "From outcome: {0}", nc.getFromOutcome());
               logger.log(Level.INFO, "To view id: {0}", nc.getToViewId(context));
               logger.log(Level.INFO, "Redirect: {0}", nc.isRedirect());
           }
      }
  }
 }

 @Override
 public ConfigurableNavigationHandler getWrapped() {
  return configurableNavigationHandler;
 }
}

If you attach this custom configurable navigation handler to one of the preceding three examples, then you will get information about the presented navigation case. You can see such an application in Mastering JavaServer Faces 2.2 book.

Using the initializer and finalizer
By using the FlowBuilder API, we can attach callback methods that will be automatically called when a flow is created and right before it is destroyed. The FlowBuilder.initializer() method has the following signatures, which are called when the flow is created:

public abstract FlowBuilder initializer(String methodExpression)
public abstract FlowBuilder initializer(javax.el.MethodExpression methodExpression)

The FlowBuilder.finalizer() signature is called before the flow is destroyed, as follows:

public abstract FlowBuilder finalizer(String methodExpression)
public abstract FlowBuilder finalizer(javax.el.MethodExpression methodExpression)

For example, the initializer method can be used to pass external parameters into a flow. Let's suppose that in the index.xhtml page (outside the flow), when we click  on the button labeled Start Registration, we want to pass the tournament name and place into the flow, as follows:

<h:form>
 <h:inputHidden id="tournamentNameId" value="Roland Garros"/>
 <h:inputHidden id="tournamentPlaceId" value="France"/>
 <h:commandButton value="Start Registration" action="registration"/>
</h:form>

These two parameters must be available when the flow starts, because the wrapped information is displayed in the registration.xhml page (the start node of the flow) via two properties from RegistrationBean, namely tournamentName and tournamentPlace. For this, we need to call a method from RegistrationBean capable of extracting this information and store it in these two properties, as shown in the following code:

//initializer method
public void tournamentInitialize() {
 tournamentName = FacesContext.getCurrentInstance().
  getExternalContext().getRequestParameterMap().get("tournamentNameId");
 tournamentPlace = FacesContext.getCurrentInstance().
  getExternalContext().getRequestParameterMap().get("tournamentPlaceId");
}

Now is the interesting part, because we can use the initializer method to indicate the tournamentInitialize() method as the callback method that should be invoked when the flow is created. This can be done in the registration-flow.xml file as follows:

<initializer>
 #{registrationBean.tournamentInitialize()}
</initializer>

So, at this moment, we can use the tournament name and place right from the beginning of the flow and during the flow's lifespan. Going further, another simple scenario can be the justification for using a finalizer method. Let's suppose that we count the registered players via an application scoped bean named PlayersCounterBean, as shown in the following code:

@Named
@ApplicationScoped
public class PlayersCounterBean implements Serializable {

 private int count = 0;

 public int getCount() {
  return count;
 }

 public void addPlayer() {
  count++;
 }
}

The count variable should be increased when the player exits the flow, and the registration is successfully done; therefore, we can place a finalizer method in the registration-flow.xml file as follows:

<finalizer>
 #{registrationBean.tournamentFinalize()}
</finalizer>

The tournamentFinalize() method is implemented in RegistrationBean, as shown in the following code:

@Named
@FlowScoped(value = "registration")
public class RegistrationBean implements Serializable {

 @Inject
 private PlayersCounterBean playersCounterBean;
 ...
 //finalizer method
 public void tournamentFinalize() {
  playersCounterBean.addPlayer();
 }
}

Since the PlayersCounterBean is an application bean, we can use its goodies outside the flow. The complete application can be found here.

The same output can be programmatically achieved using the following code:

flowBuilder.initializer("#{registrationBean. 
 tournamentInitialize(param['tournamentNameId'], param['tournamentPlaceId'])}");
flowBuilder.finalizer("#{registrationBean.tournamentFinalize()}");

For the sake of variation, in this case we didn't extract the parameter values using the request parameter Map. We preferred to use the implicit object param and to pass the values as arguments of the tournamentInitialize() method as follows:

//initializer method
public void tournamentInitialize(String tn, String tp) {
 tournamentName = tn;
 tournamentPlace = tp;
}

You can see such an application in Mastering JavaServer Faces 2.2 book.

Using the flow switch
The switch case statements are a substitute for long if statements and are useful to do conditional outcome mapping. In order to see it at work, we can suppose that for each tournament we have a separate confirm.xhtml page. Let's have the four grand slams in tennis and the associated XHTML confirmation pages, as follows:

• Roland Garros and confirm_rg.xhtml
• Wimbledon and confirm_wb.xhtml
• US Open and confirm_us.xhtml
• Australian Open and confirm_ao.xhtml

The name and place of the tournament are passed in the flow via a simple form (one form per tournament), as follows (you already know from the preceding section how this information may be obtained inside the flow):

<h:form>
 <h:inputHidden id="tournamentNameId" value="Australian Open"/>
 <h:inputHidden id="tournamentPlaceId" value="Australia"/>
 <h:commandButton value="Start Registration (Australian Open)" action="registration"/>
</h:form>

Now, after clicking on the button labeled Register To..., we need to choose the right confirmation page. For this, we can use a programmatic switch, as shown in the following code:

public class Registration implements Serializable {

 @Produces
 @FlowDefinition
 public Flow defineFlow(@FlowBuilderParameter FlowBuilder flowBuilder) {

  String flowId = "registration";
  flowBuilder.id("", flowId);

  flowBuilder.viewNode(flowId, "/" + flowId + "/" + flowId + ".xhtml").markAsStartNode();
  flowBuilder.viewNode("no-tournament-id", "/" + flowId + "/notournament.xhtml");
  flowBuilder.viewNode("confirm-rg-id", "/" + flowId + "/confirm_rg.xhtml");
  flowBuilder.viewNode("confirm-wb-id", "/" + flowId + "/confirm_wb.xhtml");
  flowBuilder.viewNode("confirm-us-id", "/" + flowId + "/confirm_us.xhtml");
  flowBuilder.viewNode("confirm-ao-id", "/" + flowId + "/confirm_ao.xhtml");
  flowBuilder.returnNode("taskFlowReturnDone").fromOutcome("#{registrationBean.returnValue}");
  
  flowBuilder.switchNode("confirm-switch-id").
   defaultOutcome("no-tournament-id").
   switchCase().condition("#{registrationBean.tournamentName eq
    'Roland Garros'}").fromOutcome("confirm-rg-id").
    condition("#{registrationBean.tournamentName eq
    'Wimbledon'}").fromOutcome("confirm-wb-id").
    condition("#{registrationBean.tournamentName eq 'US
    Open'}").fromOutcome("confirm-us-id").
    condition("#{registrationBean.tournamentName eq 'Australian
    Open'}").fromOutcome("confirm-ao-id");

  flowBuilder.initializer("#{registrationBean.
   tournamentInitialize(param['tournamentNameId'],
   param['tournamentPlaceId'])}");
  flowBuilder.finalizer("#{registrationBean.
   tournamentFinalize()}");

  return flowBuilder.getFlow();
  }
}

Notice that when no condition is evaluated to true, the selected node will be the notournament.xhtml page, which represents the default outcome. This is just a simple XHMTL page containing some specific text. 

You can see such an application in Mastering JavaServer Faces 2.2 book.

Declaratively, this can be achieved in the registration-flow.xml file as shown in the following code. You can use <view> tags to hide the outcome's path behind some IDs (map outcomes to pages), as we
saw in the programmatic example:

<switch id="confirm-switch-id">
 <default-outcome>
  /registration/notournament.xhtml
 </default-outcome>
 <case>
  <if>#{registrationBean.tournamentName eq 'Roland Garros'}</if>
  <from-outcome>/registration/confirm_rg.xhtml</from-outcome>
 </case>
 <case>
  <if>#{registrationBean.tournamentName eq 'Wimbledon'}</if>
  <from-outcome>/registration/confirm_wb.xhtml</from-outcome>
 </case>
 <case>
  <if>#{registrationBean.tournamentName eq 'US Open'}</if>
  <from-outcome>/registration/confirm_us.xhtml</from-outcome>
 </case>
 <case>
  <if>#{registrationBean.tournamentName eq 'Australian Open'}</if>
  <from-outcome>/registration/confirm_ao.xhtml</from-outcome>
 </case>
</switch>

So, switch can be useful when you don't want to map each outcome to a single page.

Packaging flows
Flows act as logical units of work; therefore, they are portable across multiple applications. The portability is obtained by packaging the flow artifacts in a JAR file. Further, the JAR file can be added in any application CLASSPATH and the flow is ready to be used. To package a flow, you need to follow some conventions, which are listed as follows:

1. Explicitly define the flows in the faces-config.xml file.
2. In the JAR root, create a META-INF folder.
3. Add the faces-config.xml file in this folder.
4. Add the beans.xml file in this folder.
5. In the same folder, META-INF, create a subfolder named flows.
6. In the flows folder, add all nodes (pages) of the flow.
7. In the JAR root, outside the META-INF folder, add all the Java code (classes) needed by the flow.

Based on the preceding steps, the flow described in the Flows with beans section can be packaged in a JAR file named registration.jar, as shown in the following screenshot:
You can see such an application in Mastering JavaServer Faces 2.2 book.

Programmatic flow scope
Programmatically speaking, the flow scope can be accessed via the javax.faces.flow.FlowHandler class. After obtaining a FlowHandler class's object, you can easily access the current flow, add a new flow, and manipulate the flow map represented by #{flowScope}, as follows:

FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
FlowHandler flowHandler = application.getFlowHandler();

//get current flow
Flow flow = flowHandler.getCurrentFlow();
Flow flowContext = flowHandler.getCurrentFlow(context);

//add flow
flowHandler.addFlow(context, flow);

//get access to the Map that backs #{flowScope}
Map<Object,Object> flowMap = flowHandler.getCurrentFlowScope();

Obviously, the FlowHandler class is the most important class involved in the interaction between runtime and the faces flow feature. This is an abstract class that can be extended to provide a custom flow handler implementation. In order to do that, you can start by creating a new FlowHandlerFactory class, which is used by the Application class to create the singleton instance of the FlowHandler class. This class has a simple implementation named FlowHandlerFactoryWrapper, which can be easily extended to return a custom flow handler, as shown in the following code:

public class CustomFlowHandlerFactory extends FlowHandlerFactoryWrapper {

 private FlowHandlerFactory flowHandlerFactory;

 public CustomFlowHandlerFactory(){}

 public CustomFlowHandlerFactory(FlowHandlerFactory flowHandlerFactory){
  this.flowHandlerFactory = flowHandlerFactory;
 }

 @Override
 public FlowHandler createFlowHandler(FacesContext context){
  FlowHandler customFlowHandler = new CustomFlowHandler(getWrapped().createFlowHandler(context));
  return customFlowHandler;
 }

 @Override
 public FlowHandlerFactory getWrapped() {
  return this.flowHandlerFactory;
 }
}

This factory should be configured in the faces-config.xml file, as shown in the following code:

<factory>
 <flow-handler-factory>
  beans.CustomFlowHandlerFactory
 </flow-handler-factory>
</factory>

Further, the CustomFlowHandler class represents an extension of the FlowHandler class. Since the FlowHandler class is an abstract class, you need to provide an implementation for each of its methods, as shown in the following code:

public class CustomFlowHandler extends FlowHandler {

 private FlowHandler flowHandler;

 public CustomFlowHandler() {}

 public CustomFlowHandler(FlowHandler flowHandler) {
  this.flowHandler = flowHandler;
 }
 ...
 //Overrided methods
 ...
}

For example, you know from the previous sections that the registration flow passed several outbound parameters to the nested schedule flow. You saw how to accomplish that declaratively, in the registration-flow.xml file, and programmatically, via the FlowBuilder API, in the Registration class. You can do the same thing from a custom flow handler in the method named, transition(), which is capable to perform a transition between a source flow (for example, registration) and a target flow (for example, schedule). When the registration flow calls the schedule flow, you can write the following code:

@Override
public void transition(FacesContext context, Flow sourceFlow,
            Flow targetFlow, FlowCallNode outboundCallNode, String toViewId) {

 if ((sourceFlow != null) && (targetFlow != null)) {
      if ((sourceFlow.getStartNodeId().equals("registration")) &&
                     (targetFlow.getStartNodeId().equals("schedule"))) {
           FlowCallNode flowCallNode = sourceFlow.getFlowCalls().get("callSchedule");
           Map<String, Parameter> outboundParameters = flowCallNode.getOutboundParameters();
           CustomParameter playernameparamO = new CustomParameter("playernameparam", "#{registrationBean.playerName}");

           CustomParameter playersurnameparamO = new CustomParameter("playersurnameparam", "#{registrationBean.playerSurname}");
           CustomParameter playerregistrationcodeO = new CustomParameter("playerregistrationcode", "349CF0YO122");

           outboundParameters.put("playernameparam", playernameparamO);
           outboundParameters.put("playersurnameparam", playersurnameparamO);
           outboundParameters.put("playerregistrationcode", playerregistrationcodeO);
      }
 }

 flowHandler.transition(context, sourceFlow, targetFlow,
 outboundCallNode, toViewId);
}

The target inbound parameters can be accessed as follows (the Map parameter cannot be altered):

Map<String, Parameter> inboundParameters = targetFlow.getInboundParameters();

Flow parameters are represented by the javax.faces.flow.Parameter abstract class. The CustomParameter class provides an implementation as follows:

public class CustomParameter extends Parameter {

 private String name;
 private String value;

 public CustomParameter(String name, String value) {
  this.name = name;
  this.value = value;
 }

 @Override
 public String getName() {
  return name;
 }

 @Override
 public ValueExpression getValue() {
  return createValueExpression(value, String.class);
 }

 private ValueExpression createValueExpression(String exp, Class<?> cls) {
  FacesContext facesContext = FacesContext.getCurrentInstance();
  ELContext elContext = facesContext.getELContext();
  return facesContext.getApplication().getExpressionFactory().
createValueExpression(elContext, exp, cls);
 }
}

See you in the next post about JSF/CDI application scope.

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