Using Spring Web Flow Part 1
by, 11-30-2011 at 04:52 AM (12845 Views)
In a previous article, I gave an introduction to Spring Web Flow. In this article, I will delve into the details of how to use Spring Web Flow.
In Spring Web Flow, a flow is defined by three primary elements: states, transitions, and flow data. States are points in a flow where something happens. Consider if you want to plan a short holiday in Florence. You need to first choose your flights, then you choose your hotel, you might also choose a car, etc. Similarly, as a flow progresses, it collects some data (i.e. the current condition of the flow) and continues along until it reaches the end state which in terms of the holiday in Florence would be making payment.
Letís take a closer look at how these three elements are defined in Spring Web Flow. Spring Web Flow defines five different kinds of state. The selection of states provided by Spring Web Flow makes it possible to construct virtually any arrangement of functionality into a conversational web application. Though not all flows will require all of the states you will most probably end up using most of them at one time or another.
The start-state, when a flow is created the initial state of the flow is defined by the start-state attribute in the Webflow.
An action-state executes an action and returns a logical result on its completion. The next to which the flow will be transitioned to depends on the result of this state
A view-state when entered pauses the flow and returns the control to the client/user and the flow is resumed on the user/client event which resumes the flow and triggers the transition to the state depending on the user/client input or decision.
A decision-state is used to determine the next state in the dynamic way or at runtime. If our next state depends on some attributes or properties ( eg. If users are not logged then redirect them to login page ).
A subflow-state is used to represent independent flows which are not dependent on the main flow. A subflow is created as child of main flow (parent flow). When a subflow is called the parent flow is suspended until the child flow completes. This helps to maintain the application as a set of sub-modules (Subflows) which can be used multiple times. The Subflow can be child of another Subflow or of the root flow.
End-state signifies the end of the flow. When a flow enters the end-state the active flow session is terminated. If the end-state of the root flow is entered the resources associated with it are cleaned up automatically.
View states are used to display information to the user and to offer the user an opportunity to play an active role in the flow. The actual view implementation could be any of the views supported by Spring MVC, but is often implemented in JSP. Within the flow definition XML file, the <view-state> element is used to define a view state:
In this simple example, the id attribute serves a dual purpose. It identifies the state within the flow. Also, because no view has been specified otherwise, it specifies welcome as the logical name of the view to be rendered when the flow reaches this state. If youíd rather explicitly identify another view name, then you can do so with the view attribute:Java Code:<view-state id="welcome" />
<view-state id="welcome" view="greeting" />
If a flow presents a form to the user, you may want to specify the object to which the form will be bound. To do that, set the model attribute:
Below Iíve specified that the form in the takePayment view will be bound to the flow-scoped paymentDetails object.Java Code:<view-state id="takePayment" model="flowScope.paymentDetails"/>
Whereas view states involve the users of the application in the flow, action states are where the application itself goes to work. Action states typically invoke some method on a Spring-managed bean and then transition to another state depending on the out- come of the method call. In the flow definition XML, action states are expressed with the <action-state> element. Hereís an example:
Although itís not strictly required, <action-state> elements usually have an <evaluate> element as a child. The <evaluate> element gives an action state some- thing to do. The expression attribute is given an expression thatís evaluated when the state is entered. In this case, expression is given a SpEL2 expression which indicates that the saveOrder() method should be called on a bean whose ID isJava Code:<action-state id="saveOrder"> <evaluate expression="holidayFlowActions.saveOrder(order)" /> <transition to="thankYou" /> </action-state>
Itís possible for a flow to be purely linear, stepping from one state to another without taking any alternate routes. But more often a flow branches at one point or another, depending on the flowís current circumstances.
Decision states enable a binary branch in a flow execution. A decision state will evaluate a Boolean expression and will take one of two transitions, depending on whether the expression evaluates to true or false. Within the XML flow definition, decision states are defined by the <decision-state> element. A typical example of a decision state might look like this:
As you can see, the <decision-state> element doesnít work alone. The <if> element is the heart of a decision state. Itís where the expression is evaluated. If the expression evaluates to true, then the flow will transition to the state identified by the then attribute. But if itís false, then the flow will transition to the state named in the else attribute.Java Code:<decision-state id="checkCarDeliveryArea"> <if test="holidayFlowActions.checkCarDeliveryArea(airport.zipCode)" then="addCustomer" else="deliveryWarning" /> </decision-state>
You probably wouldnít write all of your applicationís logic in a single method. Instead, youíd probably break it up into multiple classes, methods, and other structures. In the same way, itís a good idea to break flows down into discrete parts. The <subflow-state> element lets you call another flow from within an executing flow. Itís analogous to calling a method from within another method. A <subflow-state> might be declared as follows:
Here, the <input> element is used to pass the order object as input to the subflow. And, if the subflow ends with an <end-state> whose ID is orderCreated, then the flow will transition to the state whose ID is payment. As for end states, thatís what weíll look at next.Java Code:<subflow-state id="order" subflow="reservation/order"> <input name="order" value="order"/> <transition on="orderCreated" to="payment" /> </subflow-state>
Eventually all flows must come to an end. And thatís what theyíll do when they transition to an end state. The <end-state> element designates the end of a flow and typically appears like this:
When the flow reaches an <end-state>, the flow ends. What happens next depends on a few factors:Java Code:<end-state id="customerReady" />
- If the flow thatís ending is a subflow, then the calling flow will proceed from the <subflow-state>. The <end-state>ís ID will be used as an event to trigger the transition away from the <subflow-state>.
- If the <end-state> has its view attribute set, the specified view will be rendered. The view may be a flow-relative path to a view template, prefixed with externalRedirect: to redirect to some page external to the flow, or prefixed with flowRedirect: to redirect to another flow.
- If the ending flow isnít a subflow and no view is specified, then the flow simply ends. The browser ends up landing on the flowís base URL, and with no current flow active, a new instance of the flow begins.
Itís important to realize that a flow may have more than one end state. Since the end stateís ID determines the event fired from a subflow, you may want to end the flow through multiple end states to trigger different events in the calling flow. Even in flows that arenít subflows, there may be several landing pages that follow the completion of a flow, depending on the course that the flow took.
Transitions connect the states within a flow. Every state in a flow, with the exception of end states, should have at least one transition so that the flow will know where to go once that state has completed. A state may have multiple transitions, each one representing a different path that could be taken upon completion of the state. A transition is defined by the <transition> element, a child of the various state elements (<action-state>, <view-state>, and <subflow-state>). In its simplest form, the <transition> element identifies the next state in the flow:
The to attribute is used to specify the next state in the flow. When <transition> is declared with only a to attribute, the transition is the default transition for that state and will be taken if no other transitions are applicable. More commonly transitions are defined to take place upon some event being fired. In a view state, the event is usually some action taken by the user. In an action state, the event is the result of evaluating an expression. In the case of a subflow state, the event is determined by the ID of the subflowís end state. In any event (no pun intended), you can specify the event to trigger the transition by specifying it in the on attribute:Java Code:<transition to="customerReady" />
Shown above the flow will transition to the state whose ID is lookupCustomer if a phoneEntered event is fired. The flow can also transition to another state in response to some exception being thrown. For example, if a customer record canít be found, you may want the flow to transition to a view state that presents a registration form. The following snippet shows that kind of transition:Java Code:<transition on="phoneEntered" to="lookupCustomer"/>
The on-exception attribute is much like the on attribute, except that it specifies an exception to transition on instead of an event. In this case, a CustomerNotFound- Exception will cause the flow to transition to the registrationForm state.Java Code:<transition on-exception= "com.springexample.holidays.service.CustomerNotFoundException" to="registrationForm" />
After youíve created a flow, you may find that there are several states that share some common transitions. For example, I wouldnít be surprised to find the following <transition> sprinkled all over a flow:
Rather than repeat common transitions in multiple states, you can define them as global transitions by placing the <transition> element as a child of a <global- transitions> element. For example:Java Code:<transition on="cancel" to="endState" />
With this global transition in place, all states within the flow will have an implicit cancel transition.Java Code:<global-transitions> <transition on="cancel" to="endState" /> </global-transitions>
Weíve talked about states and transitions. Before we get busy writing flows, letís look at flow data, the remaining member of the web flow triad.
If youíve ever played one of those old text-based adventure games, you know that as you move from location to location, you occasionally find objects laying around that you can pick up and carry with you. Sometimes you need an object right away. Other times, you may carry an object around through the entire game not knowing what itís foróuntil you get to that final puzzle and find that itís useful after all.
In many ways, flows are like those adventure games. As the flow progresses from one state to another, it picks up some data. Sometimes that data is only needed for a little while (maybe just long enough to display a page to the user). Other times, that data is carried around through the entire flow and is ultimately used as the flow completes.
Flow data is stored away in variables that can be referenced at various points in the flow. It can be created and accumulated in several ways. The simplest way to create a variable in a flow is by using the <var> element:
Here, a new instance of a Customer object is created and placed into the variable whose name is customer. This variable will be available to all states in a flow.Java Code:<var name="customer" class="com.springexample.holiday.domain.Customer"/>
As part of an action state or upon entry to a view state, you may also create variables using the <evaluate> element:
In this case, the <evaluate> element evaluates an expression (a SpEL expression) and places the result in a variable named toppingsList thatís view-scoped. (Weíll talk more about scopes in a moment.)Java Code:<evaluate result="viewScope.toppingsList" expression="T(com.springexample.holiday.domain.Hotel).asList()" />
Similarly, the <set> element can set a variableís value: <set name="flowScope.holiday"
value="new com.springexample.holiday.domain.Holiday()" />
The <set> element works much the same as the <evaluate> element, setting a vari- able to the resulting value from an evaluated expression. Here, weíre setting a flow- scoped holiday variable to a new instance of a Holiday object.
Youíll see more specifics on how these elements are used in an actual flow when we get to section 8.3 and start building a real working web flow. But first, letís see what it means for a variable to be flow-scoped, view-scoped, or use some other scope.
Scoping Flow Data
The data carried about in a flow will have varying lifespans and visibility, depending on the scope of the variable itís kept in. Spring Web Flow defines five scopes, as described in table 8.2.
When declaring a variable using the <var> element, the variable is always flow-scoped within the flow defining the variable. When using <set> or <evaluate>, the scope is specified as a prefix for the name or result attribute. Below is an example of assign a value to a flow-scoped variable named the Answer:
In the next article, itís time to piece them together into a full-blown, fully functional web flow.Java Code:<set name="flowScope.theAnswer" value="42"/>