Book Image

Digital Java EE 7 Web Application Development

By : Peter Pilgrim
Book Image

Digital Java EE 7 Web Application Development

By: Peter Pilgrim

Overview of this book

Digital Java EE 7 presents you with an opportunity to master writing great enterprise web software using the Java EE 7 platform with the modern approach to digital service standards. You will first learn about the lifecycle and phases of JavaServer Faces, become completely proficient with different validation models and schemes, and then find out exactly how to apply AJAX validations and requests. Next, you will touch base with JSF in order to understand how relevant CDI scopes work. Later, you’ll discover how to add finesse and pizzazz to your digital work in order to improve the design of your e-commerce application. Finally, you will deep dive into AngularJS development in order to keep pace with other popular choices, such as Backbone and Ember JS. By the end of this thorough guide, you’ll have polished your skills on the Digital Java EE 7 platform and be able to creat exiting web application.
Table of Contents (21 chapters)
Digital Java EE 7 Web Application Development
Credits
About the Author
Acknowledgment
About the Reviewers
www.PacktPub.com
Preface
Index

The Faces Flows programmatic interface


The Faces Flows are powered by CDI. It is possible to write programmatic flows by declaring a POJO that produces a Faces Flow. In order to generate a flow dynamically, we must annotate a bean as a CDI producer and generate a flow instance.

Here is a basic SimpleFlowFactory class that supplies a Faces Flow:

import javax.faces.flow.builder.*;

public class SimpleFlowFactory implements Serializable {
  @Inject DocumentIdGenerator generator;

  @Produces @FlowDefinition
  public Flow createFlow( 
    @FlowBuilderParameter FlowBuilder flowBuilder)
  {
    final String documentId = generator.generate();
    flowBuilder.id(documentId, "simpleFlow");
    flowBuilder.viewNode("simpleFlow", 
      "/simpleFlow/simpleFlow.xhtml")
      .markAsStartNode();
    return flowBuilder.getFlow();
  }
}

We will annotate the createFlow() method as a CDI producer with the special qualifier, @FlowDefinition. We will also supply this method with a single argument, FlowBuilder, which is also annotated with @FlowBuilderParameter. These definitions, by the way, are found in the reserved imported package: javax.faces.flow.builder. We use an injected FlowBuilder to generate a specific flow. The actual node types are found in the javax.faces.flow package.

A Faces Flow requires a flow identifier, which is simpleFlow. We can optionally also define a specific document ID. We will use a dependent generator instance to demonstrate how this works. If there is no need for a document ID, then supply an empty string.

A Faces Flow requires at least one view, which is usually the default name in the XML configuration. In order to complete the first view node, we will define a ViewNode and the URI for the flow's view template: /simpleFlow/simpleFlow.xhtml. We will also need to set the flow's start node with the call set to markAsStartNode(), because a Faces Flow requires an initial node. The API for FlowBuilder is taken from the fluent builder pattern. At the end of the createFlow() method, we will generate a flow instance and return it. The JSF provider takes over the flow definition from this point onwards.

Remember that as we are building the flow ourselves, we don't have to follow all the rules of the XML configuration exactly. However, all the view templates for a flow definition must fall under one single folder in the root context of the web application.

Just because we defined a flow definition producer, we will still require the actually flow-scoped bean, namely:

@Named("simpleFlowController")
@FlowScoped("simple")
public class SimpleFlow implements Serializable {
  /* ... */
}

We will look at the specifics of each node type in the following sections.

The following UML class diagram illustrates the key types in the API:

UML class diagram for the Faces Flow API

ViewNode

With FlowBuilder, we can define the ViewNode instances. The actual type is a subclass of javax.faces.faces.ViewNode.

The following example of a code extends the last flow instance and provides additional view nodes:

@Produces @FlowDefinition
public Flow createFlow( 
  @FlowBuilderParameter FlowBuilder flowBuilder)
{
  final String documentId = generator.generate();
  flowBuilder.id(documentId, "simpleFlow");
  /* ... */
  flowBuilder.viewNode("page1", "/simpleFlow/page1.xhtml")
  flowBuilder.viewNode("page2", "/simpleFlow/page2.xhtml")
  flowBuilder.viewNode("page3", "/simpleFlow/page3.xhtml")
  return flowBuilder.getFlow();
}

The viewNode() method accepts two arguments. The first argument is the view node ID, which we will write in and refer to in the view content. The second argument is the view description language document identifier, which translates to the URI of the Facelets view.

The path of the URI can also be a relative URL. This means the eventual path is relative to the flow URI path. Otherwise, the URI must be absolute in terms of the web context root path, as we can see in the preceding code extract.

The viewNode() method returns a ViewBuilder type, which has only one additional markAsStartNode() method.

ReturnNode

We can also generate a ReturnNode instance from FlowBuilder. This is straightforward with the returnNode() method. The actual type is a subclass of javax.faces.faces.ReturnNode.

This is an extract of the createFlow() method that specifies a return node:

@Produces @FlowDefinition
public Flow createFlow( 
  @FlowBuilderParameter FlowBuilder flowBuilder)
{
  final String documentId = generator.generate();
  flowBuilder.id(documentId, "simpleFlow");
  /* ... */
  flowBuilder.returnNode("return-backup").fromOutcome("welcome");
  return flowBuilder.getFlow();
}

The returnNode() method accepts a single argument the view node ID and returns an instance of ReturnBuilder. The fromOutcome() method call specifies the view name of the outcome that we navigate to after returning from the current flow.

MethodCall

With the FlowBuilder instance, we can also create a method call node and add it to the Faces Flow. The actual type is a subclass of javax.faces.faces.MethodCallNode. Here is the POJO class:

@Produces @FlowDefinition
public Flow createFlow( 
  @FlowBuilderParameter FlowBuilder flowBuilder)
{
  /* ... */
  flowBuilder.methodCallNode("check-funds")  
    .expression(
      "#{loanController.verifyCustomer(customer.account)}", 
      new Class[]{String.class});  
    .defaultOutcome("no-funds-available")
  return flowBuilder.getFlow();
}

The methodCallNode() call accepts a view node ID and creates a MethodCallBuilder instance. This node requires an expression that informs JSF about the name of the bean and the method to invoke. Here, the method is verifyCustomer() on an accessible backing bean controller loanController. We can also pass the account record in the customer's details using an expression language. In case the target method invocation returns null or an empty string, we can specify a default outcome view identifier.

I recommend that you should reduce the coupling in a layered architecture as much as possible. Be careful about mixing too much presentation tier information with the business logic.

FlowCall

We will create a flow call node using the same abstract class type FlowBuilder. This node type invokes a nested Faces Flow. The flowCallNode() method accepts a single argument in the view node ID and returns a FlowCallBuilder instance. The actual type is a subclass of javax.faces.faces.FlowCallNode.

In this code extract, which is based on the Industrial sector carbon footprint discussion from Chapter 6, JSF Flows and Finesse, we will rewrite the flow definition XML file called footprint-flow.xml as the following SectorFlowFactory POJO class:

class SectorFlowFactory implements Serializable {
  /* ... */
  @Produces @FlowDefinition
  public Flow createFlow( 
    @FlowBuilderParameter FlowBuilder flowBuilder)
  {
    flowBuilder.id("", "sector-flow");
    /* ... */
    flowBuilder.flowCallNode("callFootprintFlow")
      .flowReference("", "footprint-flow)
      .outboundParameter("param1FromSectorFlow", 
        "param1 sectorFlow value")
      .outboundParameter("param2FromSectorFlow", 
        "param2 sectorFlow value")
      .outboundParameter("param3FromSectorFlow", 
        "#{sectorFlow.footprint}");
    return flowBuilder.getFlow();
  }
}

In the preceding code, the node is identified as callFootprintFlow. We must provide the node ID of the nest flow through a flowReference() method. The first argument of flowReference() is the flow document ID, which is an empty string here. The nested flow is identified as footprint-flow by the second argument.

We will use the outboundParameter() method on FlowCallBuilder in order to declare the outbound parameters that pass the data from the calling sector-flow to the nested footprint-flow. The first argument to outboundParameter() is the parameter name and the second argument is a literal string or an expression.

We can also rewrite the flow definition XML file from the nested flow programmatically. Here is an extract of the FootprintFlowFactory class:

class FootprintFlowFactory implements Serializable {
  /* ... */
  @Produces @FlowDefinition
  public Flow createFlow( 
    @FlowBuilderParameter FlowBuilder flowBuilder)
  {
    flowBuilder.id("", "footprint-flow");
    flowBuilder.inboundParameter("param1FromSectorFlow", 
      "#{flowScope.param1Value}");
    flowBuilder.inboundParameter("param2FromSectorFlow", 
      "#{flowScope.param2Value}");
    flowBuilder.inboundParameter("param3FromSectorFlow", 
      "#{flowScope.param3Value}");
    /* ... */
    return flowBuilder.getFlow();
  }
}

In this code, we notice that the footprint-flow ID must match the caller reference ID. For this Faces Flow, we will declare the inbound parameters with the inboundParameter() method call. The first argument is the parameter name and the second is an expression language reference that defines where the value is stored.

SwitchNode

We can use FlowBuilder to create the declaration of a switch node. These node types are composed of an association between a conditional statement and the outcome. The outcome is the view ID. A switch node has one or more conditional statements. It may also have a default outcome. During the processing of the flow, JSF executes each conditional statement in turn. If it evaluates to true (or rather, Boolean.TRUE), then the associated outcome is the chosen one. The actual type is a subclass of javax.faces.faces.SwitchNode.

Let 's see an example of the switch node in action in a POJO. Here is another class for a monetary loan decision maker called LoanDecisionFlowFactory:

class LoanDecisionFlowFactory implements Serializable {
  /* ... */
  @Produces @FlowDefinition
  public Flow createFlow( 
    @FlowBuilderParameter FlowBuilder flowBuilder)
  {
    flowBuilder.id("", "loan-application");
    /* ... */
    flowBuilder.switchNode("loan-decision")
      .defaultOutcome("home")
      .switchCase()  
        .condition("#{loanMaker.decision=='Agreed'}")
        .fromOutcome("loan-agreement")
      .switchCase()  
        .condition("#{loanMaker.decision=='Declined'}")
        .fromOutcome("loan-declined")
      .switchCase()  
        .condition("#{loanMaker.decision=='Pending'}")
        .fromOutcome("loan-pending")
    /* ... */
    return flowBuilder.getFlow();
  }
}

In the createFlow() method, we will create a Faces Flow identified as loan-application. We will then create a switch node declaration with switchNode() and identified as loan-decision. This method returns a SwitchBuilder type. This builder type has switchCase() that returns SwitchCaseBuilder. The other overloaded methods included are called defaultCome().

It is recommended that you also declare a default outcome with the defaultOutcome() method, which accepts a view ID. In this way, the flow always moves to the next viable location.

Given the SwitchBuilder builder type, we will declare the individual cases with the condition() and fromOutCome() methods. The condition() call accepts an expression string and it returns SwitchCaseBuilder. The only property on this builder is a valid outcome, which is a view identifier.

NavigationCase node

The final builder type in the API relates to a general abstract class type NavigationCaseBuilder. We can create a navigation case node with FlowBuilder. The actual type is a subclass of javax.faces.faces.FlowNode.

Here is a POJO from the financial services domain that is designed to allow the back office staff in an investment bank to post the process trades in a workflow:

class PostProcessTradeFlowFactory implements Serializable {
  /* ... */
  @Produces @FlowDefinition
  public Flow createFlow( 
    @FlowBuilderParameter FlowBuilder flowBuilder)
  {
    flowBuilder.id("", "post-process-trade-flow"); /* ... */
    flowBuilder.navigationCase()
      .fromOutcome("trade-check")
      .condition("#{trade.amount > global.risk.limit}")
      .toViewId("exceptional");

    flowBuilder.navigationCase()
      .fromOutcome("trade-check")
      .condition("#{trade.settlementCcy != 'USD'")
      .redirect().includeViewParams()
      .parameter("deskCodeNotice", "OUT_OF_CURRENCY_DEAL");
    /* ... */
    return flowBuilder.getFlow();
  }
}

In this PostProcessTradeFlowFactory POJO, we again have a createFlow() method, but this time we will call navigationCase() twice. This method has no arguments and returns a NavigationCaseBuilder type that creates the specified Faces Flow. A navigation case flow requires a trigger point, a conditional expression, and an outcome or redirection response.

In the first flow, we will call the fromOutcome() method on NavigationCaseBuilder in order to declare the JSF view ID that triggers the check. The condition() method defines the expression that must return a Boolean value. The toViewId() method defines the outcome view ID to navigate if the conditional expression evaluates as Boolean.TRUE. So, therefore, if the trade value exceeds the firm-wide exposure limit (trade.amount > global.risk.limit), then the flow navigates to the exceptional view.

With the second flow declaration, we will define a navigation case to handle an out-of-currency deal, which might be a Swap, Bond, or some other derivative. With the same trigger outcome trade check, we will trap on the expression for the currencies that settles in non-US dollars: trade.settlementCcy != 'USD'. With this navigation case node, we will execute an HTTP redirect that causes the customer to exit the entire flow. We will invoke redirect() and it will return NavigationCaseBuilder.RedirectBuilder. This nested abstract class has two methods: includeViewParams() and parameter(). The includeViewParams() method adds all of the view parameters to the redirect URL. The parameter() method adds the name and value pairs to the redirect URL. Remember that after the flow is completed, from a security digital developer's point of view, these parameters will be highly visible in the URL of the eventual HTTP GET request!

Builder types

The builder types—ViewBuilder, ReturnBuilder, MethodCallBuilder, FlowCallBuilder, SwitchBuilder, SwitchCaseBuilder, and NavigationCaseBuilder are abstract classes. They are implementations of the javax.faces.flow.builder.NodeBuilder interface. The Faces Flow API is an example of an embedded Domain Specific Language.