Chapter 6. Practical Use of Spring Web Flow

6.1. Sample applications


It is recommended that you review the Spring Web Flow sample applications included in the release distribution for best-practice illustrations of the features of this framework. A description of each sample is provided below:

  1. Phonebook - the original sample demonstrating most features (including subflows).

  2. Sellitem - demonstrates a wizard with conditional transitions, flow scope, flow execution redirects, and continuations.

  3. Flowlauncher - demonstrates all the possible ways to launch and resume flows.

  4. Itemlist - demonstrates REST-style URLs and inline flows.

  5. Shippingrate - demonstrates Spring Web Flow together with Ajax technology.

  6. NumberGuess - demonstrates use of stateful middle-tier components to carry out business logic.

  7. Birthdate - demonstrates Struts integration and the MultiAction.

  8. Fileupload - demonstrates multipart file upload.

  9. Phonebook-Portlet - the phonebook sample in a Portlet environment (notice how the flow definitions do not change).

  10. Sellitem-JSF - the sellitem sample in a JSF environment (notice how the flow definition is more concise because JSF takes care of data binding and validation).

6.2. Running the Web Flow sample applications

The samples can be built from the command line and imported as Eclipse projects - all samples come with Eclipse project settings. It is also possible to start by importing the samples into Eclipse first and then build with Ant within Eclipse.

6.2.1. Building from the Command Line

Java 1.5 (or greater) and Ant 1.6 (or greater) are prerequisites for building the sample applications. Ensure those are present in the system path or are passed on the command line. To build Web Flow samples from the command line, open a prompt, cd to the directory where Spring Web Flow was unzipped and run the following:

cd projects/spring-webflow/build-spring-webflow
ant dist
				

This builds all samples preparing "target" areas within each sample project subdirectory containing webapp structures in both exploded and WAR archive forms. The build also provides basic helper targets for deploying to Tomcat from Ant; however these webapp structures can be copied to any servlet container, and each project is also a Eclipse Dynamic Web Project (DWP) for easy deployment inside Eclipse with the Eclipse Webtools Project (WTP).

6.2.2. Importing Projects into Eclipse

Importing the sample projects into Eclipse is easy. With a new or an existing workspace select: File > Import > Existing Projects into Workspace. In the resulting dialog browse to the project subdirectory where Spring Web Flow was unzipped and choose it as the root directory to import from. Select OK. Here Eclipse will list all projects it found including the sample application projects. Select the projects you're interested in, and select Finish.

If you previously built each project from the command line Eclipse will compile with no errors. If not you will need to run the Ant build once for these errors to clear and you can do that within Eclipse.

To build all projects inside Eclipse, import and expand the build-spring-webflow project, right-click on build.xml and select Run As > Ant Build. Doing this will run the default Ant target and will build all sample projects.

To build a single project inside Eclipse, simply select the project, right-click, and select Run As > Ant Build. You can also use the convenient shortcut ALT + SHIFT + X (Execute menu), then Q (Run Ant Build).

After Ant runs and the libraries needed to compile each project are downloaded, all errors in the Eclipse problems view should go away. Try refreshing a project (F5) if you still have errors. In general, from this point on you no longer need Ant: you can rely on Eclipse's incremental compile and Eclipse's web tools (WTP) built-in JEE support for deployment. (Ant is only needed in the system for command-line usage or when the list of jar dependencies for a project changes and new jars need to be downloaded).

6.2.3. Deploying projects inside Eclipse using Eclipse Web Tools (WTP)

Each Spring Web Flow sample application project is a Eclipse Dynamic Web Project (DWP), for easy deployment to a server running inside the Eclipse IDE. To take advantage of this, you must be running Eclipse 3.2 with Web Tools 1.5.

To run a sample application as a webapp inside Eclipse, simply select the project, right-click, and select Run -> Run On Server. A convenient shortcut for this action is ALT + SHIFT + X (Execute menu), R (Run on Server). The first time you do this you will be asked to setup a Server, where you are expected to point Eclipse to a location where you have a Servlet Container such as Apache Tomcat installed. Once your container has been setup and you finish the deployment wizard, Eclipse will start the container and automatically publish your webapp to it. In addition, it will launch a embedded web browser allowing you to run the webapp fully inside the IDE.

6.2.4. Other IDE's

Importing samples into other IDE's should be fairly straight-forward. If using another IDE running the Ant build from the command line first may help as it will populate the lib subdirectories of each sample project. Follow steps similar as those outlined for Eclipse above.

6.3. Fileupload Example

6.3.1. Overview

Fileupload is a simple one page web application for uploading files to a server. It is based on Spring MVC, uses a Web Flow controller and one web flow with two states: a view state for displaying the initial JSP page and an action state for processing the submit.

6.3.2. Web.xml

The web.xml configuration maps requests for "*.htm" to the fileupload servlet - a regular Spring MVC DispatcherServlet:

<servlet>
	<servlet-name>fileupload</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
	 
<servlet-mapping>
	<servlet-name>fileupload</servlet-name>
	<url-pattern>*.htm</url-pattern>
</servlet-mapping>
				

6.3.3. Spring MVC Context

The Spring MVC servlet context for the fileupload servlet (WEB-INF/fileupload-servlet.xml) defines one controller bean:

<bean name="/admin.htm" class="org.springframework.webflow.executor.mvc.FlowController">
	<property name="flowExecutor" ref="flowExecutor" />
</bean>
				

FlowController is a Web Flow controller. It is the main point of integration between Spring MVC and Spring Web Flow routing requests to one or more managed web flow executions. The FlowController is injected with flowExecutor and flowRegistry beans containing one web flow definition:

<!-- Launches new flow executions and resumes existing executions. -->
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="singlekey"/>
	
<!-- Creates the registry of flow definitions for this application -->
<flow:registry id="flowRegistry">
	<flow:location path="/WEB-INF/fileupload.xml" />
</flow:registry>
				

Given the above definitions the following URI can be used to invoke the "fileupload" flow:

/swf-fileupload/admin.htm?_flowId=fileupload
				

Both flowExecutor and flowRegistry beans are defined with Spring custom tags schema available in Spring 2.0. The custom tags make configuration less verbose and more readable. Regular Spring bean definitions can be used as well with earlier versions of Spring.

The Spring MVC context also defines a view resolver bean for resolving logical view names and a multipartResolver bean for the upload component. In general Web Flow does not aim to replace the flexibility of Spring MVC for view resolution. It focuses on the C in MVC.

6.3.4. Fileupload Web Flow

The start state for the fileupload flow (WEB-INF/fileupload.xml) is a view state:

<start-state idref="selectFile"/>

<view-state id="selectFile" view="fileForm">
	<transition on="submit" to="uploadFile"/>
</view-state>
				

View states allow a user to participate in a flow by presenting a suitable interface. The view attribute "fileForm" is a logical view name, which the Spring MVC view resolver bean will resolve to /WEB-INF/jsp/fileForm.jsp.

The fileForm.jsp has an html form that submits back to the same controller (/swf-fileupload/admin.htm) and passes a "_flowExecutionKey" parameter. The value for _flowExecutionKey is provided by the FlowController - it identifies the current instance of the flow and allows Web Flow to resume flow execution, which is paused each time a view is displayed.

The name of the form submit button "_eventId_submit" indicates the event id to use for deciding where to transition to next. Given an event with id of "submit" the "selectFile" view transitions to the "uploadFile" state:

<action-state id="uploadFile">
	<action bean="uploadAction" method="uploadFile"/>
	<transition on="success" to="selectFile">
		<set attribute="fileUploaded" scope="flash" value="true"/>
	</transition>
	<transition on="error" to="selectFile"/>
</action-state>
				

The "uploadFile" state is an action state. Action states integrate with business application code and respond to the execution of that code by deciding what state of the flow to enter next. The code for the uploadFile state is in the "uploadAction" bean declared in the Spring web context (/WEB-INF/fileupload-servlet.xml):

<bean id="uploadAction" class="org.springframework.webflow.samples.fileupload.FileUploadAction" />
				

FileUploadAction has simple logic. It picks one of two Web Flow defined events - success or error, depending on whether the uploaded file size is greater than 0 or not. Both success and error transition back to the "selectFile" view state. However, a success event causes an attribute named "fileUploaded" to be set in flash scope

A flash-scoped attribute called "file" is also set programmatically in the FileUploadAction bean:

context.getFlashScope().put("file", new String(file.getBytes()));
return success();
				

This illustrates the choice to save attributes in one of several scopes either programatically or declaratively.

6.4. Birthdate Example

6.4.1. Overview

Birthdate is a web application with 3 consequitive screens. The first two collect user input to populate a form object. The third presents the results of business calculations based on input provided in the first two screens.

Birthdate demonstrates Spring Web Flow's Struts integration as well as the use of FormAction, a multi-action used to do the processing required for all three screens. The sample also uses JSTL taglibs in conjunction with flows.

6.4.2. Web.xml

The web.xml configuration maps requests for "*.do" to a regular Struts ActionServlet:

<servlet>
	<servlet-name>action</servlet-name>
	<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
</servlet>
	 
<servlet-mapping>
	<servlet-name>action</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>
				

The web.xml also sets up the loading of a Spring context at web application startup:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/webflow-config.xml
	</param-value>
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
				

The Spring web context contains beans to set up the Web Flow runtime environment. As will be shown in the next section Struts is configured with a Web Flow action that relies on the presence of a flowExecutor and a flowRegistry beans in this context.

6.4.3. Struts Configuration

The Struts configuration (WEB-INF/struts-config.xml) defines the following action mapping:

<action-mappings>
	<action path="/flowAction" name="actionForm" scope="request"
		type="org.springframework.webflow.executor.struts.FlowAction"/>
</action-mappings>
				

FlowAction is a Struts action acting as a front controller to the Web Flow system routing Struts requests to one or more managed web flow executions. To fully configure the FlowAction a Spring web context is required to define flowExecutor and flowRegistry beans (named exactly so). This is an excerpt from the Spring web context (/WEB-INF/webflow-config.xml) defining these beans:

<!-- Launches new flow executions and resumes existing executions. -->	
<flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
	
<!-- Creates the registry of flow definitions for this application -->
<flow:registry id="flowRegistry">
	<flow:location path="/WEB-INF/birthdate.xml"/>
	<flow:location path="/WEB-INF/birthdate-alternate.xml"/>
</flow:registry>
				

Based on the above, Web Flow is configured with two flows - birthdate and birthdate-alternate, which can be invoked as follows:

/swf-birthdate/flowAction.do?_flowId=birthdate
/swf-birthdate/flowAction.do?_flowId=birthdate-alternate
				

The Struts configuration file also defines several global forwards: birthdateForm, cardForm, and yourAge, which will be referenced from Web Flow definitions as logical view names (and left to Struts to resolve to actual JSP pages). In general Web Flow does not aim to replace view resolution capabilities of web frameworks such as Struts or Spring MVC. It focuses on the C in MVC.

6.4.4. Birthdate Web Flow

The birthdate web flow (WEB-INF/birthdate.xml) defines the following start state:

<view-state id="enterBirthdate" view="birthdateForm">
	<render-actions>
		<action bean="formAction" method="setupForm" />
	</render-actions>
	<transition on="submit" to="processBirthdateFormSubmit" />
</view-state>
				

The setupForm action is called to perform initializations for the enterBirthdate view state. Its action bean is defined the Spring web context WEB-INF/webflow-config.xml:

<bean id="formAction" class="org.springframework.webflow.samples.birthdate.BirthDateFormAction" />
				

BirthDateFormAction is a FormAction - it extends Web Flow's FormAction class, which serves a purpose similar to that of Spring MVC's SimpleFormController providing common form functionality for data binding and validation.

When the BirthDateFormAction bean is instantiated it sets the name, class and scope of the form object to use for loading form data upon display and collecting form data upon submit:

public BirthDateFormAction() {
	// tell the superclass about the form object and validator we want to
	// use you could also do this in the application context XML ofcourse
	setFormObjectName("birthDate");
	setFormObjectClass(BirthDate.class);
	setFormObjectScope(ScopeType.FLOW);
	setValidator(new BirthDateValidator());
}
				

The form object "birthDate" is placed in flow scope, which means it will not be re-created with each request but will be obtained from flow scope instead as long as the request remains within the same flow.

Once setupForm is done, the "birthdateForm" view will be rendered. The logical view name "birthdateForm" is a global-forward in struts-config.xml resolving to /WEB-INF/jsp/birthdateForm.jsp. This JSP collects data for the fields "name" and "date" bound to the birthDate form object and posts back to FlowAction with a submit image named "_eventId_submit". An event with the id of "submit" causes a transition to the processBirthdateFormSubmit action state defined as follows:

<action-state id="processBirthdateFormSubmit">
	<action bean="formAction" method="bindAndValidate">
		<attribute name="validatorMethod" value="validateBirthdateForm" />
	</action>
	<transition on="success" to="enterCardInformation" />
	<transition on="error" to="enterBirthdate" />
</action-state>
				

The processBirthDateFormSubmit action state uses the same formAction bean as the one already used to setup the form. This time its bindAndValidate method is used to populate and validate the html form values. Also, note the "validateMethod" attribute used to specify the name of the method to invoke on the Validator object setup in the constructor of the BirthDateFormAction. The use of this attribute allows partial validation of complex objects populated over several consecutive screens.

On error the action returns to the view state it came from. On success it transitions to the enterCardInformation view state:

<view-state id="enterCardInformation" view="cardForm">
	<transition on="submit" to="processCardFormSubmit" />
</view-state>
				

The logical view name "cardForm" is a global-forward in struts-config.xml resolving to /WEB-INF/jsp/cardForm.jsp. This JSP collects data for the remaining fields of the birthDate form object - "sendCard" and "emailAddress", and posts back to FlowAction with a submit image named "_eventId_submit". An event with the id of "submit" causes a transition to the processCardFormSubmit action state defined as follows:

<action-state id="processCardFormSubmit">
	<action bean="formAction" method="bindAndValidate">
		<attribute name="validatorMethod" value="validateCardForm" />
	</action>
	<transition on="success" to="calculateAge" />
	<transition on="error" to="enterCardInformation" />
</action-state>
				

For this action state the bindAndValidate method of the formAction bean is used to populate and validate the remaining html form values. The "validateMethod" attribute specifies the name of the method to invoke on the Validator object specific to the fields loaded on the current screen.

On error the action returns to the view state it came from. On success it transitions to another action state called calculateAge:

<action-state id="calculateAge">
	<action bean="formAction" method="calculateAge" />
	<transition on="success" to="displayAge" />
</action-state>
				

The logic for the calculateAge action state is in the calculateAge method of the same formAction bean used for data binding and validation. This demonstrates the flexibility Web Flow allows in properly structuring control and business logic according to function.

The caculateAge method performs business calculations and adds a string in request scope with the calculated age. Upon successful completion the calculateAge action state transitions to the end view state:

<end-state id="displayAge" view="yourAge" />
				

Once again the logical view name "yourAge" is a global-forward in struts-config.xml resolving to /WEB-INF/jsp/yourAge.jsp. This JSP page retrieves the calculated age from request scope and displays the results for the user.

The transition to the end state indicates the end of the web flow. The flow execution is cleaned up. If the web flow is entered again a new flow execution will start, creating a new form object named "birthDate" and placing it in flow scope.

6.4.5. Birthdate-alternate Web Flow

The birthdate-alternate web flow (/WEB-INF/birthdate-alternate.xml) offers an alternative way and more compact way of defining the same web flow. For example the birthdate web flow defines two independent states for the first screen - a view state (enterBirthdate) and an action state (processBirthdateFormSubmit). In birthdate-alternate those are encapsulated in the view state enterBirthdate as follows:

<view-state id="enterBirthdate" view="birthdateForm">
	<render-actions>
		<action bean="formAction" method="setupForm" />
	</render-actions>
	<transition on="submit" to="enterCardInformation">
		<action bean="formAction" method="bindAndValidate">
			<attribute name="validatorMethod" value="validateBirthdateForm" />
		</action>
	</transition>
</view-state>
				

Here the setupForm action state is defined as a render-action of the enterBirthdate view state while the transition to the next screen uses a nested action bean invoked before the transition occurs. Notice that success is implicitly required for the transition to occur. Similarly on error the transition does not occur and the same view state is displayed again.

The second screen is also defined with a nested transition and action bean:

<view-state id="enterCardInformation" view="cardForm">
	<transition on="submit" to="calculateAge">
		<action bean="formAction" method="bindAndValidate">
			<attribute name="validatorMethod" value="validateCardForm" />
		</action>
	</transition>
</view-state>
				

The remaining two states - calculateAge and displayAge are identical.