Files
spring-webflow/spring-webflow-reference/src/actions.xml
2008-11-04 13:02:54 +00:00

347 lines
13 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<chapter id="actions">
<title>Executing actions</title>
<sect1 id="actions-introduction">
<title>Introduction</title>
<para>
This chapter shows you how to use the <code>action-state</code> element to control the execution of an action at a point within a flow.
It will also show how to use the <code>decision-state</code> element to make a flow routing decision.
Finally, several examples of invoking actions from the various points possible within a flow will be discussed.
</para>
</sect1>
<sect1 id="action-state">
<title>Defining action states</title>
<para>
Use the <code>action-state</code> element when you wish to invoke an action, then transition to another state based on the action's outcome:
</para>
<programlisting language="xml"><![CDATA[
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>]]>
</programlisting>
<para>
The full example below illustrates a interview flow that uses the action-state above to determine if more answers are needed to complete the interview:
</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">
<on-start>
<evaluate expression="interviewFactory.createInterview()" result="flowScope.interview" />
</on-start>
<view-state id="answerQuestions" model="questionSet">
<on-entry>
<evaluate expression="interview.getNextQuestionSet()" result="viewScope.questionSet" />
</on-entry>
<transition on="submitAnswers" to="moreAnswersNeeded">
<evaluate expression="interview.recordAnswers(questionSet)" />
</transition>
</view-state>
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
<end-state id="finish" />
</flow>]]>
</programlisting>
</sect1>
<sect1 id="decision-state">
<title>Defining decision states</title>
<para>
Use the <code>decision-state</code> element as an alternative to the action-state to make a routing decision using a convenient if/else syntax.
The example below shows the <code>moreAnswersNeeded</code> state above now implemented as a decision state instead of an action-state:
</para>
<programlisting language="xml"><![CDATA[
<decision-state id="moreAnswersNeeded">
<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>]]>
</programlisting>
</sect1>
<sect1 id="action-outcome-events">
<title>Action outcome event mappings</title>
<para>
Actions often invoke methods on plain Java objects.
When called from action-states and decision-states, these method return values can be used to drive state transitions.
Since transitions are triggered by events, a method return value must first be mapped to an Event object.
The following table describes how common return value types are mapped to Event objects:
</para>
<table id="event-mapping-table">
<title>Action method return value to event id mappings</title>
<tgroup cols="2">
<colspec colname="Method return type" colwidth="*"/>
<colspec colname="Event identifier" colwidth="3*"/>
<thead>
<row>
<entry>Method return type</entry>
<entry>Mapped Event identifier expression</entry>
</row>
</thead>
<tbody>
<row>
<entry>java.lang.String</entry>
<entry>the String value</entry>
</row>
<row>
<entry>java.lang.Boolean</entry>
<entry>yes (for true), no (for false)</entry>
</row>
<row>
<entry>java.lang.Enum</entry>
<entry>the Enum name</entry>
</row>
<row>
<entry>any other type</entry>
<entry>success</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
This is illustrated in the example action state below, which invokes a method that returns a boolean value:
</para>
<programlisting language="xml"><![CDATA[
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>]]>
</programlisting>
</sect1>
<sect1 id="action-implementations">
<title>Action implementations</title>
<para>
While writing action code as POJO logic is the most common, there are several other action implementation options.
Sometimes you need to write action code that needs access to the flow context.
You can always invoke a POJO and pass it the flowRequestContext as an EL variable.
Alternatively, you may implement the <code>Action</code> interface or extend from the <code>MultiAction</code> base class.
These options provide stronger type safety when you have a natural coupling between your action code and Spring Web Flow APIs.
Examples of each of these approaches are shown below.
</para>
<sect2>
<title>Invoking a POJO action</title>
<programlisting language="xml"><![CDATA[
<evaluate expression="pojoAction.method(flowRequestContext)" />]]>
</programlisting>
<programlisting language="java"><![CDATA[
public class PojoAction {
public String method(RequestContext context) {
...
}
}]]>
</programlisting>
</sect2>
<sect2>
<title>Invoking a custom Action implementation</title>
<programlisting language="xml"><![CDATA[
<evaluate expression="customAction" />]]>
</programlisting>
<programlisting language="java"><![CDATA[
public class CustomAction implements Action {
public Event execute(RequestContext context) {
...
}
}]]>
</programlisting>
</sect2>
<sect2>
<title>Invoking a MultiAction implementation</title>
<programlisting language="xml"><![CDATA[
<evaluate expression="multiAction.actionMethod1" />
]]>
</programlisting>
<programlisting language="java"><![CDATA[
public class CustomMultiAction extends MultiAction {
public Event actionMethod1(RequestContext context) {
...
}
public Event actionMethod2(RequestContext context) {
...
}
...
}]]>
</programlisting>
</sect2>
</sect1>
<sect1 id="action-exceptions">
<title>Action exceptions</title>
<para>
Actions often invoke services that encapsulate complex business logic.
These services may throw business exceptions that the action code should handle.
</para>
<sect2>
<title>Handling a business exception with a POJO action</title>
<para>
The following example invokes an action that catches a business exception, adds a error message to the context, and returns a result event identifier.
The result is treated as a flow event which the calling flow can then respond to.
</para>
<programlisting language="xml"><![CDATA[
<evaluate expression="bookingAction.makeBooking(booking, messageContext)" />]]>
</programlisting>
<programlisting language="java"><![CDATA[
public class BookingAction {
public String makeBooking(Booking booking, MessageContext context) {
try {
bookingService.make(booking);
return "success";
} catch (RoomNotAvailableException e) {
context.addMessage(builder.error().
.defaultText("No room is available at this hotel").build());
return "error";
}
}
}]]>
</programlisting>
</sect2>
</sect1>
<sect1 id="action-examples">
<title>Other Action execution examples</title>
<sect2 id="action-on-start">
<title>on-start</title>
<para>
The following example shows an action that creates a new Booking object by invoking a method on a service:
</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>
</flow>]]>
</programlisting>
</sect2>
<sect2 id="action-on-state-entry">
<title>on-entry</title>
<para>
The following example shows a state entry action that sets the special <code>fragments</code> variable that causes the view-state to render a partial fragment of its view:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchForm" />
</on-entry>
</view-state>]]>
</programlisting>
</sect2>
<sect2 id="action-on-state-exit">
<title>on-exit</title>
<para>
The following example shows a state exit action that releases a lock on a record being edited:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="editOrder">
<on-entry>
<evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
result="viewScope.order" />
</on-entry>
<transition on="save" to="finish">
<evaluate expression="orderService.update(order, currentUser)" />
</transition>
<on-exit>
<evaluate expression="orderService.releaseLock(order, currentUser)" />
</on-exit>
</view-state>]]>
</programlisting>
</sect2>
<sect2 id="on-end">
<title>on-end</title>
<para>
The following example shows the equivalent object locking behavior using flow start and end actions:
</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="orderId" />
<on-start>
<evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
result="flowScope.order" />
</on-start>
<view-state id="editOrder">
<transition on="save" to="finish">
<evaluate expression="orderService.update(order, currentUser)" />
</transition>
</view-state>
<on-end>
<evaluate expression="orderService.releaseLock(order, currentUser)" />
</on-end>
</flow>]]>
</programlisting>
</sect2>
<sect2 id="action-on-render">
<title>on-render</title>
<para>
The following example shows a render action that loads a list of hotels to display before the view is rendered:
</para>
<programlisting language="xml"><![CDATA[
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="select" to="reviewHotel">
<set name="flowScope.hotel" value="hotels.selectedRow" />
</transition>
</view-state>]]>
</programlisting>
</sect2>
<sect2 id="action-on-transition">
<title>on-transition</title>
<para>
The following example shows a transition action adds a subflow outcome event attribute to a collection:
</para>
<programlisting language="xml"><![CDATA[
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" />
</transition>
</subfow-state>]]>
</programlisting>
</sect2>
<sect2 id="named actions">
<title>Named actions</title>
<para>
The following example shows how to execute a chain of actions in an action-state.
The name of each action becomes a qualifier for the action's result event.
</para>
<programlisting language="xml"><![CDATA[
<action-state id="doTwoThings">
<evaluate expression="service.thingOne()">
<attribute name="name" value="thingOne" />
</evaluate>
<evaluate expression="service.thingTwo()">
<attribute name="name" value="thingTwo" />
</evaluate>
<transition on="thingTwo.success" to="showResults" />
</action-state>]]>
</programlisting>
<para>
In this example, the flow will transition to <code>showResults</code> when <code>thingTwo</code>
completes successfully.
</para>
</sect2>
</sect1>
</chapter>