Wednesday, April 6, 2011

Spring-WebFlow SubFlow

Hey...

As you remember, I have a post about Spring-WebFlow. Here I'm going to explain SubFlow. We had done simple CRUD by Spring-WebFlow in my past sample. I'm going to generalize Simple CRUD work flow as a sub flow. So every simple crud use cases can use this general work flow resulting the development gets faster.

First of all simple CRUD configuration:
<?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">
    <view-state id="list" view="base.list" model="simpleCrudModel">
        <on-render>
            <evaluate expression="simpleCrudService.findAll()" result="requestScope.modelList"/>
        </on-render>
        <transition on="add" to="edit">
            <evaluate expression="simpleCrudService.createEntity()" result="flowScope.simpleCrudModel"/>
        </transition>
        <transition on="view" to="view">
            <evaluate expression="simpleCrudService.findById(requestParameters.selectedId)"
                      result="flowScope.simpleCrudModel"></evaluate>
        </transition>
    </view-state>
    <view-state id="view" view="base.view" model="simpleCrudModel">
        <transition on="back" to="finish"></transition>
        <transition on="edit" to="edit"></transition>
    </view-state>
    <view-state id="edit" view="base.edit" model="simpleCrudModel">
        <transition on="back" to="view"></transition>
        <transition on="save" to="saving">
        </transition>
        <transition on="delete" to="deleting">
        </transition>
    </view-state>

    <decision-state id="saving">
        <on-entry>
            <evaluate expression="simpleCrudService.saveSuccessMessage"
                      result="flashScope.saveSuccessMessage"></evaluate>
            <evaluate expression="simpleCrudService.saveErrorMessage"
                      result="flashScope.saveErrorMessage"></evaluate>
        </on-entry>
        <if test="simpleCrudService.saveOrUpdate(simpleCrudModel)" then="view" else="edit"/>
    </decision-state>
    <decision-state id="deleting">
        <on-entry>
            <evaluate expression="simpleCrudService.deleteSuccessMessage"
                      result="flashScope.deleteSuccessMessage"></evaluate>
            <evaluate expression="simpleCrudService.deleteErrorMessage"
                      result="flashScope.deleteErrorMessage"></evaluate>
        </on-entry>
        <if test="simpleCrudService.delete(simpleCrudModel)" then="view" else="edit"/>
    </decision-state>
    <end-state id="finish"/>
</flow>

Every parent flow should provide it a service class implementing BaseService and use case name in conversationScope scope. simpleCrudService and baseName are the names of parameters in conversationScope. So I can use simpleCrudService  as a service class in simplecrud-config.xml.

I have 3 major states:
  • List: This is an initial state. I should fetch a list of model by simpleCrudService and put it into the requestScope named modelList. this state has two major events:
    • view: Fetches the selected row by simpleCrudService and set it into the simpleCrudModel then goes to View state. A request parameter should be sent from the client named selectedId. I can find the selected row by this sent id(red line).
    • add: creates a simpleCrudModel by simpleCrudService and goes to Edit state.
  • View: This state define simpleCrudModel as model to be used in JSPs as command name. This state has two major events:
    • edit: Goes to edit state.
    • back: Goes to the end state named 'finish'.
  • Edit: This state also define simpleCrudModel as model to be used in JSPs as command name. This state has three major events:
    • save: Saves simpleCrudModel and returns to view if success
    • delete: Deletes simpleCrudMode and returns to view.
    • back: Returns to View state.
I use 'base.list', 'base.view', 'base.edit' as views of view-state. According my tiles configuration, they will be forwarded to '/WEB-INF/pages/base/base-list.jsp', '/WEB-INF/pages/base/base-view.jsp' and '/WEB-INF/pages/base/base-edit.jsp'. These are interface JSPs to connect to destination JSPs.
base-list.jsp
<jsp:include page="/WEB-INF/pages/${baseName}/${baseName}-list.jsp"/>
base-view.jsp
<jsp:include page="/WEB-INF/pages/${baseName}/${baseName}-view.jsp"/>
base-edit.jsp
<jsp:include page="/WEB-INF/pages/${baseName}/${baseName}-edit.jsp"/>

baseName are set from parent flow in conversationScope and I use it here to import the destination jsp page. That's because we can not use conversationScope view of View-State node.
OK the general simplecrud-config.xml is done. Now it is ready to use in flows as a sub flow.
testflow-config.xml:
<?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='testflowService' result="conversationScope.simpleCrudService"/>
        <evaluate expression='"testflow"' result="conversationScope.baseName"/>
    </on-start>
    <subflow-state id="simplecrudflow" subflow="simplecrud-config">
        <transition on="finish" to="simplecrudflow"/>
    </subflow-state>
</flow>
No extra configuration is needed just just red lines are enough for every simple crud use cases. I use a transition in subflow-state. That's because any time the flow ends, restarts itself again.

Here are JSPs. The red lines are the variable which is set in subflow configuration.

testflow-list.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<form:form commandName="simpleCrudModel">
    <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
    <input type="hidden" name="selectedId" id="selectedId"/>
    <table>
        <tr>
            <th>firstname</th>
            <th>lastname</th>
            <th></th>
        </tr>
        <c:forEach items="${modelList}" var="testflow">
            <tr>
                <td>${testflow.firstname}</td>
                <td>${testflow.lastname}</td>
                <td><input type="submit" name="_eventId_view" value="view"
                           onclick="document.getElementById('selectedId').value = '${testflow.id}';"/></td>
            </tr>
        </c:forEach>
    </table>
    <input type="submit" name="_eventId_add" value="add"/>
</form:form>

testflow-view.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<form:form commandName="simpleCrudModel">
    <div style="background-color:lightgreen;">${saveSuccessMessage}</div>
    <div style="background-color:lightgreen;">${deleteSuccessMessage}</div>
    <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
    <table>
        <tr>
            <td><label>first name</label></td>
            <td>${simpleCrudModel.firstname}</td>
        </tr>
        <tr>
            <td><label>last name</label></td>
            <td>${simpleCrudModel.lastname}</td>
        </tr>
    </table>
    <input type="submit" name="_eventId_back" value="back"/>
    <c:if test="${empty deleteSuccessMessage}">
        <input type="submit" name="_eventId_edit" value="edit"/>
    </c:if>
</form:form>

testflow-edit.jsp:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<form:form commandName="simpleCrudModel">
    <div style="background-color:red">${deleteErrorMessage}</div>
    <div style="background-color:red">${saveErrorMessage}</div>
    <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
    <table>
        <tr>
            <td><label for="firstname">first name</label></td>
            <td><form:input id="firstname" path="firstname"/></td>
        </tr>
        <tr>
            <td><label for="lastname">last name</label></td>
            <td><form:input id="lastname" path="lastname"/></td>
        </tr>
    </table>
    <input type="submit" name="_eventId_save" value="save"/>
    <input type="submit" name="_eventId_back" value="back"/>
    <input type="submit" name="_eventId_delete" value="delete"/>
</form:form>

SourceCode

Now it's time to download the application and try it yourselves. All needed jar files are available in my past sample. You can copy all of them to "[Project Root]/lib/". After deploying, the start page URL will be: http://localhost:8080/user/flows.html?_flowId=testflow-config
Mysql schema script is available in "[Project Root]/db/springwebflow.sql" you can restore it to your mysqlserver. The datasource property is available here: "[Project Root]/src/database.properties". When you run the application you should first login. The Administrator account information is:
Username: administrator
Password: 123456


all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

3 comments:

omidp said...

one day u will be top seam follower for sure :-D ....

mostafa rastgar said...

I love seam and seam lover. :D

Anonymous said...

I want a sample Spring WebFlow Appln in which
1. we have 2 textboxes and a submit button.
2. when i click on submit button the details shoud be printed on next screen

Please provide me sample Appln ASAP from Stratch