View RSS Feed

Spring Framework

Using Spring Web Flow Part 2

Rating: 3 votes, 2.33 average.
by , 11-30-2011 at 05:00 AM (11296 Views)
In the previous article, we looked at the components of Spring Web Flow. In this article, I will tie all the pieces together for the application.

As you know, Spring Web Flow provides a powerful controller to control the user navigation in case your application requires it. Below is the definition of a simple flow to carry out a booking process is shown graphically below:

Why Spring Web Flow
We know that defining and understanding page flow of a complex web application is difficult no matter which framework it is based on. Spring Web Flow allows you to represent the page flows of your application in a clear and simple way. Page flow of the application is visible just by looking at XML or java configuration.
Web flows are designed to be self contained, and thus are reusable multiple of times.
The technique to capture the page flow remains the same for all the cases and there are no specialized approaches for particular situations.

We know from the previous article that Spring Web Flow is composed of a set of states (Displaying a View or executing any Action etc.). Transition of the flow from one state to another is triggered by an event. This continues till the flow completes and enters the end-state. Let us have a look at a Web Flow for Holiday booking process flow. The UML state diagram for the same is below.

Java Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
"http://www.springframework.org/dtd/spring-webflow.dtd">

<webflow id="bookticket" start-state="obtainHolidayInfo">
	<action-state id="obtainHolidayInfo">
		<action bean="bookingActions" method="bindAndValidate" />
		<transition on="success" to="availableFlights" />
		<transition on="error" to="tryAgain" />
	</action-state>

	<action-state id="availableFlights">
		<action bean="bookingActions" />
		<transition on="success" to="displayFlights" />
	</action-state>

	<view-state id="displayFlights" view="showFlights">
		<transition on="startOver" to="cancel" />
		<transition on="select" to="selectShow" />
	</view-state>

	<action-state id="selectFlight">
		<action bean="bookingActions" />
		<transition on="success" to="isPersonalInfoRequired" />
	</action-state>

	<action-state id="availableHotels">
		<action bean="bookingActions" />
		<transition on="success" to="displayHotels" />
	</action-state>

	<view-state id="displayHotels" view="showHotels">
		<transition on="startOver" to="cancel" />
		<transition on="select" to="selectShow" />
	</view-state>

	<action-state id="selectHotel">
		<action bean="bookingActions" />
		<transition on="success" to="isPersonalInfoRequired" />
	</action-state>

	<decision-state id="isCarRequired">
		<if test="${requestScope.car == null}" then="enterCarReservation" />
		<if test="${requestScope.car}" then="enterCarReservation"
			else="displayReservationVerification" />
	</decision-state>

	<subflow-state id="enterCarReservation" flow="car">
		<attribute-mapper>
			<input value="${requestScope.car.id}" as="carId" />
		</attribute-mapper>
		<transition on="finish" to="displayReservationVerification" />
	</subflow-state>

	<decision-state id="isPersonalInfoRequired">
		<if test="${requestScope.person == null}" then="enterPersonalInformation" />
		<if test="${requestScope.person.preferences.alwaysConfirmPersonalInfo}"
			then="enterPersonalInformation" else="displayReservationVerification" />
	</decision-state>

	<subflow-state id="enterPersonalInformation" flow="person">
		<attribute-mapper>
			<input value="${requestScope.person.id}" as="personId" />
		</attribute-mapper>
		<transition on="finish" to="displayReservationVerification" />
	</subflow-state>

	<view-state id="displayReservationVerification" view="reservationVerification">
		<transition on="startOver" to="cancel" />
		<transition on="assignSeats" to="chooseSeatAssignments" />
		<transition on="book" to="book" />
	</view-state>

	<action-state id="book">
		<action bean="bookingActions" />
		<transition on="success" to="displayConfirmation" />
	</action-state>

	<end-state id="displayConfirmation" view="reservationConfirmation" />
	<end-state id="tryAgain" view="tryAgain" />
	<end-state id="cancel" view="home" />
</webflow>
If you look at the XML you can understand the logical flow (process flow) of the application even without looking at any other part of the code. You can see the webflow contains subflows, these subflows can act as modules of your application and are reusable.

Here is the definition for each of the components.

The Flow Definition
Starting with line 1 of the XML-based flow definition:
Java Code:
<webflow id="bookticket" start-state="obtainHolidayInfo">
...
</webflow>
The webflow element defines the flow, specifying its id and start-state>. The id is a unique identifier. When a new flow session is activated the start state is the first state to which the flow transitions on success. So here when a new session is activated for bookticket the first state is obtainHolidayInfo. Now in obtainHolidayInfo state definition.

Java Code:
<action-state id="obtainHolidayInfo">
   <action bean="bookingActions" method="bindAndValidate"/>
   <transition on="success" to="availableFlights"/>
   <transition on="error" to="tryAgain"/>
</action-state>
This is a action state and it will return a logical result on execution. So when the flow enters this business case (obtainHolidayInfo) bindAndValidate method is called with bookingActions identifier. If this process is successful, availableFlights state is entered otherwise tryAgain state is entered. The Spring bookingActions bean definition in the web-context.xml will be as:
web-context.xml
Java Code:
<bean id="bookingActions"
class="com.acme.springexamples.bookticket.BookingActions">
   <property name="bookingAgent" ref="myBookingAgent"/>
</bean>
The actions are managed and configured via dependency injection via Spring.Now let us move on to the next action state:

Java Code:
<action-state id="availableFlights">
<action bean="bookingActions"/>
 <transition on="success" to="displayFlights"/>
</action-state>
Here we pass the validated Holiday object as input and it will return a collection of show timings. The actual code will look like:

Java Code:
public class BookingActions extends FormAction {
 ...
public Event availableFlights(RequestContext context) {
       Holiday holiday = (Holiday)context.getRequestScope().getAttribute("holiday");
       Collection<ShowFlights> showFlights = bookingAgent.availableFlights(holiday);
       context.getRequestScope().setAttribute("showFlights", showFlights);
    return success();
   }
}
The showFlights method availableFlights will be called when the flow enters the availableFlights state; this is generic for all the states. When a state is entered a method is invoked on the target action bean. And on the successful execution of this method the showFlights collection is returned and the state is transitioned to displayShowFlightsView

This view displays all the available shows for that holiday (from show Flights collection).
Java Code:
<view-state id="displayFlights" view="showFlights">
 <transition on="startOver" to="cancel"/>
 <transition on="select" to="selectShow"/>
</view-state>
This is a view state and the flow will be paused for the user response. Here the user can either select or startOver. On select event the flow will enter the selectShow state and on startOver the flow will enter the cancel state depending on the user input. On the client side the id of the executing flow is tracked and the input values to the next event are also provided using this id.

The jsp would look like the following:
Java Code:
<input type="hidden" value="<c:out value="${flowExecution.id}"/>">
The hotel and the showHotel methods are exactly the same as the showFlights. You will see that the code and configuration are exactly the same except that the name ends with Hotels instead of Flights

Java Code:
<view-state id="displayHotels" view="showHotels">
     <transition on="startOver" to="cancel"/>
     <transition on="select" to="selectShow"/>
</view-state>
The “Is Personal Info Required?” Decision State
After the user selects a show the flow has to make a dynamic decision where to go next depending on the state of the user. If the user is logged in you may want to redirect him to the booking page and if he is not logged in you may want him to enter his details – like name and credit card number.
For this dynamic decision we use the decision state. The definition for the decision state is below:
Java Code:
<decision-state id="isPersonalInfoRequired">
 <if test="${requestScope.person == null}" then="enterPersonalInformation"/>
 <if test="${requestScope.person.preferences.alwaysConfirmPersonalInfo}"
 then="enterPersonalInformation" else="displayReservationVerification"/>
</decision-state>
Cars SubFlow State
Adding a car reservation is independent of the application logical flow. An individual can decide to add a car reservation to the booking. This can be done without affecting the booking of the holiday. For such an independent flow process we can use Subflow state.
enterPersonalInformation subflow state:

Java Code:
<subflow-state id="enterCarReservation" flow="car">
   <attribute-mapper>
   <input value="${requestScope.carId}" as="carId"/>
   </attribute-mapper>
   <transition on="finish" to="displayReservationVerification"/>
</subflow-state>
The attribute-mapper element maps attributes to and from the subflow using the attribute id. And when the subflow completes the control is returned back to parent flow. In our business flow when the enterCarReservation state is entered the car flow is created as the child of the current flow ( Webflow in this case). The carId attribute is passed to the child flow as input and the processing of the subflow is independent of the parent flow from here on till it completes. On the completion of the subflow, the parent flow resumes and depending on the result of the child flow it determines which state to transition to.

Similar to the car reservation sub flow, maintaining personal is independent of the application logical flow a personal can update his personal information without booking any tickets. For such an independent flow process we can use Subflow state.
enterPersonalInformation subflow state:

Java Code:
<subflow-state id="enterPersonalInformation" flow="person">
  <attribute-mapper>
 <input value="${requestScope.person.id}" as="personId"/>
 </attribute-mapper>
 <transition on="finish" to="displayReservationVerification"/>
</subflow-state>
The attribute-mapper element maps attributes to and from the subflow using the attribute id. And when the subflow completes the control is returned back to parent flow. In our business flow when the enterPersonalInformation state is entered the personal flow is created as the child of the current flow (Webflow in this case). The personId attribute is passed to the child flow as input and the processing of the subflow is independent of the parent flow from here on till it completes. On the completion of the subflow, the parent flow resumes and depending on the result of the child flow it determines which state to transition to.

After a show is successfully booked our flow ends and we want to display a confirmation.
Java Code:
<end-state id="displayConfirmation" view="reservationConfirmation"/>
This is the end-state and when the bookticket flow enters this state the flow is terminated and the resources are cleaned up for reuse (an automatic process in Spring Web Flow).

Note: The entered end state ID is used as grounds for a state transition in the resuming parent flow's subflow state. You can see this in action by taking a look at the "enterPassengerInformation" subflow state definition. Note how it responds to the "finish" result of the subflow, which corresponds to a "finish" end state within the passenger flow.
Flow Deployment. Now that we have the flow definition, we would like to use it now with our Spring application. All we need to do is this:

Java Code:
<bean name="/book.htm" class="org.springframework.web.flow.mvc.FlowController">
 <property name="flow">
 <ref bean="bookFlow"/>
 </property>
</bean>
 
<bean id="bookFlow" class="org.springframework.web.flow.config.XmlFlowFactoryBean">
 <property name="location" value="classpath:bookticket-flow.xml"/>
</bean>
Spring Web Flow should be used in application where you want user to have controlled navigation and the navigation is complex and the process flow is large. Because Spring Web Flow is stateful it is good for complex business flows. It should not be used in simple applications with a small flow or which need a stateless solution.

Now here is the rest of the key pieces of that you would need to complete the creation of the application using Spring Web Flow.

Setting environment variables: You should have spring-webflow-1.0.jar, spring-binding-1.0.jar and spring.jar files in your classpath. Now that the environment is ready we are all set to design our first flow. We will design a login page. On startup a form view will be displayed in the browser. On submit validate the form input data and login the user. We can now create the flow definition for our login process.

Java Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "-//SPRING//DTD WEBFLOW 1.0//EN"
 "http://www.springframework.org/dtd/spring-webflow-1.0.dtd">

<flow start-state="displayLoginForm">

 <view-state id="displayLoginForm" view="form">
 <entry-actions>
 <action bean="loginForm" method="setupForm"/>
 </entry-actions>
 <transition on="submit" to="processLogin">
 <action bean="loginForm" method="bindAndValidate"/>
 </transition>
 </view-state>

 <action-state id="processLogin">
 <action bean="loginForm" method="processLogin"/>
 <transition on="success" to="finish"/>
 </action-state>
 <end-state id="finish" view="success"/>
</flow>
1. Creating flow action: Define the formAction bean and validate and submit logic.

Java Code:
public class HolidayLoginAction extends FormAction {
 public HolidayLoginAction() {
 setFormObjectClass(FormObject.class);
 }

 public static class FormObject implements Serializable {
 private String user;
  private String password;

 public String getUser() {
 return user;
 }

 public void setUser(String user) {
 this.user = user;
 }
 public String getPassword(){
 return password;
 }

 public void setPassword(String password) {
 this.password = password;
 }

 }

 public Event processLogin(RequestContext context) throws Exception {
 FormObject formObject = (FormObject)getFormObject(context);
 // todo login logic
 return success();
 }

}
2. Now we will deploy our flow using Spring Application Context. We will use XmlFlowRegistryFactoryBean.
Java Code:
<bean id="flowRegistry" class="org.springframework.webflow.registry.XmlFlowRegistryFactoryBean">
 <property name="flowLocations" value="/WEB-INF/flows/myflow.xml"/>
</bean>

<bean id="formAction" class="net.javabeat.example.HolidayLoginAction"/>
3. Define a FlowController and ViewResolver:
dispatcher-servlet.xml
Java Code:
<bean name="/myApp.htm" class="org.springframework.webflow.executor.mvc.FlowController">
 <property name="flowLocator" ref="flowRegistry"/>
</bean>

<!-- Maps flow view-state view names to JSP templates with JSTL support -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
 <property name="prefix" value="/WEB-INF/jsp/"/>
 <property name="suffix" value=".jsp"/>
</bean>
4. Next, create the views of the flow.
/WEB-INF/jsp/form.jsp
XML Code:
<html>
<head>
 <title>My Login Page</title>
</head>
<body>
<p>
 <table>
 <form action="myApp.htm">
 <tr>
 <td>User</td>
 <td><input type="text" name="user" size="25"/></td>
 </tr>
 <tr>
 <td>Password</td>
 <td><input type="text" name="password" size="25"/></td>
 </tr>

 <tr>
 <td colspan="2" align="right">
 <input name="_eventId_submit" type="submit">
 <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}">
 </td>
 </tr>
 </form>
 </table>
</p>
</body>
</html>

_eventId and _flowExecutionKey parameters are required for the view to submit to the server. _flowExecutionKey is used to maintain the state of the flow. This way the server identifies the current flow of the client. _eventId is used to signal which event occurred on the client, in our login application it will submit “submit” if the submit button is pressed. And depending on its value the flow transition occurs to the next step.

Now you are in a position to deploy and test our application. Spring Web Flow is the best solution for managing a complex business process flow. If you need to manage the flow within a part of the application, then you should seriously consider Spring Web Flow for this.

Submit "Using Spring Web Flow Part 2" to Facebook Submit "Using Spring Web Flow Part 2" to Digg Submit "Using Spring Web Flow Part 2" to del.icio.us Submit "Using Spring Web Flow Part 2" to StumbleUpon Submit "Using Spring Web Flow Part 2" to Google

Updated 12-05-2011 at 01:45 PM by Spring Framework

Categories
MVC , Spring 3 , Web Flow , Spring Flow , Webflow

Comments