Spring Web Flow is a framework developed on top on Spring Web MVC that aims in capturing the flow of an application so that it is possible for the application developers to design web application based on use-cases in the form of flows. Organization of multiple flow can be easily managed through the definition of parent and child flows. The framework also provides integration support so that an application can be easily extended with the data tier through Spring’s or JPA’s transaction manager. The article assumes that the user has knowledge on Core Spring and Spring Web MVC and the basic concepts of Web flow, sub flows, persistence context is explained with plenty of samples.
also read:
also read:
Setting up Spring Web Flow
In this section, we will see how to setup the environment for Spring Web flow by providing a sample application. We will start writing a Web Application using Spring Web MVC framework and will discuss how and where Spring Web Flow fits in. The sample application that we are going to develop in this section presents a login page for mail users. Users are prompted asking to enter the username and password. If the login credentials are proven to be valid, then the page is displayed containing the mail items specific to the logged-in user. An error page is shown when the credentials are invalid.
Web Application Deployment Descriptor
The below listing provides the definition of web application’s deployment descriptor. As you can see, the index.jsp page will be displayed at startup and every request ending with the pattern “*.htm” will be mapped to the Spring’s dispatcher servlet with the name ‘mailview’.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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"> <servlet> <servlet-name>mailview</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mailview</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Because the name of the servlet is mailview, the framework will try and load the Spring’s configuration file named ‘mailview-servlet.xml’ in the /WEB-INF directory, though the default path of the configuration file can be overridden.
Spring Configuration
Here is the complete listing of the Spring’s configuration file. The listing contains bean definitions that falls into three categories. The first bean definitions are the ones related to the Application, the second bean definitions are the ones related to Spring Web MVC and finally there are bean definitions specific to Spring Web Flow.
Spring configuration
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"> <bean id="mailItemsViewService" /> <bean id="handlerMapping"> <property name="mappings"> <value>/homeView.htm=flowController</value> </property> </bean> <bean id="flowController"> <property name="flowExecutor" ref="flowExecutor" /> </bean> <webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry"> </webflow:flow-executor> <webflow:flow-registry id="flowRegistry"> <webflow:flow-location id="homeView" path="/WEB-INF/flows/mailViewFlow.xml" /> </webflow:flow-registry> </beans>
The first bean definition is specific to the Application which is the MailItemViewService which provides the service for listing out the mail items specific to a user. The second bean definition is the ‘handlerMapping’ bean which defines the handler for mapping the incoming requests. We have mapped the URL pattern that ends with ‘/homeView.htm’ to a customized version of the Controller which is the Spring Web Flow Controller.
The Spring Web Flow Controller component is responsible for the invocation of the flow definition that we will see in the later section. The Controller references to an instance of the flow executor that is responsible for the execution of the flow definitions by loading the flow definitions from the flow registry. Simply put, a flow registry represents a repository for maintaining all the flow definition flows. With this basic knowledge in mind, we will proceed to the next section of defining a flow.
How to define a Flow in Spring Web Flow (SWF) Framework?
A complex web application involves multiple use cases in the form of flows and every application can be considered as a combination of flows. Flows may have dependencies too and having said that, the outcome of one flow may be the income of another flow and vice-versa. The heart of a Web Application making use of Spring Web flow framework lies in defining the flow.
A flow has a start and an end and it can have multiple transitions in the form of states. The possible states are view-state, action state and decision state. In a typical application, the flow starts in displaying the login view to the user in which case the flow is currently said to be in view state. Once the user enters the credentials, the credentials need to be verified, this usually happens by having a service object that interacts with the data layer for verification. So, now the flow is said to be in action-state, as it performs the action of calling the service for validation. Now based on the return results from the service, we provide a welcome view or a failure view. Now the flow is said to be in decision state as it decides which view to be displayed to the user based on the return results from the service and now the flow ends.
Flow definition
<?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"> <var name="mailItemModel"/> <view-state id="homeView" model="mailItemModel" view="/jsp/homeView.jspx"> <transition on="viewMailItems" to="viewMailItemsAction" /> </view-state> <action-state id="viewMailItemsAction"> <evaluate expression="mailItemsViewService.isUserValid(mailItemModel.username)" result="flowScope.isValidUser" /> <transition to="checkValidUser" /> </action-state> <decision-state id="checkValidUser"> <if test="flowScope.isValidUser" then="getMailItems" else="invalidUserView"/> </decision-state> <action-state id="getMailItems"> <evaluate expression="mailItemsViewService.retriveMailItems(mailItemModel.username)" result="flowScope.mailItems" /> <transition to="validUserView" /> </action-state> <end-state id="validUserView" view="/jsp/mailItemsView.jspx"/> <end-state id="invalidUserView" view="/jsp/invalidUserView.jspx" /> </flow>
It is also possible to define variables inside a flow and such variables are called flow variables. Those variables can be referred and used within the states of a flow. In the above example, we have defined a flow variable called ‘mailItemModel’ that holds the username and the password for the login mail application. It is possible to explicitly define the start of a flow and if not defined, the first state defined in the xml file will be the start of the flow.
Here the first state defined is the view state mentioning which view to be displayed through its attribute ‘view’. The view also takes the model object which we declared before. By this mapping we essentially say that whatever input that we get from the ‘homeView.jspx’ will be updated to the model object provided appropriate mapping is done in the view file. A transition can happen only when the user clicks the login button after entering the username and the password. The transition in this case takes ‘on’ and ‘to’ attributes. The ‘on’ attribute specifies the event name that will trigger this transition. Within a view there can be multiple transitions, say if there are multiple buttons like Login, Clear and Cancel in which case there are three transitions, in which case the attribute ‘on’ serves as a unique identifier for the source of a transition. The ‘to’ attribute specifies the identifier of the next transition which could be a view transition, action transition or a decision transition.
In our case, we call the ‘viewMailItemsAction’ state, which calls the service method ‘isUserValid()’ for validating the username based on the username. As you can see, calling a service method is represented through the expression. The expression represented through the element ‘evaluate’ contains the service object, method name and the method parameters. The service object ‘mailItemsViewService’ is declared in the Spring configuration file mailview-servlet.xml and because we have declared the flow variable ‘mailItemModel’, its properties like username and password can be used in the expression element. The results of a method call can be saved for later reference in other states also. Here in the flow variable ‘flowScope.isValidUser’, the result is stored which may have the value true or false. Next we make a transition to the decision state represented through ‘checkValidUser’.
In the decision state we identify whether the user is valid or not by checking the result of the variable ‘isValidUser’. If the value turns out to be true, we call the state ‘getMailItems’ for fetching all the mail items and if the value turns out to be false, the state ‘invalidUserView’ is called. The state ‘getMailItems’ turns out to be an action state as it should fetch the mail items by calling the service method retrieveMailItems() and the result is stored in the variable ‘flowScopre.mailItems’. Then it calls the view state ‘viewValidUser’ which displays a welcome kind of view to the user. For an invalid login, the view called will be ‘invalidUserView’ and the view page displayed will be ‘invalidUserView.jspx’. The welcome and the failure views are represented through ‘end-state’ which marks the end of the flow.
Home Page View
The home page view is the page that will be displayed during the start of the flow. Note that this page is a JSP page drawn in XML syntax. This page also uses the form tags from Spring for facilitating the model-view integration.
Home Page
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Mail View Form</title> </head> <body> <form:form method = "post" modelAttribute = "mailItemModel"> Mail Username: <form:input path="username" /> <br /> <input type="submit" name="_eventId_viewMailItems" value="View Mail Items" /> </form:form> </body> </html> </jsp:root>
In the form tag, the attribute ‘modelAttribute’ points to mailItemModel which was declared earlier in the flow definition. This ensures that when the form is submitted, the value of username is set to MailItemModel.username property. Also note that the name of the ‘login’ button follows the pattern of ‘_eventId_’. This event name is the one that is later referred in the ‘on’ attribute of the transition element.
Mail Items View
This is the view page that will be shown when the username is valid. As you can remember, in the flow definition when the username is valid, the mail items are fetched and stored in the flow variable ‘flowScope.mailItems’.
Mail Items View
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" xmlns:c="http://java.sun.com/jsp/jstl/core" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Valid User</title> </head> <body> <h2>List of Mail Items for the username ${username}</h2> <table cellpadding="15" border="1" style="background-color: #ffffcc;"> <tr> <th>From</th> <th>Subject</th> <th>Message</th> </tr> <c:forEach var="mailItem" items="${mailItems}"> <tr> <td>${mailItem.from}</td> <td>${mailItem.subject}</td> <td>${mailItem.message}</td> </tr> <br/> </c:forEach> </table> </body> <a href ="./homeView.htm">Home Page</a> </html> </jsp:root>
By using Spring tags, it is possible to access this flow variable in the view page. This page iterates over the collection of the mail items and formats the display of mail items in a table view.
Invalid User View
This page will be displayed when the username is invalid. It provides an error message to the user.
Invalid User View
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Error!</title> </head> <body> <h2>Invalid username, please try again.</h2> </body> <a href ="./homeView.htm">Home Page</a> </html> </jsp:root>
Model object
We have used the MailItemModel in this application for two purpose. While collecting the username from the login page, the username is mapped to MailItemModel.username and the value is used for verification. Secondly, the relevant mail items applicable for the user is fetched and populated in this model object only.
Mail Item Model
package net.javeabet.spring.webflow.mailview; import java.io.Serializable; public class MailItemModel implements Serializable { private String username; private String from; private String subject; private String message; public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
The various properties like ‘from’, ‘subject’ and ‘message’ are populated and used in the welcome view.
Mail Items Service
This service class provides two functionalities : for authentication – to find out whether the username is valid through the method ‘isUserValid()’ and to fetch the mail items through the method ‘retrieveMailItems()’.
Mail Items Service
package net.javeabet.spring.webflow.mailview; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class MailItemsViewService { private static Map<String, Set> mailItemsMap = new HashMap<String, Set>(); public boolean isUserValid(String username){ boolean validUser = mailItemsMap.keySet().contains(username); return validUser; } public Set retriveMailItems(String username){ Set mailItemsSet = mailItemsMap.get(username); return mailItemsSet; } static{ Set modelSet = new HashSet(); modelSet.add(mailViewModel("[email protected]", "System Upgrade", "Upgrade your system from virus protection")); modelSet.add(mailViewModel("[email protected]", "Timesheet Reminder", "Complete your timesheet entries for this week")); mailItemsMap.put("admin", modelSet); } private static MailItemModel mailViewModel(String from, String subject, String message){ MailItemModel model = new MailItemModel(); model.setFrom(from); model.setSubject(subject); model.setMessage(message); return model; } }
Note that this class populates dummy data for testing purpose only, whereas for a real-time application, this class may make use of a persistence technology framework for hitting the database.
Index Page
As you can remember in the web application’s deployment descriptor, in the welcome page list, the view page mentioned is index.page and this page makes a redirect to homeView.htm. The application’s flow starts from here. The URL pattern “.htm” is mapped with dispatcher servlet which re-directs the flow controller to load and execution the flow definition.
<%@ page session="false"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:redirect url="/homeView.htm" />
What is Sub Flows in SWF?
In this section, we will discuss the concept of subflow with an example. A subflow is just like any flow that can be called from a flow. A subflow provides greater re-use as it can be invoked from a main (or a parent) flow multiple flow. A subflow can also invoke another subflow. In this section, we will develop a simple asset management application that provides a view of assets. The application will initially provide a main login page where the user will enter the credentials for authentication. After logging-in, the page containing different types of assets will be displayed. The user can select a category of assets, and again asset specific login page will be displayed for authentication. After authentication, the user will be shown the list of asset items. Note that the purpose of this example application is to show the usage of subflows in an application.
Web Application’s Deployment Descriptor
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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"> <servlet> <servlet-name>asset-mgmt</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>asset-mgmt</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Note that in the above web application’s deployment descriptor, the name of the servlet is ‘asset-mgmt’ and the spring configuration from the file ‘asset-mgmt-servlet.xml’ will be loaded.
Spring Configuration file
In the below spring configuration file, we have defined separate service definitions for the overall asset management login and for a category specific savings asset. Note that for simplicity, we have added only one category specific asset which is the savings asset and it is always possible to extend the application for different types of assets.
Spring Configuration
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"> <bean id="assetManagementService" /> <bean id="savingsAssetService" /> <bean id="handlerMapping"> <property name="mappings"> <value>/assetMgmtHomeView.htm=flowController</value> </property> </bean> <bean id="flowController"> <property name="flowExecutor" ref="flowExecutor" /> </bean> <webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry"> </webflow:flow-executor> <webflow:flow-registry id="flowRegistry"> <webflow:flow-location id="assetMgmtHomeView" path="/WEB-INF/flows/assetMgmtFlow.xml" /> <webflow:flow-location id="savingsAssetViewFlow" path="/WEB-INF/flows/savingsAssetViewFlow.xml" /> </webflow:flow-registry> </beans>
Other than the application specific bean definitions, the other bean definitions are related to Spring Web MVC and Spring Web Flow. This time, we will define two spring flow definitions file, one for the main flow and the other for the sub flow. So, we have to ensure that the flow registry is configured to load both the Spring Web Flow definition files. The main web flow is defined in the file ‘assetMgmtFlow.xml’ and the subflow is defined in the flow definition flow ‘savingsAssetViewFlow.xml’ file.
Mail Flow Definition
The mail flow definition is given below. Initially the page ‘assetMgmtHomeView’ will be displayed for collecting the credentials to be validated against the service AssetManagementService. If the credentials are invalid, then the flow redirects the control to the page ‘invalidAssetCredentialsView’ page.
Flow definition
<?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"> <var name="assetMgmtModel"/> <view-state id="assetMgmtHomeView" model="assetMgmtModel" view="/jsp/assetMgmtHomeView.jspx"> <transition on="login" to="validateCredentialsAction" /> </view-state> <action-state id="validateCredentialsAction"> <evaluate expression="assetManagementService.isCredentialValid(assetMgmtModel.username, assetMgmtModel.password)" result="flowScope.isCredentialsValid" /> <transition to="checkCredentialsValid" /> </action-state> <decision-state id="checkCredentialsValid"> <if test="flowScope.isCredentialsValid" then="assetDetailsViewId" else="invalidCredentialsView"/> </decision-state> <view-state id="assetDetailsViewId" view="/jsp/assetDetailsView.jspx"> <transition on="loginForSavingsAsset" to="savingsAssetViewId" /> </view-state> <subflow-state id="savingsAssetViewId" subflow="savingsAssetViewFlow"> <input name="assetMgmtModel" /> </subflow-state> <end-state id="invalidCredentialsView" view="/jsp/invalidAssetCredentialsView.jspx" /> </flow>
However, if the credentials are valid, then the control is taken to the state ‘savingsAssetViewId’ which is a subflow represented through the ‘sub-flow-state’ element. The name of the subflow is ‘savingsAssetViewFlow’ and this name is the one which is configured in the list of flow definition identifiers for the flow registry. Note that it is also possible to pass inputs from the main flow to the subflow through the ‘input’ element and the subflow can define flow variables in itself. Note that when a subflow is called from the main flow, the execution of the main flow is paused and will be resumed only when the subflow ends.
Asset Model
Here is the model object for the AssetManagement which defines the properties username and password.
Asset Management Model
package net.javeabet.spring.webflow.assetmgmt; import java.io.Serializable; public class AssetManagementModel implements Serializable{ private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
Asset Management Service
For simplicity, we have hard-coded the credential information within the implementation of the service. In the next section, we will see how to make the implementation to integrate with a persistence framework.
Asset Management Service
package net.javeabet.spring.webflow.assetmgmt; public class AssetManagementService { public boolean isCredentialValid(String username, String password){ return (username.equals("admin") && password.equals("admin")); } }
Asset Home Page View
This page will be displayed to the user at startup for collecting the credentials. Note that the model is tied to assetMgmtModel with the help of Spring Web Flow custom tags.
A.java
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Asset Management Home</title> </head> <body> <form:form method = "post" modelAttribute = "assetMgmtModel"> <h1 align="center"> Asset Management Home </h1> <hr/> <h2> Enter your username and password to login. </h2> Username: <form:input path="username" /> <br /> Password: <form:input path="password" /> <br/><br/> <input type="submit" name="_eventId_login" value="Login" /> </form:form> </body> </html> </jsp:root>
The event transition name is specified as ‘login’ and this will used as an identifier in the transition element.
Asset Details Home View
The asset details page will provide a list of asset categories. Note that this page will be displayed only after a successful login. For simplicity we have included only one category of asset which is the savings asset.
Asset Details Home
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Asset Details</title> </head> <h1 align="center"> Click on the following assets for a detailed view. </h1> <hr/> <body> <center> <form:form method = "post"> <input type="submit" name="_eventId_loginForSavingsAsset" value="Go to Savings Asset" /> </form:form> </center> </body> </html> </jsp:root>
The transition identifier from this view will be ‘loginForSavingsAsset’ which happens when the user clicks the ‘Go to Savings Asset’ button. Again in the savings asset login page, the user will be asked for additional login information for the purpose of illustrating sub flows.
Invalid Credentials View
The page will be displayed when the login credentials for the main asset home page proves to be invalid.
Invalid Credentials View
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Error!</title> </head> <body> <h2 align="center">Invalid username, please try again.</h2> </body> <hr/> <a href ="./assetMgmtHomeView.htm">Home Page</a> </html> </jsp:root>
Sub Flow Definition
Note that the elements in the subflow definition have the similar logic for checking the user existence by calling appropriate service and then displaying the appropriate view. The only difference being that this flow will be called from the main flow.
Sub flow definition
<?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"> <var name="savingsAssetModel"/> <view-state id="savingsAssetViewId" model="savingsAssetModel" view="/jsp/savings/savingsAssetHomeView.jspx"> <transition on="loginForSavingsAsset" to="validateSavingsAssetCredentialsAction" /> </view-state> <action-state id="validateSavingsAssetCredentialsAction"> <evaluate expression="savingsAssetService.isCredentialValid(savingsAssetModel.username, savingsAssetModel.password)" result="flowScope.isSavingsAssetCredentialsValid" /> <transition to="checkSavingsAssetCredentialsValid" /> </action-state> <decision-state id="checkSavingsAssetCredentialsValid"> <if test="flowScope.isSavingsAssetCredentialsValid" then="fetchSavingsDetailsAction" else="invalidSavingsAssetCredentialsView"/> </decision-state> <action-state id="fetchSavingsDetailsAction"> <evaluate expression="savingsAssetService.getSavingsDetails(savingsAssetModel.username, savingsAssetModel.password)" result="flowScope.savingsDetails" /> <transition to="savingsAssetDetailsViewId" /> </action-state> <view-state id="savingsAssetDetailsViewId" view="/jsp/savings/savingsAssetDetailsView.jspx"> </view-state> <view-state id="invalidSavingsAssetCredentialsView" view="/jsp/savings/invalidSavingsAssetCredentialView.jspx" /> </flow>
Savings Model
Here is the definition of the SavingsAssetModel that has the username and the password properties. Note that all model objects should implement the Serializable interface as they will be persisted between flow states.
Savings Model
package net.javeabet.spring.webflow.assetmgmt.savings; import java.io.Serializable; public class SavingsAssetModel implements Serializable { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Savings Asset Service
The service definition for Savings Asset is given below. The class populates the test data for authentication purpose.
Savings Asset Service
package net.javeabet.spring.webflow.assetmgmt.savings; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class SavingsAssetService { private static Map<SavingsAssetModel, Set> userSavingsDetailMap; public boolean isCredentialValid(String username, String password){ Iterator<Map.Entry<SavingsAssetModel, Set>> iterator = userSavingsDetailMap.entrySet().iterator(); while (iterator.hasNext()){ SavingsAssetModel model = iterator.next().getKey(); System.out.println("Model is " + model + ", " + model.getUsername() + ", " + model.getPassword()); if (model != null && model.getUsername().equals(username) && model.getPassword().equals(password)){ return true; }else{ continue; } } return false; } public Set getSavingsDetails(String username, String password){ Iterator<Map.Entry<SavingsAssetModel, Set>> iterator = userSavingsDetailMap.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<SavingsAssetModel, Set> entry = iterator.next(); SavingsAssetModel model = entry.getKey(); if (model != null && model.getUsername().equals(username) && model.getPassword().equals(password)){ return entry.getValue(); }else{ continue; } } return new HashSet(); } static{ userSavingsDetailMap = new HashMap<SavingsAssetModel, Set>(); populateData("admin-savings", "admin-savings", savingsDetail("Fixed Deposit in HDFC", "HDFC", "FD", 20130), savingsDetail("Recurring Deposit in ICICI", "ICICI", "FD", 40320), savingsDetail("Insurance in MNYL", "Max New York Life", "Insurance", 220000)); populateData("guest", "guest", savingsDetail("Fixed Deposit in Citibank", "Citibank", "FD", 12040), savingsDetail("Insurance in SBI", "SBI", "Insurance", 330000)); } private static void populateData(String username, String password, SavingsDetail ... savingsDetails){ SavingsAssetModel model = new SavingsAssetModel(); model.setUsername(username); model.setPassword(password); Set savingsDetailSet = new HashSet(); for (SavingsDetail savingsDetail : savingsDetails){ savingsDetailSet.add(savingsDetail); } userSavingsDetailMap.put(model, savingsDetailSet); } private static SavingsDetail savingsDetail(String name, String organization, String category, double amount){ SavingsDetail savingsDetail = new SavingsDetail(); savingsDetail.setName(name); savingsDetail.setOrganization(organization); savingsDetail.setCategory(category); savingsDetail.setAmount(amount); return savingsDetail; } }
Savings Detail Model
The model for savings detail is given below. The page that displays the savings asset item to the user will display the information from this model object.
Savings Detail Model
package net.javeabet.spring.webflow.assetmgmt.savings; import java.io.Serializable; public class SavingsDetail implements Serializable { private String name; private String organization; private String category; private double amount; public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getOrganization() { return organization; } public void setOrganization(String organization) { this.organization = organization; } }
Savings Asset Home Page View
This page is the home page for savings asset for collecting the username/password information. This home page will be called from sub flow. Note that the model that is coupled to the view is ‘savingsAssetModel’ and the transition identifier is ‘loginForSavingsAsset’.
Savings Asset Home Page
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Asset Management Home</title> </head> <body> <form:form method = "post" modelAttribute = "savingsAssetModel"> <h1 align="center"> Savings Asset Home Page </h1> <hr/> <h2> Enter your username and password to login. </h2> Username: <form:input path="username" /> <br/> Password: <form:password path="password" /> <br/><br/> <input type="submit" name="_eventId_loginForSavingsAsset" value="Login" /> </form:form> </body> </html> </jsp:root>
Savings Asset Details View
When the authentication is valid, then the asset items will be fetched and will be placed in the flow variable ‘mailItems’. This flow variable is referenced here and is iterated for displaying the items in the view.
Savings Asset Details View
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" xmlns:c="http://java.sun.com/jsp/jstl/core" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Savings Asset Items</title> </head> <body> <h2 align="center">Savings asset details</h2> <table cellpadding="15" border="1" style="background-color: #ffffcc;" align="center"> <tr> <th>Name</th> <th>Organization</th> <th>Category</th> <th>Savings Amount</th> </tr> <c:forEach var="savingsDetail" items="${savingsDetails}"> <tr> <td>${savingsDetail.name}</td> <td>${savingsDetail.organization}</td> <td>${savingsDetail.category}</td> <td>${savingsDetail.amount}</td> </tr> <br/> </c:forEach> </table> </body> <a href ="./assetMgmtHomeView.htm">Home Page</a> </html> </jsp:root>
Invalid Savings Asset View
This page will be displayed when the credentials for savings asset is invalid.
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form" version="2.1"> <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" /> <html> <head> <title>Error!</title> </head> <body> <h2 align="center">Invalid username for savings asset, please try again.</h2> <hr/> </body> <a href ="./assetMgmtHomeView.htm">Home Page</a> </html> </jsp:root>
Persistence Context
It is quite common that web applications interact with the database for reading and writing data and Spring Web Flow provides support for integration with Hibernate and JPA currently. The persistence nicely integrates with the flow and it is possible to create the persistence context when the flow is created, close the context when the flow ends. When the commit operation has to be done, the persistence context can be specified by supplying the attribute “commit=true” at the state level. We will illustrate the way of creating and committing persistence context with the help of the sample application in this section. The sample application provides facilities for creating and viewing all the bugs. When creating a bug, the bug information will be persisted in the specified data store using the JPA persistence technology.
Web Application’s Deployment Descriptor
The following listing provides information on the web application’s deployment descriptor. The name of the dispatcher servlet is ‘bug’ and it is expected that the Spring bean definitions are defined in the configuration file ‘bug-servlet.xml’.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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"> <servlet> <servlet-name>bug</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>bug</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Spring Configuration file
Other than the regular bean definitions defined for the application, Spring Web MVC and Spring Web Flow, the following listing also provides bean definitions that are specific to the persistence.
Spring configuration
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"> <bean id="bugService" /> <!-- Spring MVC --> <bean id="handlerMapping"> <property name="mappings"> <value>/bugHomeView.htm=flowController</value> </property> </bean> <bean id="flowController"> <property name="flowExecutor" ref="flowExecutor" /> </bean> <!-- Spring Web Flow --> <webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry"> <webflow:flow-execution-listeners> <webflow:listener ref="jpaFlowExecutionListener" /> </webflow:flow-execution-listeners> </webflow:flow-executor> <webflow:flow-registry id="flowRegistry"> <webflow:flow-location id="bugHomeView" path="/WEB-INF/flows/bugFlow.xml" /> </webflow:flow-registry> <bean id="dataSource" destroy-method="close" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/__default" /> <property name="resourceRef" value="true" /> </bean> <bean id="entityManagerFactory"> <property name="dataSource" ref="dataSource" /> <property name="persistenceUnitName" value="bug" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="true" /> </bean> <bean id="transactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean /> <bean id="jpaFlowExecutionListener"> <constructor-arg ref="entityManagerFactory" /> <constructor-arg ref="transactionManager" /> </bean> </beans>
The initial integration point is the definition of the flow execution listener which will be execution when the flow execution happens. The listener bean identifier is ‘jpaFlowExecutionListener’ and it references to bean definitions which are ‘entityManagerFactory’ and ‘transactionManager’. The entity manager factory bean references to data source information combined with the persistence unit information. The source code of persistence unit configuration file is attached herewith. Similarly the transaction manager is configured in Spring’s JPA transaction manager.
Flow Definition
In the following flow definition, the startup page that is shown to the user will be ‘bugHomeView’. From there the user can create a bug or can view the list of created bugs by clicking the appropriate links in which case, either the state transition ‘createBugView’ or ‘viewAllBugsAction’ can happen. From there we call the appropriate service methods. The integration point for attaching a persistence context with the flow is defined through the element ‘persistence-context’ element.
Flow definition
<?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"> <persistence-context /> <var name="bug"/> <view-state id="bugHomeView" view="/jsp/bugHomeView.jspx"> <transition on="viewAllBugs" to="viewAllBugsAction" /> <transition on="createBug" to="createBugView" /> </view-state> <action-state id="viewAllBugsAction"> <evaluate expression="bugService.getAllBugs()" result="flowScope.allBugs" /> <transition to="viewAllBugsView" /> </action-state> <end-state id="viewAllBugsView" view="/jsp/viewAllBugs.jspx"/> <view-state id="createBugView" view="/jsp/createBugView.jspx"> <transition on="createBug" to="createBugAction" /> <transition on="cancelCreateBug" to="cancelCreateBugView" /> </view-state> <action-state id="createBugAction"> <evaluate expression="bugService.createBug(bug.description, bug.moduleName, bug.raisedBy)"/> <transition to="createBugConfirmationView" /> </action-state> <end-state id="invalidUserView" view="/jsp/invalidUserView.jspx" /> <end-state id="createBugConfirmationView" view="/jsp/createBugConfirmationView.jspx" commit="true" /> <end-state id="cancelCreateBugView" view="/jsp/cancelCreateBugConfirmationView.jspx"/> </flow>
A commit operation is required when a bug is created by the user which happens in the state ‘createBugConfirmationView’, when the user will be prompted to create the bug for final confirmation. Hence the attribute ‘commit=true’ is specified in that state.
Bug Service
The following listing provides the bug service definitions which contains the service methods for creating a bug and for viewing all the newly created bugs.
Bug Service
package net.javabeat.articles.spring.webflow.bug; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; public class BugService { @PersistenceContext private EntityManager entityManager; public Set getAllBugs(){ Set bugSet = new HashSet( entityManager.createQuery("from BUG").getResultList()); return bugSet; } public void createBug(String description, String moduleName, String raisedBy){ Bug newBug = new Bug(); newBug.setDescription(description); newBug.setModuleName(moduleName); newBug.setRaisedBy(raisedBy); entityManager.persist(newBug); } }
Note that in the above code, the persistence context is annotated for the entityManager. This ensures the presence of the persistence unit configuration file in the application’s classpath. Note that we have used the Java Persistence Query Language for creating and viewing bugs.
Model object
The model object is annotated with JPA annotations for making them persistent in the database. The properties ‘description’, ‘moduleName’ and ‘raisedBy’ properties are annotated with @Column annotations and the column names are mapped as ‘DESCRIPTION’, ‘MODULE_NAME’ and ‘RAISED_BY’.
Bug
package net.javabeat.articles.spring.webflow.bug; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity(name = "Bug") @Table(name = "BUG") public class Bug implements Serializable { @Id private String id; @Column(name = "DESCRIPTION") private String description; @Column(name = "MODULE_NAME") private String moduleName; @Column(name = "RAISED_BY") private String raisedBy; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getModuleName() { return moduleName; } public void setModuleName(String moduleName) { this.moduleName = moduleName; } public String getRaisedBy() { return raisedBy; } public void setRaisedBy(String raisedBy) { this.raisedBy = raisedBy; } }
To ensure that the above model be persisted, we have defined the @Entity annotation mentioning that in the table ‘BUG’, the data will be persisted. Note that we have annotated the model with JPA annotations and have used the Spring’s transaction manager support for managing transactions. To make sure that the JPA annotations are visible to Spring, we have added the bean ‘PersistenceAnnotationBeanPostProcessor’ in the ‘bean-servlet’ configuration file.
The rest of the view definition files ‘bugHomeView.jspx’, ‘createBugView.jspx’, ‘viewAllBugs.jspx’, ‘createBugConfirmationView.jspx’ and ‘cancelCreateBugConfirmationView.jspx’ that carries the logic of presenting and collecting information from the user is included in the source code of the article.
Conclusion
This article started with the illustration on setting up the application’s Spring Web Flow through the ‘MailView’ example where it demonstrated the underlying concepts like Flow, State, Transition, Flow definition, Flow execution and Flow registry. It then went on to extend the discussion on Sub flows by presenting a sample application. Finally the usage of Spring Web Flow managed persistence context is explained with a sample that creates and maintains bugs.
also read: