Spring Web Flows

A Practical Guide
End-of-Life Notice
This article discusses the now defunct Ervacon Spring Web Flow system and not the official Spring Web Flow sub project of the Spring Framework. The concepts are very similar, but the technical details differ.
We advice all users of Ervacon Spring Web Flow to migrate to the current release of Spring Web Flow. Visit the Spring Web Flow Portal for more information, download links and support options.
This article introduces Spring web flows. Basic web flow principles are explained and illustrated. The article also serves as a practical guide to building a web application using web flows and the Spring framework by discussing a sample application.

The reader is assumed to have a basic understanding of J2EE web applications, XML, the Spring framework and particularly the Spring web MVC framework. Check the resources for links to background information.
02/11/2004 - By Erwin Vervaet (erwin@ervacon.com)

19/11/2004 UPDATE: The article was updated to reflect some refactoring in the source code starting with version 0.6.0 of the Spring web flow system: the class com.ervacon.springframework.web.servlet.mvc.webflow.WebFlow was renamed to com.ervacon.springframework.web.servlet.mvc.webflow.SimpleWebFlow.

Introduction

Traditionally, defining the page flow in a web application has been a less than intuitive process. Frameworks like Struts and Spring force you to cut the page flow into individual controllers and views. Struts, for instance, will map a request to an action. The action will then select a view and forward to it. Although this is a simple and functional system, it has a major disadvantage: the overall page flow of the web application is not at all clear from looking at the action definitions in the struts-config.xml file. Flexibility also suffers since actions cannot easily be reused.

The Spring web MVC framework offers a slightly higher level of functionality: form controllers that implement a predefined work flow. Two such controllers are provided out of the box: SimpleFormController and AbstractWizardFormController. However, these are still hard coded examples of a more general work flow concept.

This is where Spring web flows come in. They allow you to represent the page flow in (part of) a web application in a clear and simple way. As we will see, this has several advantages:

For now it suffices to say that a web flow is composed of a set of states. A state is a point in the flow where something happens: for instance showing a view or executing an action. Each state has one or more transitions that are used to move to another state. A transition is triggered by an event. To give you an impression of what a web flow might look like, the following piece of XML defines a web flow roughly equivalent to the work flow implemented by the SimpleFormController. A more detailed explanation of web flow principles will follow later on in the article.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Simple Form Flow">
	<start-state state="form"/>

	<view-state id="form" view="form">
		<transition name="submit" to="bindAndValidate"/>
	</view-state>

	<action-state id="bindAndValidate">
		<action name="bindAndValidateAct" bean="bindAndValidateAction"/>
		<transition name="bindAndValidateAct.ok" to="doSubmit"/>
		<transition name="bindAndValidateAct.error" to="form"/>
	</action-state>

	<action-state id="doSubmit">
		<action name="doSubmitAct" bean="doSubmitAction"/>
		<transition name="doSubmitAct.ok" to="success"/>
		<transition name="doSubmitAct.error" to="endError"/>
	</action-state>

	<view-state id="success" view="success"/>

	<end-state id="endError"/>
</web-flow>

Readers that are familiar with business process management (BPM) will realize that web flows are a specialized case of a general purpose work flow, so they could theoretically be implemented using a general purpose BPM system like jBPM (see resources). Since simplicity is an important design goal for the Spring web flow system, it does not use such a general purpose work flow engine. Instead we have a simple web flow implementation targeting the definition of page flows in a web application.

The source code for the sample application discussed in the remainder of this article can be downloaded using this link: PhoneBook.war . It might be a good idea to download and unzip this now so you can study it while reading through the article.

Sample Application

The simple application we will use to illustrate the functionality offered by Spring web flows is a phone book application. This is an application typically found on company intranets and most of you will probably be familiar with its concepts. Basically it allows you to locate an employee of the company using some search criteria. Once you have found the right person, you can consult detailed information such as phone number, desk location, manager, colleagues and so on. Figure 1 sketches the basic requirements and page flow of this phone book application.

Phone Book Application Sketch
Figure 1. Overview of the sample application

As the sketch illustrates, the application really consists of two modules: a search module that allows us to locate a particular person, and a detail module that displays details for a particular person. The search module will use the detail module to display details for one of the people in the search result. The sketch also shows that we can directly access the details for a person's colleague from the detail page. This means that the detail module will also recursively use the detail module to show colleague details.
Later on in the article, we will see that we can define each module in a separate web flow. This means we will end up with 2 flows: a search flow and a detail flow.

Since our focus in this article is the implementation of the web interface of the application, we will just provide a basic business layer containing hard-coded dummy data. The domain objects are contained in the package com.ervacon.springframework.samples.phonebook.domain. We have 4 business classes:

With the business functionality out of the way, we are ready to go and develop the web interface for our phone book application with Spring web flows.

Spring MVC Setup

Before we can start using the web flow controller, we need to configure a basic Spring web application. The first thing we must do is make sure we have the necessary jar files in our /WEB-INF/lib directory. A Spring web application using web flows requires 4 jars in the classpath: spring.jar which contains the Spring framework itself; commons-logging.jar is needed for logging; webflow.jar contains the web flow controller implementation and finally jdom.jar which is used internally to read and analyze web flow XML files.

Since this will be a normal J2EE web application, we'll need a web.xml deployment descriptor in the /WEB-INF directory that describes the elements used in the application. The deployment descriptor for the sample application is shown below. It defines the following things:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
	"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp">
	<servlet>
		<servlet-name>contextLoader</servlet-name>
		<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet>
		<servlet-name>phoneBook</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>2</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>phoneBook</servlet-name>
		<url-pattern>/phoneBook/*</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	<taglib>
		<taglib-uri>/WEB-INF/tld/spring.tld</taglib-uri>
		<taglib-location>/WEB-INF/tld/spring.tld</taglib-location>
	</taglib>
</web-app>

The next thing we need in a Spring MVC application is a root Spring application context. It is a best practice to define your business objects in this application context. This way you can cleanly separate the definition of your business objects from any web application artifacts. Let's follow this best practice and create a file /WEB-INF/applicationContext.xml that defines our business facade: the phoneBook bean.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
	"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="phoneBook" class="com.ervacon.springframework.samples.phonebook.domain.PhoneBook">
	</bean>
</beans>

Finally we need to properly configure the dispatcher servlet that we defined in web.xml above. By default this servlet will read its configuration from a Spring application context stored in /WEB-INF/servletName-servlet.xml. In our case this will be /WEB-INF/phoneBook-servlet.xml. The only thing we really need to configure is a view resolver. This object is responsible for translating symbolical view names (e.g. "criteria") to actual view implementations (e.g. a JSP file /WEB-INF/jsp/criteria.jsp). This is done using the InternalResourceViewResolver as shown below. So in the case of our sample application, the pages will be located in a directory called /WEB-INF/jsp.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
	"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix"><value>/WEB-INF/jsp/</value></property>
		<property name="suffix"><value>.jsp</value></property>
	</bean>
</beans>

We leave all other dispatcher servlet configuration at its default values. This implies we will be using a simple BeanNameUrlHandlerMapping to locate the controller that will handle a request. Consult the Spring framework reference documentation for further details on how to configure the dispatcher servlet (see resources).

The Web Flow Controller

Let's now investigate how web flows are integrated into the Spring framework. The component that does this integration is a specialized Spring web controller implementation: com.ervacon.springframework.web.servlet.mvc.webflow.WebFlowController. This controller will load a web flow when it is initialized by Spring. As any Spring web controller, it needs to be defined as a normal JavaBean in the application context of the dispatcher servlet. In our case we need to include the following bean definitions in the phoneBook-servlet.xml file for our search flow. Similar definitions need to be included for the detail flow.

<bean id="search" name="/search" class="com.ervacon.springframework.web.servlet.mvc.webflow.WebFlowController">
	<property name="cacheSeconds"><value>0</value></property>
	<property name="webFlowName"><value>searchFlow</value></property>
</bean>

<bean id="searchFlow" class="com.ervacon.springframework.web.servlet.mvc.webflow.SimpleWebFlow">
	<property name="webFlowResource"><value>/WEB-INF/search-flow.xml</value></property>
</bean>

As you can see, the web flow controller references the actual web flow by name and not using a real bean reference. This is because the controller will need the name of the flow to be able to reacquire a reference to a flow from the application context (using getBean()). The web flow is defined as another JavaBean of type com.ervacon.springframework.web.servlet.mvc.webflow.SimpleWebFlow with a single property: the name of the XML file that holds the flow definition. Our sample application will have 2 such files: /WEB-INF/search-flow.xml and /WEB-INF/detail-flow.xml. Also note that all caching is disabled by setting the cacheSeconds property to 0. This is functionality the web flow controller inherits from the Spring AbstractController.

Since the web flow controller needs to keep track of the state of the flows it manages, it requires an HTTP session to be present. The requireSession property, also inherited from AbstractController, will be forced to "true". In practice this means that we will need to establish a session before invoking the web flow controller. Since we're developing a simple sample application, we'll just add the following JSP directive to our welcome page index.jsp, ensuring that we have an active HTTP session when the user uses the sample application.

<%@ page session="true" %>

Remember that we are using the BeanNameUrlHandlerMapping, locating handlers for a request by obtaining a bean name from the request. If we assume the sample application will be available under the context root /PhoneBook, a request of the form http://server/PhoneBook/phoneBook/search will be mapped to the search web flow controller defined above. similarly, http://server/PhoneBook/phoneBook/detail will be handled by the detail web flow controller.

When the web flow controller is invoked by the dispatcher servlet to handle a request, it examines the request parameters to determine what to do. The following request parameters are recognized:

Name Description
_flowId The value of this parameter gives the id of a previously started flow. When it is not present, a new flow will be started, with a new unique id.
_currentState This parameter indicates the state of the flow in which the event should be executed. It is not used when starting a new flow and optional for an existing flow. It allows you to freely jump around in a flow, not restricting you to the actual current state. This feature should really only be used when there is no other elegant solution.
_event The event that will be triggered in the current state of the flow. It is required when accessing an existing flow and not used when starting a new flow.

Using the request parameters specified above, the web flow controller implements the following logic:

  1. When a request comes in, the "_flowId" parameter is retrieved from the request.
    • If it is present, the controller attempts to load the identified, existing flow from the HTTP session.
    • If it is not present, the controller starts a new instance of its web flow and generates an id to uniquely identify it. The state of this new flow is stored in the HTTP session.
  2. For an existing flow, the controller will use the value of the "_event" request parameter to trigger a transition defined in the current state of the flow.
  3. The outcome of the steps above is a reference to a model and a view to render. Before returning this model-and-view combination to the dispatcher servlet for further processing, the web flow controller adds the flow id and the current state to the model. These values will be exposed to the view using the keys "flowId" and "currentState".

We now have enough information to add a link to our search flow in the index.jsp welcome page. Since we want to start a new flow, we don't need any request parameters, we just have to use a link that will be handled by the search web flow controller. The following piece of HTML does exactly that:

<A href="phoneBook/search">Phone Book</A>

Since the URL of the welcome page will be of the form http://server/PhoneBook, this relative URL will resolve to http://server/PhoneBook/phoneBook/search. As we explained before, this will invoke the web flow controller managing the search flow.

Web Flow Principles

With all the infrastructure in place, we are now ready to start looking at web flows and what they can do for you. Technically, a Spring web flow is nothing more than an XML file representation of the page flow of a web application. This XML format is defined in web-flow.dtd. To properly indicate that an XML file contains a web flow definition, it should contain the following document type declaration:

<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

You might be wondering why the web flow XML files don't just use the normal Spring beans syntax, as defined by spring-beans.dtd. This is a deliberate choice. It would indeed be possible to define a web flow using the normal Spring beans syntax. However, this would be counter intuitive and hard to read. The specialized web flow XML syntax aims to be simple and clear. Also, each web flow is closely integrated with a Spring application context, so beans used by the web flow will still be defined using the Spring beans syntax. Both formats coexist, they do not compete with each other.

The web flow DTD defines that a flow is composed of a set of states. Each state will have a unique id in the flow. The following state types are supported.

As explained in the introduction, each state in a web flow (except for the start and end states) defines a number of transitions to move from one state to another. A transition is triggered by an event signaled inside a state. The way a state signals events depends on the state type. For a view state, the event is based on user input submitted to the controller using the "_event" request parameter. In case of an action state, the executed actions signal events. Flow-states signal events based on the outcome of the sub flow they executed.

To step away from theory, lets try to convert the page flow of the sample application, as depicted in Figure 1, into a web flow. As we mentioned before, our phone book application really consists of two modules: a search module and a detail module. Starting with just the view states for the search module, we end up with the following flow definition.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Search Flow">
	<start-state state="criteria"/>

	<view-state id="criteria" view="criteria">
		<transition name="search" to="results"/>
	</view-state>

	<view-state id="results" view="results">
		<transition name="newSearch" to="criteria"/>
		<transition name="detail" to="...detail module..."/>
	</view-state>
</web-flow>

As you can see, this closely follows our initial page flow design. Note that this flow does not define an end state. This is typical for web flows since there are many web applications that never really end. The flow for the detail module is very similar (see below). In this case we do have an end state since we need to end this flow and return to the calling flow when going back.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Detail Flow">
	<start-state state="detail"/>

	<view-state id="detail" view="detail">
		<transition name="back" to="endOk"/>
		<transition name="colleagueDetail" to="...detail module..."/>
	</view-state>

	<end-state id="endOk"/>
</web-flow>

These web flow definitions reference three views (using the view attribute): "criteria", "results" and "detail". Given the view resolver definition that we're using (discussed earlier), we end up with three corresponding JSP pages: /WEB-INF/jsp/criteria.jsp, /WEB-INF/jsp/results.jsp and /WEB-INF/jsp/detail.jsp. Let's now look at the implementation of these JSPs.

Views

Implementing views used in a Spring web flows based application is not really different from view implementation in any other Spring web application. The only point of interest is linking back to the flow controller and signaling an event. The sample application uses JSP as view technology. To keep things as simple as possible, only basic JSP functionality is used, with Java scriptlets in the pages. The JSTL is not used.

Let's start by looking at the "criteria" view, implemented in /WEB-INF/jsp/criteria.jsp. This page shows a simple input form submitting phone book search criteria to the server.

<FORM name="searchForm" action="search">
	<INPUT type="hidden" name="_flowId" value="<%=request.getAttribute("flowId") %>">
	<INPUT type="hidden" name="_event" value="search">

	<TABLE>
		<TR>
			<TD>First Name</TD>
			<TD><INPUT type="text" name="firstName" value=""></TD>
		</TR>
		<TR>
			<TD>Last Name</TD>
			<TD><INPUT type="text" name="lastName" value=""></TD>
		</TR>
	</TABLE>
</FORM>

First of all, notice that the action of the HTML form is set to "search". This really is a relative URL resolving to http://server/PhoneBook/phoneBook/search. In other words, the form submission will be handled by our search web flow controller.The next thing to notice is the two hidden fields that were added to the form:

In the end, the "search" event in the current state of the flow will be triggered. The current state is the "criteria" view state, so in response to the "search" event, the flow will transition to the "results" view state and render the "results" view. The only thing left to do is submit this form when the Search button is clicked. This can be done with a simple piece of JavaScript:

<INPUT type="button" onclick="javascript:document.all.searchForm.submit()" value="Search">

Note that we did not use a special JSP custom tag library to implement this view. Only standard JSP functionality was used. Implementing this view using other view technology, like a Velocity template, is equally simple.

The "results" view implemented in /WEB-INF/jsp/results.jsp and the "detail" view implemented in /WEB-INF/jsp/detail.jsp are very similar. They contain small Java scriptlets displaying all information. Note that the "detail" view is part of the detail web flow and as such has to submit to the detail web flow controller! An example is the following anchor which is used to submit the "colleagueDetail" event to the detail flow:

<A href="detail?_flowId=<%=request.getAttribute("flowId") %>&_event=colleagueDetail&id=<%=colleague.getUserId() %>">
	<%=colleague.getFirstName() %> <%=colleague.getLastName() %>
</A>

Of course the web flows discussed so far don't really do anything. They just navigate between pages. To execute some business processing in a web flow, we need actions. This is discussed in the next paragraph.

Actions

Actions are executed in an action state of a web flow. Actions are pieces of code that do useful work in the flow: they process user input, trigger business functionality, prepare model data for a view and so on. When their work is done, actions signal an event allowing the web flow to continue. An action is just a simple Java class implementing the com.ervacon.springframework.web.servlet.mvc.webflow.Action interface.

public interface Action {
    public String execute(HttpServletRequest request, HttpServletResponse response, Map model);
}

Let's examine this method signature. First, as you can see, the current HTTP request and response are available to the action, so an action could directly generate a client response if it wanted to. This is very similar to the way in which a Spring controller can generate a response itself. Secondly, the model map parameter in the execute method holds the flow model., providing the action with access to the model information exposed to the view in a view state of the flow. Each web flow instance will have an associated flow model which will persist for the lifetime of the flow. The flow model is accessible from all states of the flow, so it can be used to exchange data between states or to hold flow-wide data. Finally, the execute method returns the event that will be signaled. This value cannot be null.

There is one predefined action that comes with the Spring web flows implementation: com.ervacon.springframework.web.servlet.mvc.webflow.BindAndValidateCommandAction. This action will bind request parameters to a command object, validate the command object and put it in the flow model. It's functionality is very similar to that offered by the Spring BaseCommandController. Our sample application uses this action to capture the search criteria in a PhoneBookQuery object. Another action we'll need is a custom action to trigger the actual search using the submitted criteria. Let's add these actions to our search flow:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Search Flow">
	<start-state state="criteria"/>

	<view-state id="criteria" view="criteria">
		<transition name="search" to="bindAndValidateCriteria"/>
	</view-state>

	<action-state id="bindAndValidateCriteria">
		<action name="bindAndValidateAct" bean="bindAndValidateCriteriaAction"/>
		<transition name="bindAndValidateAct.ok" to="search"/>
		<transition name="bindAndValidateAct.error" to="criteria"/>
	</action-state>

	<action-state id="search">
		<action name="searchAct" bean="queryAction"/>
		<transition name="searchAct.ok" to="results"/>
	</action-state>

	<view-state id="results" view="results">
		<transition name="newSearch" to="criteria"/>
		<transition name="detail" to="...detail module..."/>
	</view-state>
</web-flow>

As you can see, the full name of the event used to select a transition in an action state is a concatenation of the action name and the event name. If the "bindAndValidateAct" signals an "ok" event, the "bindAndValidateAct.ok" transition will be triggered, moving to the "search" state. The action definitions shown above reference two beans named "bindAndValidateCriteriaAction" and "queryAction" respectively. These JavaBean objects will have to be defined in the Spring application context associated with our dispatcher servlet: /WEB-INF/phoneBook-servlet.xml.

<bean id="bindAndValidateCriteriaAction"
	class="com.ervacon.springframework.web.servlet.mvc.webflow.BindAndValidateCommandAction">
	<property name="commandName"><value>query</value></property>
	<property name="commandClass">
		<value>com.ervacon.springframework.samples.phonebook.domain.PhoneBookQuery</value>
	</property>
	<property name="validator"><ref bean="queryValidator"/></property>
</bean>

<bean id="queryValidator" class="com.ervacon.springframework.samples.phonebook.web.PhoneBookQueryValidator">
</bean>

<bean id="queryAction" class="com.ervacon.springframework.samples.phonebook.web.QueryAction">
	<property name="phoneBook"><ref bean="phoneBook"/></property>
</bean>

Notice how the "bindAndValidateCriteriaAction" uses the "commandName", "commandClass" and "validator" properties, similar to the BaseCommandController. Our custom PhoneBookQueryValidator will ensure that the submitted search criteria are not empty. Also note that the "queryAction" uses a normal Spring JavaBean reference to get hold of our "phoneBook" business facade. Implementing this custom action is straightforward:

public class QueryAction implements Action {
	private PhoneBook phoneBook;

	public void setPhoneBook(PhoneBook phoneBook) {
		this.phoneBook=phoneBook;
	}

	public String execute(HttpServletRequest request, HttpServletResponse response, Map model) {
		PhoneBookQuery query=(PhoneBookQuery)model.get("query");
		model.put("persons", phoneBook.query(query));
		return "ok";
	}
}

The PhoneBookQuery command object that the query action obtains from the flow model was put there by the "bindAndValidateCriteriaAction". Since this is a sample application, we assume that nothing can go wrong during phone book querying and just signal the "ok" event. As a result, the flow will transition to the "results" view state and render the "results" view. This view will have access to the list of persons resulting from the query as it was stored in the flow model by the query action.

The detail flow will also require a custom action to obtain the details of a particular person. This is implemented in the class com.ervacon.springframework.samples.phonebook.web.GetPersonAction. The required web flow state definitions and Spring bean declarations are similar to those discussed above.

Sub Flows and Model Mappers

The last web flow state type that we need to tackle is the flow state. This state type allows a web flow to use another flow as a sub flow. While the sub flow is active, execution of the parent flow is suspended and the sub flow handles all requests. When the sub flow reaches an end state, execution continues in the parent flow. The event signaled to continue the parent flow is the id of the end state that was reached in the sub flow.

As explained before, our sample application uses 2 modules: a search module and a detail module, each defined in a separate web flow. It should now be clear that the search flow will use the detail flow as a sub flow to show person details. Since we can directly access a person's colleague's details from the detail flow, the detail flow will also use itself as a sub flow. What we have really done now is package the detail flow as a reusable web application module! It can easily be reused in different situations: starting from the search flow, from inside the detail flow, or even as a stand-alone flow if this would be useful. This is a very powerful functionality offered by the Spring web flows system.

Let's now look at the complete search flow definition to see how it invokes the detail flow as a sub flow.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Search Flow">
	<start-state state="criteria"/>

	<view-state id="criteria" view="criteria">
		<transition name="search" to="bindAndValidateCriteria"/>
	</view-state>

	<action-state id="bindAndValidateCriteria">
		<action name="bindAndValidateAct" bean="bindAndValidateCriteriaAction"/>
		<transition name="bindAndValidateAct.ok" to="search"/>
		<transition name="bindAndValidateAct.error" to="criteria"/>
	</action-state>

	<action-state id="search">
		<action name="searchAct" bean="queryAction"/>
		<transition name="searchAct.ok" to="results"/>
	</action-state>

	<view-state id="results" view="results">
		<transition name="newSearch" to="criteria"/>
		<transition name="detail" to="bindAndValidateId"/>
	</view-state>

	<action-state id="bindAndValidateId">
		<action name="bindAndValidateAct" bean="bindAndValidateIdAction"/>
		<transition name="bindAndValidateAct.ok" to="detail"/>
		<transition name="bindAndValidateAct.error" to="endError"/>
	</action-state>

	<flow-state id="detail" flow="detailFlow" model-mapper="idMapper">
		<transition name="endOk" to="results"/>
		<transition name="endError" to="endError"/>
	</flow-state>

	<end-state id="endError" view="error"/>
</web-flow>

When the "detail" event is triggered in the "results" view state, the flow first puts the user id of the selected person in the flow model using the BindAndValidateCommandAction discussed in the previous section. Once this is done, we arrive in the flow state, which will launch the detail flow to show person details. Since the detail flow defines "endOk" and "endError" end states, the flow state in the parent flow will need to define corresponding transitions. Here is the complete definition of the detail flow:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-flow PUBLIC "-//ERVACON//DTD SPRING WEB FLOW//EN" "http://www.ervacon.com/dtd/web-flow.dtd">

<web-flow name="Detail Flow">
	<start-state state="getDetail"/>

	<action-state id="getDetail">
		<action name="getDetailAct" bean="getPersonAction"/>
		<transition name="getDetailAct.ok" to="detail"/>
		<transition name="getDetailAct.error" to="endError"/>
	</action-state>

	<view-state id="detail" view="detail">
		<transition name="back" to="endOk"/>
		<transition name="colleagueDetail" to="bindAndValidateColleagueId"/>
	</view-state>

	<action-state id="bindAndValidateColleagueId">
		<action name="bindAndValidateAct" bean="bindAndValidateColleagueIdAction"/>
		<transition name="bindAndValidateAct.ok" to="colleagueDetail"/>
		<transition name="bindAndValidateAct.error" to="endError"/>
	</action-state>

	<flow-state id="colleagueDetail" flow="detailFlow" model-mapper="colleagueIdMapper">
		<transition name="endOk" to="detail"/>
		<transition name="endError" to="endError"/>
	</flow-state>

	<end-state id="endOk"/>
	<end-state id="endError" view="error"/>
</web-flow>

We are now left with one remaining unanswered question: how will the detail flow know which person details to show? Or in other words: how does the parent flow pass this kind of information to the sub flow? The answer is a model mapper. Model mappers are objects defined in a Spring application context that map model data from the parent flow to the sub flow and back again. They implement the ModelMapper interface.

public interface ModelMapper {
    public void mapToSubFlow(Map parentFlowModel, Map subFlowModel);
    public void mapFromSubFlow(Map parentFlowModel, Map subFlowModel);
}

Mapping data to the sub flow happens before the sub flow is started. Mapping data from the sub flow back to the parent flow is done when the sub flow completes. A model mapper is optional in a flow state. If no mapper is specified, no mapping will be done. Our search flow uses an "idMapper" to map the user id of the person for which to show details to the detail flow. This mapper is defined in /WEB-INF/phoneBook-servlet.xml:

<bean id="idMapper" class="com.ervacon.springframework.web.servlet.mvc.webflow.ParameterizableModelMapper">
	<property name="toMappingsList">
		<list>
			<value>id</value>
		</list>
	</property>
</bean>

A predefined mapper that comes with the Spring web flows implementation is used here: ParameterizableModelMapper. This is a flexible mapper that can be configured in a Spring application context. Please consult the JavaDoc documentation for exact details.

Testing the Application

Let's now put our sample application to the test to see what we have created. Deploy the PhoneBook.war application archive to the servlet engine of your choice (I used Tomcat 5.0.25 for my tests). How to do this is beyond the scope of this article so consult the documentation that comes with your server for detailed instructions.

Once your server has been started, open the URL http://localhost:8080/PhoneBook with your browser. The actual link could be different depending on how you deployed the application. This will display the welcome page of the application: index.jsp. This page simply contains a link to the phone book sample application. Click this link and you will end up on the phone book search criteria page.

Criteria Page
Figure 2. The search criteria page

Leaving both fields empty will be refused by the PhoneBookQueryValidator that we defined: a validation error message will appear at the top of the search criteria page. Entering some valid criteria will trigger our search action (QueryAction) and will display the results page.

Results Page
Figure 3. The search results page

From this page we can go back to the criteria page to start a new search or consult person details for one of the people we found. Click the user id link to get details for a particular person. The detail sub flow will be launched, using our GetPersonAction to retrieve the person details from the domain layer.

Detail Page
Figure 4. The person detail page

The person detail page allows us to go back to the search result page, or directly consult the details for one of the colleagues of a person. The latter results in a recursive invocation of the detail flow. Notice that the Back button behaves differently depending on the situation: you either go back to the search result page or, in case you consulted the detail of a colleague, you go back to the details of the previous person.

Conclusion

This concludes our discussion of the Spring web flows system. We talked about the general concepts that underpin this system and how it is integrated into the Spring framework. As an illustration, we used a phone book sample application.

Web flows deliver a number of important benefits:

The current version of the Spring web flows implementation is stable and functionally complete. We encourage you to give it a try and welcome any feedback you might have!

Resources and References

About the Author

Erwin Vervaet is a software engineer with a keen interest in applying modern IT concepts and tools. He has been using Java since 1996, and has a master's degree in computer science from the Katholieke Universiteit Leuven in Belgium. He has been involved in IT research, e-commerce projects, open source initiatives, and industrial software systems. As an independent consultant, Erwin builds object-oriented business information systems using the Java language. You can contact Erwin at erwin@ervacon.com.

© Copyright Ervacon 2011. All Rights Reserved. Contact us.
All trademarks and copyrights on this page are owned by their respective owners.