Incorporated Ross' documentation for the sellitem sample.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -46,7 +46,9 @@
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Sellitem - demonstrates a wizard with conditional transitions, flow scope, flow execution redirects, and continuations.</para>
|
||||
<para>
|
||||
<link linkend="sellitem-sample">Sellitem</link> - demonstrates a wizard with conditional transitions, flow scope, flow execution redirects, and continuations.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Phonebook-Portlet - the phonebook sample in a Portlet environment (notice how the flow definitions do not change).</para>
|
||||
@@ -1358,5 +1360,431 @@ addMapping(mapping.source("requestParameters.data").target("flowScope.item").val
|
||||
to the initial "displayItemList" state.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</sect1>
|
||||
<sect1 id="sellitem-sample">
|
||||
<title>Sellitem Example</title>
|
||||
<sect2>
|
||||
<title>Overview</title>
|
||||
<para>
|
||||
The Sellitem example demonstrates using Web Flow to build a
|
||||
shopping cart wizard with
|
||||
a shipping rate subflow, decision states, service and data access
|
||||
Spring POJO beans, Spring 2.0 form tags, and Web Flow's
|
||||
FormAction bean for data binding, validation, and
|
||||
error reporting.
|
||||
</para>
|
||||
<para>
|
||||
The Sellitem example breaks down its Spring application configuration
|
||||
into a number of files organized according to purpose.
|
||||
Although the example itself uses a small number of beans you
|
||||
may consider organizing a real-world application (with many more
|
||||
beans) according to similar principles. Before going into the specifics
|
||||
of each individual context use the diagram below to
|
||||
get a brief overview of all configuration files including their
|
||||
location and purpose.
|
||||
</para>
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/sellitem-configuration.png" format="PNG" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="images/sellitem-configuration.png" format="PNG" align="center"/>
|
||||
</imageobject>
|
||||
<caption>
|
||||
<para>Sellitem Spring & Web Flow Application Wiring</para>
|
||||
</caption>
|
||||
</mediaobject>
|
||||
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Web.xml</title>
|
||||
<para>
|
||||
The web.xml configuration maps "*.htm" requests to the sellitem servlet -
|
||||
a Spring MVC DispatcherServlet:
|
||||
<programlisting>
|
||||
<servlet>
|
||||
<servlet-name>sellitem</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
/WEB-INF/sellitem-servlet-config.xml
|
||||
/WEB-INF/sellitem-webflow-config.xml
|
||||
</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>sellitem</servlet-name>
|
||||
<url-pattern>*.htm</url-pattern>
|
||||
</servlet-mapping>
|
||||
</programlisting>
|
||||
The contextConifgLocation parameter for the DispatcherServlet indicates the
|
||||
Spring MVC web context for the sellitem servlet is spread over two xml files:
|
||||
sellitem-servlet-config.xml and sellitem-webflow-config.xml.
|
||||
The web.xml also requests an additional Spring context to be loaded
|
||||
from the classpath through the ContextLoaderListener:
|
||||
<programlisting>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
classpath:org/springframework/webflow/samples/sellitem/services-config.xml
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
</programlisting>
|
||||
This service layer context defines beans to be referenced from
|
||||
web flow definitions. The next section discusses the
|
||||
content of this context in more detail.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Services-config.xml</title>
|
||||
<para>
|
||||
The services-config.xml loaded from the classpath through Spring MVC's
|
||||
ContextLoaderListener defines several beans for the service
|
||||
and data access layers of the application. For example,
|
||||
the service context defines a DAO bean ("saleProcessor") and injects
|
||||
it with a data source:
|
||||
<programlisting>
|
||||
<bean id="saleProcessor" class="org.springframework.webflow.samples.sellitem.JdbcSaleProcessor">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
|
||||
<property name="url" value="jdbc:hsqldb:mem:sellItem"/>
|
||||
<property name="username" value="sa"/>
|
||||
</bean>
|
||||
</programlisting>
|
||||
The services context also declares a bean of type InMemoryDatabaseCreator
|
||||
set to autowire by type meaning that its fields will be compared against the types of
|
||||
beans available in the context and will be automatically set when a match is found.
|
||||
Hence the dataSource bean is used to set the dataSource property
|
||||
of InMemoryDatabaseCreator:
|
||||
<programlisting>
|
||||
<bean id="databaseCreator" class="org.springframework.webflow.samples.sellitem.InMemoryDatabaseCreator"
|
||||
autowire="byType"/>
|
||||
</programlisting>
|
||||
Looking inside the InMemoryDatabaseCreator, its initDao() method invoked
|
||||
during context initialization creates a table called T_SALES for use by the sample
|
||||
application. This table is created in an in-memory hsqldb database called
|
||||
sellitem (based on the url property of the dataSource bean).
|
||||
It's also worth noting the bean declarations related to declarative
|
||||
transaction management:
|
||||
<programlisting>
|
||||
<tx:annotation-driven/>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
</programlisting>
|
||||
The "<tx:annotation-driven>"declaration indicates transaction
|
||||
configuration is governed by Java 5 annotations used in bean classes
|
||||
such as this annotation in the SaleProcessor interface:
|
||||
<programlisting>
|
||||
@Transactional
|
||||
public interface SaleProcessor {
|
||||
|
||||
public void process(Sale sale);
|
||||
}
|
||||
</programlisting>
|
||||
For annotated beans the Spring container automatically creates
|
||||
proxies according to the transaction semantics in the annotation
|
||||
metadata. The "<tx:annotation-driven>" tag has a transaction-manager
|
||||
attribute but this attribute is not required if the transaction
|
||||
manager bean is named "transactionManager".
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Spring MVC Context</title>
|
||||
<para>
|
||||
The Spring MVC web context is split over two files -
|
||||
sellitem-servlet-config.xml and sellitem-webflow-config.xml.
|
||||
The sellitem-servlet-config.xml defines
|
||||
a controller and a view resolver.
|
||||
<programlisting>
|
||||
<bean name="/pos.htm" class="org.springframework.webflow.executor.mvc.FlowController">
|
||||
<property name="flowExecutor" ref="flowExecutor" />
|
||||
</bean>
|
||||
|
||||
<!-- Maps flow view-state 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>
|
||||
</programlisting>
|
||||
FlowController is a web flow controller extending Spring MVC's AbstractController
|
||||
delegating requests (in this case for the "/pos.htm" servlet path) to the
|
||||
flowExecutor bean it is configured with. FlowController acts
|
||||
as gateway to Web Flow and a single controller instance can serve
|
||||
the application as most of the actual control logic is encapsulated
|
||||
in web flow definitions.
|
||||
</para>
|
||||
<para>
|
||||
The sellitem-webflow-config.xml defines web flow specific beans such as
|
||||
a flow executor, a flow registry and a flow listener beans:
|
||||
<programlisting>
|
||||
<!-- Launches new flow executions and resumes existing executions -->
|
||||
<flow:executor id="flowExecutor" registry-ref="flowRegistry">
|
||||
<flow:execution-listeners>
|
||||
<flow:listener ref="listener" criteria="sellitem-flow" />
|
||||
</flow:execution-listeners>
|
||||
</flow:executor>
|
||||
|
||||
<!-- Creates the registry of flow definitions for this application -->
|
||||
<flow:registry id="flowRegistry">
|
||||
<flow:location path="/WEB-INF/flows/**/*-flow.xml" />
|
||||
</flow:registry>
|
||||
|
||||
<!-- Observes the lifecycle of sellitem-flow executions -->
|
||||
<bean id="listener"
|
||||
class="org.springframework.webflow.samples.sellitem.SellItemFlowExecutionListener" />
|
||||
</programlisting>
|
||||
The FlowExecutor is the central entry point into the
|
||||
Spring Web Flow system. It drives the execution of flow definitions
|
||||
configured through the flowRegistry. The flowRegistry bean is configured
|
||||
to load definitions from files ending with "-flow.xml" in any
|
||||
subdirectory of /WEB-INF/flows. This matches to
|
||||
sellitem-flow.xml, shipping-flow.xml, sellitem-simple-flow.xml,
|
||||
sellitem-conversation-scope-flow.xml and shipping-conversation-scope-flow.xml.
|
||||
</para>
|
||||
<para>
|
||||
As shown here the flow executor can also be configured with a flow
|
||||
listener, which is a callback mechanism for flow execution lifecycle events.
|
||||
The SellItemFlowExecutionListener extends FlowExecutionListenerAdapter -
|
||||
a default implementation of the FlowExecutionListener interface
|
||||
sparing the need to implement methods for all lifecycle events.
|
||||
</para>
|
||||
<para>
|
||||
Looking inside SellItemFlowExecutionListener it implements the stateEntering
|
||||
method executed whenever a new state is about to be entered.
|
||||
The logic in this method checks if the current web flow state
|
||||
has an attribute named "role" and if so it ensures the user
|
||||
has that role:
|
||||
<programlisting>
|
||||
String role = nextState.getAttributes().getString("role");
|
||||
if (StringUtils.hasText(role)) {
|
||||
HttpServletRequest request = ((ServletExternalContext)context.getExternalContext()).getRequest();
|
||||
if (!request.isUserInRole(role)) {
|
||||
throw new EnterStateVetoException(context.getActiveFlow().getId(), context.getCurrentState().getId(),
|
||||
nextState.getId(), "State requires role '" + role
|
||||
+ "', but the authenticated user doesn't have it!");
|
||||
}
|
||||
}
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Based on the above definitions - web.xml, Spring MVC controller bean, and
|
||||
web flow registry, the sellitem-flow can be initiated with the
|
||||
following URI:
|
||||
<programlisting>
|
||||
/swf-sellitem/pos.htm?_flowId=sellitem-flow
|
||||
</programlisting>
|
||||
<emphasis>
|
||||
Note: although it is possible to invoke the shipping-flow directly as well,
|
||||
it expects an input attribute and is intended to be invoked as a subflow.
|
||||
</emphasis>
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Sellitem-beans.xml</title>
|
||||
<para>
|
||||
Before tracing the sequence of states in sellitem-flow.xml notice the
|
||||
import declaration at the bottom of that file:
|
||||
<programlisting>
|
||||
<import resource="sellitem-beans.xml"/>
|
||||
</programlisting>
|
||||
The sellitem-beans.xml located in the same directory declares a
|
||||
web flow FormAction bean for use in the flow definition and configures
|
||||
it with a SaleValidator and a SellItemPropertyEditorRegistrar:
|
||||
<programlisting>
|
||||
<!-- Manages setting up, binding input to, and validating a Sale "backing wizard form object" -->
|
||||
<bean id="formAction" class="org.springframework.webflow.action.FormAction">
|
||||
<property name="formObjectClass" value="org.springframework.webflow.samples.sellitem.Sale"/>
|
||||
<property name="validator">
|
||||
<bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/>
|
||||
</property>
|
||||
<!-- Installs property editors used to format non-String fields like 'shipDate' -->
|
||||
<property name="propertyEditorRegistrar">
|
||||
<bean class="org.springframework.webflow.samples.sellitem.SellItemPropertyEditorRegistrar"/>
|
||||
</property>
|
||||
</bean>
|
||||
</programlisting>
|
||||
The SellValidator will be used to validate form input data.
|
||||
The SellItemPropertyEditorRegistrar is responsible for registering
|
||||
custom property editors. Such editors are used to bind text data from
|
||||
HTML form fields to server-side Objects. For example
|
||||
SellItemPropertyEditorRegistrar registers a custom date
|
||||
editor:
|
||||
<programlisting>
|
||||
public void registerCustomEditors(PropertyEditorRegistry registry) {
|
||||
registry.registerCustomEditor(Date.class,
|
||||
new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true));
|
||||
}
|
||||
</programlisting>
|
||||
This editor will bind the shipDate form field in shippingDetailsForm.jsp
|
||||
to the shipDate property of the Sale object on the server side.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Sellitem-flow Flow Definition</title>
|
||||
<para>
|
||||
The sellitem-flow uses a start action to invoke the setupForm method of the
|
||||
formAction bean:
|
||||
<programlisting>
|
||||
<start-actions>
|
||||
<!-- create the backing form object and initialize a empty errors collection -->
|
||||
<action bean="formAction" method="setupForm"/>
|
||||
</start-actions>
|
||||
</programlisting>
|
||||
A start action is invoked before entering the start state.
|
||||
The setupForm method prepares a form object
|
||||
and registers custom property editors.
|
||||
The form backing object class (as declared in sellitem-beans.xml)
|
||||
is of type Sale and is saved in flow scope under
|
||||
the name of "sale" - it is possible to
|
||||
override both the scope and the name of the form object in the
|
||||
FormAction bean's declaration.
|
||||
</para>
|
||||
<para>
|
||||
The start state enterPriceAndItemCount is a view state, which resolves
|
||||
to the JSP page /WEB-INF/jsp/priceAndItemCountForm.jsp:
|
||||
<programlisting>
|
||||
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
|
||||
<transition on="submit" to="enterCategory">
|
||||
<action bean="formAction" method="bindAndValidate">
|
||||
<attribute name="validatorMethod" value="validatePriceAndItemCount"/>
|
||||
</action>
|
||||
</transition>
|
||||
</view-state>
|
||||
</programlisting>
|
||||
The priceAndItemCountForm.jsp page collects a price and an itemCount using
|
||||
Spring 2.0 form input tags binding form fields to properties in the form
|
||||
backing object "sale". When pressed, the submit button "_eventId_submit"
|
||||
causes a web flow transition for an event with the id of "submit" to
|
||||
the view state "enterCategory". Prior to transitioning the formAction's
|
||||
bindAndValidate method is called to perform binding and (partial) validation
|
||||
using the validatePriceAndItemCount method of the validator object.
|
||||
</para>
|
||||
<para>
|
||||
The next view state enterCategory (based on categoryForm.jsp)
|
||||
collects inputs for sale category and whether shipping is required. On
|
||||
submit it transitions to the requiresShipping state:
|
||||
<programlisting>
|
||||
<view-state id="enterCategory" view="categoryForm">
|
||||
<transition on="submit" to="requiresShipping">
|
||||
<action bean="formAction" method="bind"/>
|
||||
</transition>
|
||||
</view-state>
|
||||
</programlisting>
|
||||
The requiresShipping state is a decision state making
|
||||
flow routing decisions. It evaluates a boolean expression against the
|
||||
executing flow and it decides where to transition to next.
|
||||
Here the shipping boolean property of the "sale" form backing
|
||||
object is checked to decide whether to go to the enterShippingDetails
|
||||
subflow state or proceed directly to processSale.
|
||||
<programlisting>
|
||||
<decision-state id="requiresShipping">
|
||||
<if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
|
||||
</decision-state>
|
||||
</programlisting>
|
||||
The enterShippingDetails subflow state is based on shipping-flow.xml
|
||||
located in the same directory. The form backing object "sale" is
|
||||
passed to it as an input attribute using an attribute mapper declaration:
|
||||
<programlisting>
|
||||
<subflow-state id="enterShippingDetails" flow="shipping-flow">
|
||||
<attribute-mapper>
|
||||
<input-mapper>
|
||||
<input-attribute name="sale"/>
|
||||
</input-mapper>
|
||||
</attribute-mapper>
|
||||
<transition on="finish" to="processSale"/>
|
||||
</subflow-state>
|
||||
</programlisting>
|
||||
The shipping-flow subflow is a simple flow with one view state. It
|
||||
collects the shipping details, binds the data and returns to its parent
|
||||
flow. The id of the subflow end state "finish" is returned to the
|
||||
parent subflow state causing a transition to the processSale action state.
|
||||
<programlisting>
|
||||
<action-state id="processSale">
|
||||
<bean-action bean="saleProcessor" method="process">
|
||||
<method-arguments>
|
||||
<argument expression="flowScope.sale"/>
|
||||
</method-arguments>
|
||||
</bean-action>
|
||||
<transition on="success" to="finish"/>
|
||||
</action-state>
|
||||
</programlisting>
|
||||
The saleProcessor bean, a POJO defined in services-config.xml
|
||||
is invoked using a "bean-action" declaration (as opposed to the "action"
|
||||
declation used to invoke a web flow Action such as FormAction).
|
||||
The saleProcessor (an instance of JdbcSaleProcessor) performs a database
|
||||
update using the values of the Sale object and upon
|
||||
successful completion transitions to the end view state:
|
||||
<programlisting>
|
||||
<end-state id="finish" view="costOverview">
|
||||
<entry-actions>
|
||||
<!-- force reinstall of property editors so costOverview can render formatted Sale values -->
|
||||
<action bean="formAction" method="setupForm"/>
|
||||
</entry-actions>
|
||||
</end-state>
|
||||
</programlisting>
|
||||
Then end state calls FormAction's setupForm method again.
|
||||
This does not re-create the "sale" form object (still in flow scope)
|
||||
but it does ensure any custom property editors are
|
||||
registered for use in rendering the JSP.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Sellitem-simple-flow Flow Definition</title>
|
||||
<para>
|
||||
A simpler version of the sellitem-flow is available in the sellitem-simple-flow.xml file.
|
||||
This version uses a view state to gather shipping details instead of using a subflow. You
|
||||
can launch the sellitem-simple-flow using the following URI:
|
||||
<programlisting>
|
||||
/swf-sellitem/pos.htm?_flowId=sellitem-simple-flow
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Sellitem-conversation-scope-flow Flow Definition</title>
|
||||
<para>
|
||||
This web flow is equivalent in functionality to the sellitem-flow definition
|
||||
described above. The main difference is that it uses "conversation"
|
||||
scope to store the form backing object declared in
|
||||
/WEB-INF/flows/converstation-scope/sellitem-beans.xml.
|
||||
<programlisting>
|
||||
<bean id="formAction" class="org.springframework.webflow.action.FormAction">
|
||||
<property name="formObjectClass" value="org.springframework.webflow.samples.sellitem.Sale"/>
|
||||
<property name="formObjectScope" value="CONVERSATION"/>
|
||||
<property name="formErrorsScope" value="CONVERSATION"/>
|
||||
</programlisting>
|
||||
Conversation scope retains attributes stored in it for the life
|
||||
of the flow execution and is shared by all flow sessions.
|
||||
For example when invoking the shipping details subflow the parent
|
||||
flow does not need to pass the "sale" form backing object because
|
||||
it is now stored in conversation scope and is accessible to both flows:
|
||||
<programlisting>
|
||||
<subflow-state id="enterShippingDetails" flow="shipping-conversation-scope-flow">
|
||||
<transition on="finish" to="processSale"/>
|
||||
</subflow-state>
|
||||
</programlisting>
|
||||
Also, when the "sale" object needs to be accessed it is done by referencing
|
||||
conversation cope:
|
||||
<programlisting>
|
||||
<decision-state id="requiresShipping">
|
||||
<if test="${conversationScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
|
||||
</decision-state>
|
||||
</programlisting>
|
||||
You can launch the sellitem-conversation-scope-flow using the following URI:
|
||||
<programlisting>
|
||||
/swf-sellitem/pos.htm?_flowId=sellitem-conversation-scope-flow
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user