494 lines
18 KiB
XML
494 lines
18 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<chapter id="defining-flows">
|
|
<title>Defining Flows</title>
|
|
<sect1 id="defining-flows-introduction">
|
|
<title>Introduction</title>
|
|
<para>
|
|
This chapter begins the Users Section.
|
|
It shows how to implement flows using the flow definition language.
|
|
By the end of this chapter you should have a good understanding of language constructs, and be capable of authoring a flow definition.
|
|
</para>
|
|
</sect1>
|
|
<sect1 id="flow-overview">
|
|
<title>What is a flow?</title>
|
|
<para>
|
|
A flow encapsulates a reusable sequence of steps that can execute in different contexts.
|
|
Below is a <ulink url="http://www.jjg.net/ia/visvocab/">Garrett Information Architecture</ulink> diagram illustrating a reference to a flow that encapsulates the steps of a hotel booking process:
|
|
</para>
|
|
<mediaobject>
|
|
<imageobject role="fo">
|
|
<imagedata fileref="images/hotels-site.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<imageobject role="html">
|
|
<imagedata fileref="images/hotels-site.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<caption>
|
|
<para>Site Map illustrating a reference to a flow</para>
|
|
</caption>
|
|
</mediaobject>
|
|
</sect1>
|
|
<sect1 id="flow-makeup">
|
|
<title>What is the makeup of a typical flow?</title>
|
|
<para>
|
|
In Spring Web Flow, a flow consists of a series of steps called "states".
|
|
Entering a state typically results in a view being displayed to the user.
|
|
On that view, user events occur that are handled by the state.
|
|
These events can trigger transitions to other states which result in view navigations.
|
|
</para>
|
|
<para>
|
|
The example below shows the structure of the book hotel flow referenced in the previous diagram:
|
|
</para>
|
|
<mediaobject>
|
|
<imageobject role="fo">
|
|
<imagedata fileref="images/hotels-site-bookhotel-flow.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<imageobject role="html">
|
|
<imagedata fileref="images/hotels-site-bookhotel-flow.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<caption>
|
|
<para>Flow diagram</para>
|
|
</caption>
|
|
</mediaobject>
|
|
</sect1>
|
|
<sect1 id="flow-authoring">
|
|
<title>How are flows authored?</title>
|
|
<para>
|
|
Flows are authored by web application developers using a simple XML-based flow definition language.
|
|
The next steps of this guide will walk you through the elements of this language.
|
|
</para>
|
|
</sect1>
|
|
<sect1 id="essential-flow-elements">
|
|
<title>Essential language elements</title>
|
|
<sect2 id="flow-element">
|
|
<title>flow</title>
|
|
<para>
|
|
Every flow begins with the following root element:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<?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">
|
|
|
|
</flow>]]>
|
|
</programlisting>
|
|
<para>
|
|
All states of the flow are defined within this element.
|
|
The first state defined becomes the flow's starting point.
|
|
</para>
|
|
</sect2>
|
|
<sect2 id="view-state-element">
|
|
<title>view-state</title>
|
|
<para>
|
|
Use the <code>view-state</code> element to define a step of the flow that renders a view:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" />]]>
|
|
</programlisting>
|
|
<para>
|
|
By convention, a view-state maps its id to a view template in the directory where the flow is located.
|
|
For example, the state above might render <filename>/WEB-INF/hotels/booking/enterBookingDetails.xhtml</filename>
|
|
if the flow itself was located in the <filename>/WEB-INF/hotels/booking</filename> directory.
|
|
</para>
|
|
</sect2>
|
|
<sect2 id="transition-element">
|
|
<title>transition</title>
|
|
<para>
|
|
Use the <code>transition</code> element to handle events that occur within a state:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
These transitions drive view navigations.
|
|
</para>
|
|
</sect2>
|
|
<sect2 id="end-state-element">
|
|
<title>end-state</title>
|
|
<para>
|
|
Use the <code>end-state</code> element to define a flow outcome:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<end-state id="bookingCancelled" />]]>
|
|
</programlisting>
|
|
<para>
|
|
When a flow transitions to a end-state it terminates and the outcome is returned.
|
|
</para>
|
|
</sect2>
|
|
<sect2 id="checkpoint-essential-language-elements">
|
|
<title>Checkpoint: Essential language elements</title>
|
|
<para>
|
|
With the three elements <code>view-state</code>, <code>transition</code>, and <code>end-state</code>, you can quickly express your view navigation logic.
|
|
Teams often do this before adding flow behaviors so they can focus on developing the user interface of the application with end users first.
|
|
Below is a sample flow that implements its view navigation logic using these elements:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<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="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>
|
|
|
|
<view-state id="reviewBooking">
|
|
<transition on="confirm" to="bookingConfirmed" />
|
|
<transition on="revise" to="enterBookingDetails" />
|
|
<transition on="cancel" to="bookingCancelled" />
|
|
</view-state>
|
|
|
|
<end-state id="bookingConfirmed" />
|
|
|
|
<end-state id="bookingCancelled" />
|
|
|
|
</flow>]]>
|
|
</programlisting>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 id="flow-actions">
|
|
<title>Actions</title>
|
|
<para>
|
|
Most flows need to express more than just view navigation logic.
|
|
Typically they also need to invoke business services of the application or other actions.
|
|
</para>
|
|
<para>
|
|
Within a flow, there are several points where you can execute actions. These points are:
|
|
<itemizedlist>
|
|
<listitem><para>On flow start</para></listitem>
|
|
<listitem><para>On state entry</para></listitem>
|
|
<listitem><para>On view render</para></listitem>
|
|
<listitem><para>On transition execution</para></listitem>
|
|
<listitem><para>On state exit</para></listitem>
|
|
<listitem><para>On flow end</para></listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
<para>
|
|
Actions are defined using a concise expression language. Spring Web Flow uses the Unified EL by default.
|
|
The next few sections will cover the essential language elements for defining actions.
|
|
</para>
|
|
<sect2 id="evaluate-element">
|
|
<title>evaluate</title>
|
|
<para>
|
|
The action element you will use most often is the <code>evaluate</code> element.
|
|
Use the <code>evaluate</code> element to evaluate an expression at a point within your flow.
|
|
With this single tag you can invoke methods on Spring beans or any other flow variable.
|
|
For example:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<evaluate expression="entityManager.persist(booking)" />]]>
|
|
</programlisting>
|
|
<sect3 id="evaluate-element-result">
|
|
<title>Assigning an evaluate result</title>
|
|
<para>
|
|
If the expression returns a value, that value can be saved in the flow's data model called <code>flowScope</code>:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />]]>
|
|
</programlisting>
|
|
</sect3>
|
|
<sect3 id="evaluate-element-result-type">
|
|
<title>Converting an evaluate result</title>
|
|
<para>
|
|
If the expression returns a value that may need to be converted, specify the desired type using the <code>result-type</code> attribute:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels"
|
|
result-type="dataModel"/>]]>
|
|
</programlisting>
|
|
</sect3>
|
|
</sect2>
|
|
<sect2 id="checkpoint-actions">
|
|
<title>Checkpoint: flow actions</title>
|
|
<para>
|
|
Now review the sample booking flow with actions added:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<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">
|
|
|
|
<input name="hotelId" />
|
|
|
|
<on-start>
|
|
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
|
|
result="flowScope.booking" />
|
|
</on-start>
|
|
|
|
<view-state id="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>
|
|
|
|
<view-state id="reviewBooking">
|
|
<transition on="confirm" to="bookingConfirmed" />
|
|
<transition on="revise" to="enterBookingDetails" />
|
|
<transition on="cancel" to="bookingCancelled" />
|
|
</view-state>
|
|
|
|
<end-state id="bookingConfirmed" />
|
|
|
|
<end-state id="bookingCancelled" />
|
|
|
|
</flow>]]>
|
|
</programlisting>
|
|
<para>
|
|
This flow now creates a Booking object in flow scope when it starts.
|
|
The id of the hotel to book is obtained from a flow input attribute.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 id="flow-inputoutput">
|
|
<title>Input/Output Mapping</title>
|
|
<para>
|
|
Each flow has a well-defined input/output contract.
|
|
Flows can be passed input attributes when they start, and can return output attributes when they end.
|
|
In this respect, calling a flow is conceptually similar to calling a method with the following signature:
|
|
</para>
|
|
<programlisting language="java"><![CDATA[
|
|
FlowOutcome flowId(Map<String, Object> inputAttributes);]]>
|
|
</programlisting>
|
|
<para>
|
|
... where a <code>FlowOutcome</code> has the following signature:
|
|
</para>
|
|
<programlisting language="java"><![CDATA[
|
|
public interface FlowOutcome {
|
|
public String getName();
|
|
public Map<String, Object> getOutputAttributes();
|
|
}]]>
|
|
</programlisting>
|
|
<sect2 id="input-element">
|
|
<title>input</title>
|
|
<para>
|
|
Use the <code>input</code> element to declare a flow input attribute:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<input name="hotelId" />]]>
|
|
</programlisting>
|
|
<para>
|
|
Input values are saved in flow scope under the name of the attribute.
|
|
For example, the input above would be saved under the name <code>hotelId</code>.
|
|
</para>
|
|
<sect3 id="input-element-type">
|
|
<title>Declaring an input type</title>
|
|
<para>
|
|
Use the <code>type</code> attribute to declare the input attribute's type:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<input name="hotelId" type="long" />]]>
|
|
</programlisting>
|
|
<para>
|
|
If an input value does not match the declared type, a type conversion will be attempted.
|
|
</para>
|
|
</sect3>
|
|
<sect3 id="input-element-value">
|
|
<title>Assigning an input value</title>
|
|
<para>
|
|
Use the <code>value</code> attribute to specify an expression to assign the input value to:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<input name="hotelId" value="flowScope.myParameterObject.hotelId" />]]>
|
|
</programlisting>
|
|
<para>
|
|
If the expression's value type can be determined, that metadata will be used for type coersion if no <code>type</code> attribute is specified.
|
|
</para>
|
|
</sect3>
|
|
<sect3 id="input-element-required">
|
|
<title>Marking an input as required</title>
|
|
<para>
|
|
Use the <code>required</code> attribute to enforce the input is not null or empty:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />]]>
|
|
</programlisting>
|
|
</sect3>
|
|
</sect2>
|
|
<sect2 id="output-element">
|
|
<title>output</title>
|
|
<para>
|
|
Use the <code>output</code> element to declare a flow output attribute.
|
|
Output attributes are declared within end-states that represent specific flow outcomes.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<end-state id="bookingConfirmed">
|
|
<output name="bookingId" />
|
|
</end-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
Output values are obtained from flow scope under the name of the attribute.
|
|
For example, the output above would be assigned the value of the <code>bookingId</code> variable.
|
|
</para>
|
|
<sect3 id="output-element-value">
|
|
<title>Specifying the source of an output value</title>
|
|
<para>
|
|
Use the <code>value</code> attribute to denote a specific output value expression:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<output name="confirmationNumber" value="booking.confirmationNumber" />]]>
|
|
</programlisting>
|
|
</sect3>
|
|
</sect2>
|
|
<sect2 id="checkpoint-input-output">
|
|
<title>Checkpoint: input/output mapping</title>
|
|
<para>
|
|
Now review the sample booking flow with input/output mapping:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<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">
|
|
|
|
<input name="hotelId" />
|
|
|
|
<on-start>
|
|
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
|
|
result="flowScope.booking" />
|
|
</on-start>
|
|
|
|
<view-state id="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>
|
|
|
|
<view-state id="reviewBooking">
|
|
<transition on="confirm" to="bookingConfirmed" />
|
|
<transition on="revise" to="enterBookingDetails" />
|
|
<transition on="cancel" to="bookingCancelled" />
|
|
</view-state>
|
|
|
|
<end-state id="bookingConfirmed" >
|
|
<output name="bookingId" value="booking.id"/>
|
|
</end-state>
|
|
|
|
<end-state id="bookingCancelled" />
|
|
|
|
</flow>]]>
|
|
</programlisting>
|
|
<para>
|
|
The flow now accepts a <code>hotelId</code> input attribute and returns a <code>bookingId</code> output attribute
|
|
when a new booking is confirmed.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 id="flow-variables">
|
|
<title>Variables</title>
|
|
<para>
|
|
A flow may declare one or more instance variables.
|
|
These variables are allocated when the flow starts.
|
|
Any <code>@Autowired</code> transient references the variable holds are also rewired when the flow resumes.
|
|
</para>
|
|
<sect2 id="var-element">
|
|
<title>var</title>
|
|
<para>
|
|
Use the <code>var</code> element to declare a flow variable:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>]]>
|
|
</programlisting>
|
|
<para>
|
|
Make sure your variable's class implements <code>java.io.Serializable</code>, as the instance state is saved between flow requests.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 id="calling-subflows">
|
|
<title>Calling subflows</title>
|
|
<para>
|
|
A flow may call another flow as a subflow. The flow will wait until the subflow returns, then respond to the subflow outcome.
|
|
</para>
|
|
<sect2 id="subflow-state-element">
|
|
<title>subflow-state</title>
|
|
<para>
|
|
Use the <code>subflow-state</code> element to call another flow as a subflow:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<subflow-state id="addGuest" subflow="createGuest">
|
|
<transition on="guestCreated" to="reviewBooking">
|
|
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
|
|
</transition>
|
|
<transition on="creationCancelled" to="reviewBooking" />
|
|
</subfow-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
The above example calls the <code>createGuest</code> flow, then waits for it to return.
|
|
When the flow returns with a <code>guestCreated</code> outcome, the new guest is added to the booking's guest list.
|
|
</para>
|
|
<sect3 id="subflow-state-element-input">
|
|
<title>Passing a subflow input</title>
|
|
<para>
|
|
Use the <code>input</code> element to pass input to the subflow:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<subflow-state id="addGuest" subflow="createGuest">
|
|
<input name="booking" />
|
|
<transition to="reviewBooking" />
|
|
</subfow-state>]]>
|
|
</programlisting>
|
|
</sect3>
|
|
<sect3 id="subflow-state-element-output">
|
|
<title>Mapping subflow output</title>
|
|
<para>
|
|
Simply refer to a subflow output attribute by its name within a outcome transition:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="guestCreated" to="reviewBooking">
|
|
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
|
|
</transition>]]>
|
|
</programlisting>
|
|
<para>
|
|
In the above example, <code>guest</code> is the name of an output attribute returned by the <code>guestCreated</code> outcome.
|
|
</para>
|
|
</sect3>
|
|
</sect2>
|
|
<sect2 id="checkpoint-subflow">
|
|
<title>Checkpoint: calling subflows</title>
|
|
<para>
|
|
Now review the sample booking flow calling a subflow:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<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">
|
|
|
|
<input name="hotelId" />
|
|
|
|
<on-start>
|
|
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
|
|
result="flowScope.booking" />
|
|
</on-start>
|
|
|
|
<view-state id="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>
|
|
|
|
<view-state id="reviewBooking">
|
|
<transition on="addGuest" to="addGuest" />
|
|
<transition on="confirm" to="bookingConfirmed" />
|
|
<transition on="revise" to="enterBookingDetails" />
|
|
<transition on="cancel" to="bookingCancelled" />
|
|
</view-state>
|
|
|
|
<subflow-state id="addGuest" subflow="createGuest">
|
|
<transition on="guestCreated" to="reviewBooking">
|
|
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
|
|
</transition>
|
|
<transition on="creationCancelled" to="reviewBooking" />
|
|
</subfow-state>
|
|
|
|
<end-state id="bookingConfirmed" >
|
|
<output name="bookingId" value="booking.id" />
|
|
</end-state>
|
|
|
|
<end-state id="bookingCancelled" />
|
|
|
|
</flow>]]>
|
|
</programlisting>
|
|
<para>
|
|
The flow now calls a <code>createGuest</code> subflow to add a new guest to the guest list.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter> |