In this chapter you'll learn how to execute flows within Spring MVC, Struts, and Java Server Faces (JSF) based applications.
org.springframework.webflow.executor.FlowExecutor is the central facade interface external systems use to drive the execution of flows. This facade acts as a simple, convenient service entry-point into the Spring Web Flow system that is reusable across environments.
The FlowExecutor interface is shown below:
public interface FlowExecutor { ResponseInstruction launch(String flowDefinitionId, ExternalContext context); ResponseInstruction resume(String flowExecutionKey, String eventId, ExternalContext context); ResponseInstruction refresh(String flowExecutionKey, ExternalContext context); }
As you can see there are three central use-cases fulfilled by this interface:
Launch (start) a new execution of a flow definition.
Resume a paused flow execution by signaling an event against its current state.
Request that the last response issued by a flow execution be re-issued. Unlike start and signalEvent, the refresh operation is an idempotent operation that does not affect the state of a flow execution.
Each operation accepts an ExternalContext that provides normalized access to properties of an external system that has called into Spring Web Flow, allowing access to environment-specific request parameters as well as request, session, and application-level attributes.
Each operation returns a ResponseInstruction which the calling system is expected to use to issue a suitable response.
These relationships are shown graphically below:
As you can see, an ExternalContext implementation exists for each of the environments Spring Web Flow supports. If a flow artifact such as an Action needs to access native constructs of the calling environment it can downcast a context to its specific implementation. The need for such downcasting is considered a special case.
The default executor implementation is org.springframework.webflow.executor.FlowExecutorImpl. It allows for configuration of a FlowDefinitionLocator responsible for loading the flow definitions to execute, as well as the FlowExecutionRepository strategy responsible for persisting flow executions that remain active beyond a single request into the server.
The configurable FlowExecutorImpl properties are shown below:
Table 5.1. FlowExecutorImpl properties
Property name | Description | Cardinality |
---|---|---|
definitionLocator | The service for loading flow definitions to be executed, typically a FlowDefinitionRegistry | 1 |
executionFactory | The factory for creating new flow executions. | 1 |
executionRepository | The repository for saving and loading persistent (paused) flow executions | 1 |
<?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"> <!-- 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> </beans>
This instructs Spring to create a flow executor that can execute all XML-based flow definitions contained within the /WEB-INF/flows directory. The default flow execution repository, continuation, is used.
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="simple"/>
This executor is configured with a simple repository that manages execution state in the user session.
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="client"/>
This executor is configured with a continuation-based repository that serializes continuation state to the client using no server-side state.
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="singleKey"/>
This executor is configured with a simple repository that assigns a single flow execution key per conversation. The key, once assigned, never changes for the duration of the conversation.
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="continuation"> <flow:execution-attributes> <flow:alwaysRedirectOnPause value="false"/> <flow:attribute name="foo" value="bar"/> </flow:execution-attributes> </flow-executor>
This executor is configured to set two flow execution system attributes alwaysRedirectOnPause=false and foo=bar.
Note | |
---|---|
The alwaysRedirectOnPause attribute determines if a flow execution redirect occurs automatically each time an execution pauses (automated POST+REDIRECT+GET behavior). Setting this attribute to false will disable the default 'true' behavior where a flow execution redirect always occurs on pause. |
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="continuation"> <flow:execution-listeners> <flow:listener ref="listener" criteria="order-flow"/> </flow:execution-listeners> </flow-executor> <!-- A FlowExecutionListener to observe the lifecycle of order-flow executions --> <bean id="listener" class="org.springframework.webflow.samples.sellitem.SellItemFlowExecutionListener"/>
This executor is configured to apply the execution listener to the "order-flow".
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Launches new flow executions and resumes existing executions: Spring 1.2 config version --> <bean id="flowExecutor" class="org.springframework.webflow.config.FlowExecutorFactoryBean"> <property name="definitionLocator" ref="flowRegistry"/> <property name="executionAttributes"> <map> <entry key="alwaysRedirectOnPause"> <value type="java.lang.Boolean">false</value> </entry> </map> </property> <property name="repositoryType" value="CONTINUATION"/> </bean> <!-- Creates the registry of flow definitions for this application: Spring 1.2 config version --> <bean id="flowRegistry" class="org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean"> <property name="flowLocations"> <list> <value>/WEB-INF/flows/**/*-flow.xml</value> </list> </property> </bean> </beans>
This achieves similar semantics as the Spring 2.0 version above. The 2.0 version is more concise, provides stronger validation, and encapsulates internal details such as FactoryBean class names. The 1.2 version is Spring 1.2 or > compatible and digestable by Spring IDE 1.3.
Spring Web Flow integrates with both Servlet and Portlet MVC which ship with the core Spring Framework. Use of Portlet MVC requires Spring 2.0.
For both Servlet and Portlet MVC a FlowController acts as an adapter between Spring MVC and Spring Web Flow. As an adapter, this controller has knowledge of both systems and delegates to a flow executor for driving the execution of flows. One controller typically executes all flows of an application, relying on parameterization to determine what flow to launch or what flow execution to resume.
<bean name="/flowController.htm" class="org.springframework.webflow.executor.mvc.FlowController"> <property name="flowExecutor" ref="flowExecutor"/> </bean>
This controller, exported at the context-relative /flowController.htm URL, delegates to the configured flow executor for driving flow executions in a Spring Servlet MVC environment.
<bean id="portletModeControllerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping"> <property name="portletModeMap"> <map> <entry key="view" value-ref="flowController"/> </map> </property> </bean> <bean id="flowController" class="org.springframework.webflow.executor.mvc.PortletFlowController"> <property name="flowExecutor" ref="flowExecutor"/> <property name="defaultFlowId" ref="search-flow"/> </bean>
This controller, exported for access with the configured portlet mode, delegates to the configured flow executor for driving flow executions in a Spring Portlet MVC environment (by default, an execution of the search-flow will be launched).
Spring Web Flow allows for full control over how flow executor method arguments such as the flowDefinitionId, flowExecutionKey, and eventId are extracted from an incoming controller request with the org.springframework.webflow.executor.support.FlowExecutorArgumentExtractor strategy.
Note | |
---|---|
The various flow controllers typically do not use this strategy directly but instead use a convenient FlowExecutorArgumentHandler implementation that takes care of argument extraction as well as exposing responsibilities (in callback URLs). |
The next several examples illustrate strategies for parameterizing flow controllers from the browser to launch and resume flow executions:
The default executor argument extractor strategy is request-parameter based. The default request parameters are:
Table 5.2. Extractor request parameter names
Parameter name | Description |
---|---|
_flowId | The flow definition id, needed to launch a new flow execution. |
_flowExecutionKey | The flow execution key, needed to resume and refresh an existing flow execution. |
_eventId | The id of an event that occured, needed to resume an existing flow execution. |
<a href="flowController.htm?_flowId=myflow">Launch My Flow</a>
<form action="flowController.htm" method="post"> <input type="submit" value="Launch My Flow"/> <input type="hidden" name="_flowId" value="myflow"> </form>
<a href="flowController.htm?_flowExecutionKey=${flowExecutionKey}&_eventId=submit"> Submit </a>
<form action="flowController.htm" method="post"> ... <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"> <input type="hidden" name="_eventId" value="submit"/> <input type="submit" class="button" value="Submit"> </form>
<form action="flowController.htm" method="post"> ... <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"> <input type="submit" class="button" name="_eventId_submit" value="Submit"> <input type="submit" class="button" name="_eventId_cancel" value="Cancel"> </form>
Note | |
---|---|
In this case the eventId is determined by parsing the name of the button that was pressed. |
The request-path based argument extractor strategy relies on executor arguments being path elements as much as possible. This results in friendlier REST-style URLs such as http://host/app/myflow instead of http://host/app?_flowId=myflow.
<bean name="/flowController.htm" class="org.springframework.webflow.executor.mvc.FlowController"> <property name="flowExecutor" ref="flowExecutor"/> <property name="argumentHandler"> <bean class="org.springframework.webflow.executor.support.RequestPathFlowExecutorArgumentHandler"/> </property> </bean>
<a href="flowController/myflow"/>Launch My Flow</a>
<form action="${flowExecutionKey}" method="post"> ... <input type="submit" class="button" name="_eventId_submit" value="Submit"> <input type="submit" class="button" name="_eventId_cancel" value="Cancel"> </form>
Spring Web Flow integrates with Struts 1.x or >. The integration is very similiar to Spring MVC where a single front controller (FlowAction) drives the execution of all flows for the application by delegating to a configured flow executor.
<form-beans> <form-bean name="actionForm" type="org.springframework.web.struts.SpringBindingActionForm"/> </form-beans> <action-mappings> <action path="/flowAction" name="actionForm" scope="request" type="org.springframework.webflow.executor.struts.FlowAction"/> </action-mappings>
Spring Web Flow integrates with JSF. The JSF integration relies on custom implementations of core JSF artifacts such as navigation handler and phase listener to drive the execution of flows.
<faces-config> <application> <navigation-handler> org.springframework.webflow.executor.jsf.FlowNavigationHandler </navigation-handler> <property-resolver> org.springframework.webflow.executor.jsf.FlowPropertyResolver </property-resolver> <variable-resolver> org.springframework.webflow.executor.jsf.FlowVariableResolver </variable-resolver> <variable-resolver> org.springframework.web.jsf.DelegatingVariableResolver </variable-resolver> <variable-resolver> org.springframework.web.jsf.WebApplicationContextVariableResolver </variable-resolver> </application> <lifecycle> <phase-listener>org.springframework.webflow.executor.jsf.FlowPhaseListener</phase-listener> </lifecycle> </faces-config>