Thursday, August 14, 2008

Spring Web Flow 1.0.5

The key features provided for by Spring Web Flow are:
  • The ability to define an application’s flow external to the application’s logic
  • The ability to create reusable flows that can be used across multiple applications


What about AbstractWizardFormController?
AbstractWizardFormController is just a form controller where the form is spread across multiple pages. Moreover, the “flow” of a subclass of AbstractWizardFormController is still embedded directly within the controller implementation, making it hard to discern flow logic from application logic. Spring Web Flow loosens the coupling between an application’s code and its page flow by enabling you to define the flow in a separate, selfcontained flow definition.

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:services-config.xml
</param-value>
</context-param>

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

<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>


Spring application context
<?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:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
<!-- Messages -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages</value>
</list>
</property>
</bean>

<!-- Resolves flow view names to .jsp templates -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>

<!-- flow controller -->
<bean name="/flow.htm"
class="org.springframework.webflow.executor.mvc.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
</bean>

<!-- 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/flows/**-flow.xml" />
</flow:registry>
......


The configuration file for a specific web flow
Let's say we have a web flow called resetPassword. The URL to trigger this web flow is http://.../.../flow.htm?_flowId=resetPassword-flow.
Spring will look up the configuration in the file "resetPassword-flow.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-1.0.xsd">
<start-state idref="checkLogin" />
<action-state id="checkLogin">
<action bean="checkLoginAction" />
<transition on="loggedIn" to="inputNewPassword" />
<transition on="notLoggedIn" to="inputAccountID" />
</action-state>
<view-state id="inputAccountID" view="inputAccountID">
<render-actions>
<action bean="accountIdFormAction" method="setupForm" />
</render-actions>
<transition on="submit" to="securityQuestions">
<action bean="accountIdFormAction" method="bindAndValidate" />
</transition>
</view-state>
<view-state id="securityQuestions" view="securityQuestions">
<render-actions>
<bean-action bean="accountService" method="getAccountById">
<method-arguments>
<argument expression="flowScope.accountIdForm" /> <!-- ${requestParameters.accountId} -->
</method-arguments>
<method-result name="account" />
</bean-action>
</render-actions>
<transition on-exception="sample.spring.mvc.webflow.stub.AccountNotFoundException" to="inputAccountID">
<set attribute="accountNotFound" scope="flash" value="true"/>
</transition>
<transition on="verify" to="verifySecurityQuestions" />
</view-state>
<action-state id="verifySecurityQuestions">
<action bean="verifySecurityQuestionsAction" />
<transition on="success" to="inputNewPassword" />
<transition on="failure" to="securityQuestions" />
</action-state>
<view-state id="inputNewPassword" view="inputNewPassword">
<render-actions>
<action bean="resetPasswordForm" method="setupForm" />
</render-actions>
<transition on="submit" to="passwordChanged">
<action bean="resetPasswordForm" method="bindAndValidate" />
<bean-action bean="accountService" method="updatePassword">
<method-arguments>
<argument expression="requestParameters.password" />
</method-arguments>
</bean-action>
</transition>
</view-state>
<end-state id="passwordChanged" view="passwordChanged" />

<import resource="resetPassword-flow-beans.xml" />
</flow>


start state and end state
One web flow can only have one start-state but can have multiple end-state.

action state and view state
Action state will execute a method in a java class.
View state is going to display a screen.
<action-state id="checkLogin">
<action bean="checkLoginAction" />
<transition on="loggedIn" to="inputNewPassword" />
<transition on="notLoggedIn" to="inputAccountID" />
</action-state>

This action-state will call "checkLoginAction" which was configured in "resetPassword-flow-beans.xml".
 <bean id="checkLoginAction" class="sample.spring.mvc.webflow.CheckLoginAction" />
Here is the java class
public class CheckLoginAction implements Action {

public static final String LOGGED_IN = "loggedIn";
public static final String NOT_LOGGED_IN = "notLoggedIn";

public Event execute(final RequestContext context) throws Exception {
final Event event;

String accountId = (String)context.getExternalContext().
getSessionMap().get("accountId");

if (StringUtils.isEmpty(accountId)) {
event = new Event(this, NOT_LOGGED_IN);
} else {
event = new Event(this, LOGGED_IN);
}
return event;
}
}
The method of web flow action class should return a Event which is composed with a String. Spring will lookup this String in transitions of this action. In the example above,
 <transition on="loggedIn" to="inputNewPassword" />
<transition on="notLoggedIn" to="inputAccountID" />
if the event "loggedIn" is returned, it will go to the state "inputNewPassword". If the event "notLoggedIn" is returned, it will go to the state "inputAccountID".

The state "inputAccountID" is an view-state here.
<view-state id="inputAccountID" view="inputAccountID">
<render-actions>
<action bean="accountIdFormAction" method="setupForm" />
</render-actions>
<transition on="submit" to="securityQuestions">
<action bean="accountIdFormAction" method="bindAndValidate" />
</transition>
</view-state>

Spring will look up the view="inputAccountID". Based on the viewResolver configuration above
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
. It will display /WEB-INF/jsp/inputAccountID.jsp

Before display the screen, spring will run render-action.

Render actions
<render-actions>
<action bean="accountIdFormAction" method="setupForm" />
</render-actions>
Render actions are executed by a view state before they render the view. In this case we use the "setupForm" method to prepare the Spring form handling machinery to properly display the form.

JSP for web flow
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>


<form:form method="POST" action="flow.htm" commandName="accountIdForm">
<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey}">


<b>Please input your Account ID</b>

<input type="text" name="accountId" />
<form:errors path="accountId" cssClass="error" />
<p><input type="submit" class="button" name="_eventId_submit"
value="Submit" /></p>
</form:form>
We should put one hidden element "_flowExecutionKey".
The other thing special is the name of the submit button "_eventId_submit". When the form is submitted, the name of this parameter is split into two parts. The first part, _eventId, signals that we’re identifying the event. The second part, submit,is the name of the event to be triggered when the form is submitted.
<transition on="submit" to="securityQuestions">
<action bean="accountIdFormAction" method="bindAndValidate" />
</transition>
You also can put action inside transition. The example above means it will bind the form values to the bean and then run the validation.

Bean action
<view-state id="securityQuestions" view="securityQuestions">
<render-actions>
<bean-action bean="accountService" method="getAccountById">
<method-arguments>
<argument expression="flowScope.accountIdForm" />
</method-arguments>
<method-result name="account" />
</bean-action>
</render-actions>
<transition
on-exception="sample.spring.mvc.webflow.stub.AccountNotFoundException"
to="inputAccountID">
<set attribute="accountNotFound" scope="flash" value="true"/>
</transition>
<transition on="verify" to="verifySecurityQuestions" />
</view-state>
Bean-action will call a method before render the page. The example above will call method "getAccountById" of "accountService" which is defined in spring application context "service-config.xml" (check web.xml). The parameter is "flowScope.accountIdForm" while accoutIdForm is the commandName declared in the form in jsp. The returned object will be put in context with the name "account".

Exception handling
If the method above throw any exception, it could be caught and forward the request to the correct page.
<transition 
on-exception="sample.spring.mvc.webflow.stub.AccountNotFoundException"
to="inputAccountID">
<set attribute="accountNotFound" scope="flash" value="true"/>
</transition>


Bean confirguration for the web flow
Usually, the bean configuration will be put in a separated file. In the example above, the bean configuration file is "resetPassword-flow-beans.xml".
<beans xmlns="http://www.springframework.org/schema/beans"
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.0.xsd">

<!-- Search form action that setups the form and processes form submissions -->
<bean id="checkLoginAction" class="sample.spring.mvc.webflow.CheckLoginAction" />

<bean id="accountIdFormAction" class="org.springframework.webflow.action.FormAction">
<property name="formObjectClass" value="sample.spring.mvc.webflow.form.AccountIdForm"/>
<property name="validator">
<bean class="sample.spring.mvc.webflow.validator.AccountIdRegExpValidator">
<property name="pattern" value="[\d]*" /> <!-- only number is allowed in account Id -->
</bean>
</property>
</bean>

<bean id="resetPasswordForm" class="org.springframework.webflow.action.FormAction">
<property name="formObjectClass" value="sample.spring.mvc.webflow.form.ResetPasswordForm"/>
<property name="validator">
<bean class="sample.spring.mvc.webflow.validator.ResetPasswordFormValidator" />
</property>
</bean>

<bean id="verifySecurityQuestionsAction" class="sample.spring.mvc.webflow.VerifySecurityQuestionsAction">
<property name="accountService" ref="accountService"/>
</bean>

</beans>




No comments:

Post a Comment