Spring Web Flow 2 in Action

Written by Erhan Bagdemir on April 9, 2010 Categories: Java Island Tags: , , , , , , ,

spring-webflow-logo
Introduction

In this article i will show you how to use Spring Web Flow 2 to implement your business logic in flows building a sample application. It is a basic registration process. Spring Webflow project provides a domain-specific-language for developers and an advance control between the conversational-states. You can define your steps in flows and interacting with users during flow session, you can collect informations and process them in your actions.

You can find a documentation about webflow under http://www.springsource.org/webflow.

Setting up your environment

If you already know how to create maven application using eclipse, you can skip this section.

Before we begin, i assume that you have a basic knowledge about maven. You can integrate maven with your IDE using most popular maven plugin for eclipse m2eclipse. We can create our project with "New Project" dialog:
Bildschirmfoto 2009-12-23 um 22.12.11

Let's create a basic maven project (You could select an archetype according to your project requirements.)

Bildschirmfoto 2009-12-23 um 22.13.02We must set project specific informations using dialog

Bildschirmfoto 2009-12-23 um 22.14.30Clicking on Finish button we created a basic maven project for our sample SWF application. Now we are ready to go. We need some dependent libraries to associate with our project. We will define these in our pom.xml. For spring workflow we need these following entries between dependencies elements:

    <dependencies>
        <dependency>
            <groupId>org.springframework.webflow</groupId>
            <artifactId>spring-binding</artifactId>
            <version>2.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.webflow</groupId>
            <artifactId>spring-js</artifactId>
            <version>2.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.webflow</groupId>
            <artifactId>spring-webflow</artifactId>
            <version>2.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>
        <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

After defining these dependencies, you will notice that the maven plugin is downloading the jar files from central maven repository and they will be added into your build path.

First Steps

In this section we will build step-by-step our web application. To do that, we do need a web.xml under folder WEB-INF as you know to define our servlets. In this application we implement MVC pattern using Spring MVC and it's dispatcher servlet. The DispatcherServlet is the front controller of Spring MVC and dispatchs the request to the responsible handlers (controllers). Here i won't discuss deeply about Spring MVC. But you need to know how to define the DispatcherServlet in your web.xml and what it does for us. Here is a sample web.xml :

<!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>
 
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:library.xml,classpath:webflow.xml</param-value>
</context-param>
 
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
 
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
 
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/registration/*</url-pattern>
</servlet-mapping>
 
</web-app>

With the url mapping above, it is defined that all requests which are redirected to "/registration/" context will be handled by dispatcher servlet. "contextConfigLocation" is the location where the Spring Framework can find the configuration file, in which the entire objects can be configured, whose lifecycles are controlled by Framework and live in it's context. With "classpath:" prefix we tell the framework that the configuration file can be found in classpath.

In a regular Spring MVC application we should create some Controllers to handle requests and define mappings in the configuration file, for example "/createLogin.do=CreateLoginController" tells dispatcher servlet, that a request "/registration/createLogin.do" should be handled by CreateLoginController. For Webflow, the mappings must be defined too, but in an another way.

<?xml version="1.0" encoding="UTF-8"?>
 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:webflow="http://www.springframework.org/schema/webflow-config"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">	
 
    <webflow:flow-executor id="flowExecutor" />
 
    <webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
    	<webflow:flow-location id="signup.htm" path="/flow.xml" />
	</webflow:flow-registry>
 
	<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    	<property name="flowExecutor" ref="flowExecutor" />
	</bean>
 
	<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    	<property name="flowRegistry" ref="flowRegistry"/>
    	<property name="order" value="0"/>
	</bean>
 
	<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    	<property name="prefix" value="/WEB-INF/jsp/"/>
    	<property name="suffix" value=".jsp"/>
	</bean>
    <bean name="registrationAction" class="com.bagdemir.registration.handlers.RegistrationAction"/>
 
 
	<bean name="formAction" class="org.springframework.webflow.action.FormAction">
		<property name="formObjectName"><value>customerData</value></property>
		<property name="formObjectClass"><value>com.bagdemir.registration.beans.CustomerData</value></property>
		<property name="validator">
			<bean class="com.bagdemir.registration.validators.RegistrationFormValidator"/>
		</property>
	</bean>
 
</beans>

You see an example webflow configuration file above. It is a regular spring file with webflow tag support. flowExecutor is the central service, which executes flows. flowRegistry holds the mappings to the flows. For example; a request to "/registration/order" will execute the flow in myflow.xml. The bean with the name formAction is the action object which handles form actions submitted by users. If you would like to use your custum actions you could inherit FormAction class and add some more new behaviors. formAction does take some properties like formObjectName, formObjectClass and validator. form the ObjectName is the name of form object defined with property formObjectClass which is bound to the form calling setupForm method of FormAction instance. Validator property defines validator instance which will be used for validation of form values bound.

Our registration flow can be defined like this:

<?xml version="1.0" encoding="UTF-8"?>
 
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
 
<on-start>
  <evaluate expression="initRegistrationProcessAction"/>
</on-start>
 
<view-state id="startOrder" view="views/startRegistration.jsp">
  <on-render>
  	<evaluate expression="customerDataFormAction.setupForm"/>
   </on-render>
   <transition on="submit" to="credentialsStep">
        <evaluate expression="customerDataFormAction.bindAndValidate"/>
   </transition>
</view-state>
 
<view-state id="credentialsStep" view="views/credentialsStep.jsp">
   <transition on="submit" to="confirmationStep">
      	<evaluate expression="customerDataFormAction.bindAndValidate"/>
   </transition>
</view-state>
 
<view-state id="confirmationStep" view="views/confirmationStep.jsp">
   <transition on="submit" to="completeRegistration">
       	<evaluate expression="customerDataFormAction.bindAndValidate"/>
   </transition>
</view-state>
 
<end-state id="completeRegistration" view="views/completeRegistration.jsp" />
 
</flow>

As you noticed that, we have elements ending with <*-state> which define our flow states. We can translate the xml above pseudo-like as following:
1. on-"start" call expression "initRegistrationProcessAction" which contains our initialization logic.
2. run startOrder immediately after start-state using startRegistration.jsp and while rendering, call customerDataFormAction's setupForm method on action object. setupForm method creates the form object and assoicates it with error objects.
3. wait for user interaction.
4. on-Submit event from user, bindAndValidate method will be evaluated on action, which binds the form values to the form object and triggers the validator if some are registered. After binding the flow will go on with confirmation step.
5. Confirmation step is like 4. waits for user interaction, in this case a confirmation of registration.
6. end-state shows that our flow execution ends with this state, resulting with registration confirmation.

We need some views to display our form and to interact with the users. We need a regular JSP file with spring form taglib support:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

Here is our registration form:

<form:form commandName="customerData">
	<table>
	<tr><td>Name:</td><td><form:input path="name"/><form:errors path="name" /></td></tr>
	<tr><td>Surname:</td><td><form:input path="surname"/><form:errors path="surname" /></td></tr>
	<tr><td>Street/Door:</td><td><form:input path="street"/><form:input path="doornumber"/><form:errors path="street" /><form:errors path="doornumber" /></td></tr>
	<tr><td>Zipcode:</td><td><form:input path="zipcode"/><form:errors path="zipcode" /></td></tr>
	<tr><td>State:</td><td><form:input path="state"/><form:errors path="state" /></td></tr>
	<tr><td>Password:</td><td><form:input path="password"/><form:errors path="password" /></td></tr>
	<tr><td>Confirm your password:</td><td><form:input path="passwordConfirmation"/><form:errors path="passwordConfirmation" /></td></tr>
	<tr><td colspan=2><input type="submit" name="_eventId_submit" value="Register"/></td></tr>
	</table>
</form:form>

In registration form we have used spring form tag library to bind our form objects with forms. "path" attributes let us define input element with which field of form object to be associated. Key point above in the Form ist that the submit element has it's name in the following format "_eventId_*". "_eventId_submit" activates "confirmationStep" as it's defined in flow using transition element in view step:

<transition on="submit" to="credentialsStep">

If you are familiar with spring form tag library, there is no speciality, since we use very basic form elements from library. We used spring's validation support and interfaces in our example to validate form fields. If the validation fails, form:errors element shows the users registered error message for the field in validator.

public class RegistrationFormValidator implements Validator {
 
	public boolean supports(Class clazz) {
		return clazz.getName().equals(CustomerData.class.getName()) ? true : false;
	}
 
	public void validate(Object object, Errors errors) {
		//TODO your validation logic.
                /* object is your form object. */
		CustomerData custData = (CustomerData) object;
                /* an example validation for name field */
		if (StringUtils.isBlank(custData.getName()))
			errors.rejectValue("name", "name.required", "name is required");
	}
}

We'd registered RegistrationFormValidator as validator in our bean definition. Using it, Spring takes the responsibility for validating form object. If the validation fails, the flow won't go with the next step and shows the users error messages immediately.
I want to tell about scopes available in Webflow which we didn't mention until now. Webflow has the following scopes to let developers put their scope objects like request,session,application and page scopes in Servlet world:


Flow scope; will be created and initialized when a flow starts and will be destructed when the flow ends. In our example application if we look at log files, we will notice that, our form object will be stored on flowScope also.

flowSessions = list[[FlowSessionImpl@144dff0 flow = 'signup.htm', state = 'confirmation', scope = map['customerData' -> com.bagdemir.registration.beans.CustomerData@1cdae24, 'currentFormObject' -> com.bagdemir.registration.beans.CustomerData@1cdae24, 'viewScope' -> map[[empty]]]]]]' into repository
View scope; view scope will be created when a view state enters and will be destroyed when the state exits. This scope can be only referenceable from within view states.
Request scope; will be created when a flow is created and destroyed, when the flow returns.
Flash scope; will be created when a flow starts and will be cleared after every view render and will be destroyed when flow ends.
Conversation scope; available for all flow sessions in conversations. It will be allocated when Top-Level flow starts and will be destroyed when the top-level flow ends. This scope is available also for all it's sub-flows.

I introduced in this text how to build a basic SWF application. There are also advanced topics in SWF like conversional states and sub-flows, however I won't mention about them in this text. SWF is useful if you have projects which have workflow- structures and have interaction steps with users. Jumps between steps and rendering of states will be automated and controlled by SWF.

1 Comment

1 Comment

  • nancy says:

    nice article , please post the screen snapshot of the project file list that would be good for us to follow up. Thank you.

Leave a Reply