Spring Web Flow

On Spring Framework, together the core project, we can find a lot of extensions which provide to cover different technologies. One of this is Spring web flow.

Spring web flow is Spring extension that provides a tool for defining a web application flow inside a configuration files. It can do that by using a combination of states and actions.

In this article I’d like to illustrate, with a simple project, what is it and how we can adopt that in our solution.

Before starting take a look at official documentation here http://static.springsource.org/spring-webflow/docs/2.3.x/reference/htmlsingle/spring-webflow-reference.html

I used 2.3.1 version including org.springframework.webflow-2.3.1.RELEASE.jar library.

As I was saying, a Spring web flow is built with a combination of states (viewState) and actions (actionState). Mixing it togheter, we’re able to build a flow inside a web application.

In my example I use a common cart control to drive the user to a check out process of the order.

The diagram of the flow:

So, we have three steps. The shop state for adding and removing products, the check out state for adding the shipping address (I should have rename it)  and the last state is the summary of the order.

The xml configuration file of this diagram is the follow

<?xml version="1.0" encoding="UTF-8"?>
<flow start-state="viewStart" 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">

	<view-state id="viewStart" view="/WEB-INF/jsp/viewstart.jsp">

		<on-render>
			<set name="requestScope.ordersCart" value="orderCart.getOrders()" />
		</on-render>

		<transition on="addproduct" to="addProductToCart" />
		<transition on="removeProduct" to="removeProductToCart" />
		<transition on="checkout" to="checkoutProductToCart" />
	</view-state>

	<action-state id="addProductToCart">
		<on-entry>
			<set name="requestScope.productCode" value="requestParameters.productCode" />
			<set name="requestScope.quantity" value="requestParameters.quantity" />
		</on-entry>
		<evaluate expression="orderCart.addItem(productCode, quantity)" />

		<transition to="viewStart" />
	</action-state>

	<action-state id="removeProductToCart">
		<on-entry>
			<set name="requestScope.productCode" value="requestParameters.productCode" />
			<set name="requestScope.quantity" value="requestParameters.quantity" />
		</on-entry>
		<evaluate expression="orderCart.removeItem(productCode, quantity)" />

		<transition to="viewStart" />
	</action-state>

	<view-state id="checkoutProductToCart" view="/WEB-INF/jsp/viewshipping.jsp">
		<on-render>
			<set name="requestScope.ordersCart" value="orderCart.getOrders()" />
		</on-render>

		<transition on="addaddress" to="addAddressToCart" />
		<transition on="back" to="viewStart" />
	</view-state>

	<action-state id="addAddressToCart">
		<on-entry>
			<set name="requestScope.name" value="requestParameters.customerName" />
			<set name="requestScope.surname" value="requestParameters.customerSurname" />
			<set name="requestScope.address" value="requestParameters.customerAddress" />
			<set name="requestScope.city" value="requestParameters.customerCity" />
			<set name="requestScope.country" value="requestParameters.customerCountry" />
		</on-entry>
		<evaluate
			expression="orderAddress.addAddress(name, surname, address, city, country)" />

		<transition to="viewSummary" />
	</action-state>

	<end-state id="viewSummary" view="/WEB-INF/jsp/viewsummary.jsp">
		<on-entry>

			<set name="requestScope.ordersCart" value="orderCart.getOrders()" />
			<set name="requestScope.orderAddress" value="orderAddress.getAddress()" />
		</on-entry>
	</end-state>

</flow>

In Spring configuration file I’d declared the flow definition:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:webflow="http://www.springframework.org/schema/webflow-config"
	xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-2.5.xsd
		http://www.springframework.org/schema/util
		http://www.springframework.org/schema/util/spring-util-2.0.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-2.5.xsd
		http://www.springframework.org/schema/webflow-config
		http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"
	default-autowire="byName">

	<context:annotation-config />

	<context:component-scan base-package="it.springorderwebflow.controller" />
	<webflow:flow-registry id="flowRegistry">
		<webflow:flow-location path="/WEB-INF/flows/start.xml"
			id="main" />
	</webflow:flow-registry>

	<webflow:flow-executor id="flowExecutor"
		flow-registry="flowRegistry">
	</webflow:flow-executor>

	<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
		<property name="flowRegistry" ref="flowRegistry" />
		<property name="order" value="0" />
	</bean>
	<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
		<property name="flowExecutor" ref="flowExecutor" />
	</bean>

</beans>

The process starts from viewstart state defined inside viewstart.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>

<head>
<title>Start</title>
</head>
<body>

	<form method="post">
		<table>
			<tr>
				<td>Product Code</td>
				<td><input type="text" name="productCode" size="5" /></td>
			</tr>
			<tr>
				<td>Quantity</td>
				<td><input type="text" name="quantity" size="3" /></td>
			</tr>
			<tr>
				<td><input type="submit" value="Add" /> <input type="hidden"
					name="_eventId" value="addproduct" /></td>
				<td><input type="button"
					onclick="window.location='${flowExecutionUrl}&_eventId=checkout'"
					value="Check Out" /></td>
			</tr>
		</table>
	</form>
	<p>
		<table border="1">
			<tr>
				<td><b>Product Code</b></td>
				<td><b>Quantity</b></td>
				<td>&nbsp;</td>
			</tr>
			<c:forEach var="order" items="${ordersCart}">
				<tr>
					<td>${order.productCode}</td>
					<td>${order.quantity}</td>
					<td><a
						href="${flowExecutionUrl}&_eventId=removeProduct&productCode=${order.productCode}&quantity=${order.quantity}">Remove</a></td>
				</tr>
			</c:forEach>
		</table>
	</p>

</body>
</html>

Afterward, the user goes on to the state checkoutProductToCart and the view viewshipping.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Shipping</title>
</head>
<body>
	<table>
		<tr>
			<td colspan="2"><b>Order Detail</b></td>
		</tr>
		<c:forEach var="order" items="${ordersCart}">
			<tr>
				<td>${order.productCode}</td>
				<td>${order.quantity}</td>
			</tr>
		</c:forEach>
	</table>
	<form method="post">
		<table>
			<tr>
				<td colspan="2"><b>Address</b></td>
			</tr>
			<tr>
				<td>Name</td>
				<td><input type="text" name="customerName" /></td>
			</tr>
			<tr>
				<td>Surname</td>
				<td><input type="text" name="customerSurname" /></td>
			</tr>
			<tr>
				<td>Address</td>
				<td><input type="text" name="customerAddress" /></td>
			</tr>
			<tr>
				<td>City</td>
				<td><input type="text" name="customerCity" /></td>
			</tr>
			<tr>
				<td>Country</td>
				<td><input type="text" name="customerCountry" /></td>
			</tr>
			<tr>
				<td><input type="submit" value="Send" /> <input type="hidden"
					name="_eventId" value="addaddress" /></td>
				<td><input type="button"
					onclick="window.location='${flowExecutionUrl}&_eventId=back'"
					value="Back" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

Finally, if the user wuold like to confirm the order, the process goes on to the state viewSummary and the view viewsummary.jsp

<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Checkout</title>
</head>
<body>

	<table>
		<tr>
			<td colspan="2"><b>Order Detail</b></td>
		</tr>
		<c:forEach var="order" items="${ordersCart}">
			<tr>
				<td>${order.productCode}</td>
				<td>${order.quantity}</td>
			</tr>
		</c:forEach>
		<tr>
			<td colspan="2"><b>Address</b></td>
		</tr>
		<tr>
			<td>Name</td>
			<td>${orderAddress.name}</td>
		</tr>
		<tr>
			<td>Surname</td>
			<td>${orderAddress.surname}</td>
		</tr>
		<tr>
			<td>Address</td>
			<td><td>${orderAddress.address}</td></td>
		</tr>
		<tr>
			<td>City</td>
			<td><td>${orderAddress.city}</td></td>
		</tr>
		<tr>
			<td>Country</td>
			<td><td>${orderAddress.country}</td></td>
		</tr>
	</table>
</body>
</html>

Inside the flow configuration files we can see some actions defined for adding and removing products and get the cart.

That actions are defined inside some spring components.

package it.springorderwebflow.controller;

import it.springorderwebflow.bean.Order;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

@Component
public class OrderCart {

 List<Order> order = new ArrayList<Order>();
 
 public void addItem(String productCode, int quantity)
 {
  order.add(new Order(productCode, quantity));
 }
 
 public List<Order> getOrders()
 {
  return order;
 }
 
 public void removeItem(String productCode, int quantity)
 {  
  order.remove(new Order(productCode, quantity));
 }
}

The OrderAddress class file.

package it.springorderwebflow.controller;</pre>
import org.springframework.stereotype.Component;

import it.springorderwebflow.bean.Address;

@Component
public class OrderAddress {

 private Address customerAddress;
 
 public void addAddress(String name, String surname, String address,
   String city, String country) {

  customerAddress = new Address(name, surname, address, city, country);
  
 }
 
 public Address getAddress()
 {
  return customerAddress;
 }

}

Now the beans.

package it.springorderwebflow.bean;

public class Order {

 private String productCode;
 private int quantity;

 public Order(String productCode, int quantity) {
  this.productCode = productCode;
  this.quantity = quantity;
 }

 public String getProductCode() {
  return productCode;
 }

 public void setProductCode(String productCode) {
  this.productCode = productCode;
 }

 public int getQuantity() {
  return quantity;
 }

 public void setQuantity(int quantity) {
  this.quantity = quantity;
 }

 @Override
 public boolean equals(Object obj)
 {
  return (((Order)obj).productCode.equals(this.productCode) &&
    ((Order)obj).quantity == this.quantity);
  
 }
}

package it.springorderwebflow.bean;

public class Address {
 private String name;
 private String surname;
 private String address;
 private String city;
 private String country;

 public Address(String name, String surname, String address,
   String city, String country) {
  this.name = name;
  this.surname = surname;
  this.address = address;
  this.city = city;
  this.country = country;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getSurname() {
  return surname;
 }

 public void setSurname(String surname) {
  this.surname = surname;
 }

 public String getAddress() {
  return address;
 }

 public void setAddress(String address) {
  this.address = address;
 }

 public String getCity() {
  return city;
 }

 public void setCity(String city) {
  this.city = city;
 }

 public String getCountry() {
  return country;
 }

 public void setCountry(String country) {
  this.country = country;
 }
}

The web.xml file is very easy to configure

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	version="2.5">

	<display-name>SpringOrderWebFlow</display-name>

	<servlet>
		<servlet-name>orderwebflow</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>orderwebflow</servlet-name>
		<url-pattern>/web/*</url-pattern>
	</servlet-mapping>

</web-app>

Now, it’s time to browse the web application. To do it just click on http://localhost:8080/SpringOrderWebFlow/web/main

The result is a process for leading the user inside a order process system.

Summary

I must be honest. I’m not 100% conviced by this technology. I think it can work on step by step process (like the above process or, for example, user creation) but I’m not sure that could be the best idea for using it on the whole web site.

Advertisements

One thought on “Spring Web Flow

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s