actions.md 15.1 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1 2
# 6. Executing actions

茶陵後's avatar
茶陵後 已提交
3
## 6.1. Introduction
茶陵後's avatar
茶陵後 已提交
4 5 6 7 8

This chapter shows you how to use the `action-state` element to control the execution of an action at a point within a flow.
It will also show how to use the `decision-state` element to make a flow routing decision.
Finally, several examples of invoking actions from the various points possible within a flow will be discussed.

茶陵後's avatar
茶陵後 已提交
9
## 6.2. Defining action states
茶陵後's avatar
茶陵後 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

Use the `action-state` element when you wish to invoke an action, then transition to another state based on the action's outcome:

```
<action-state id="moreAnswersNeeded">
	<evaluate expression="interview.moreAnswersNeeded()" />
	<transition on="yes" to="answerQuestions" />
	<transition on="no" to="finish" />
</action-state>
		
```

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:

```
<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.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>
			
```

After the execution of each action, the action-state checks the result to see if matches a declared
transition to another state. That means if more than one action is configured they are executed in
an ordered chain until one returns a result event that matches a state transition out of the
action-state while the rest are ignored. This is a form of the Chain of Responsibility (CoR) pattern.

The result of an action's execution is typically the criteria for a transition out of this state.
Additional information in the current RequestContext may also be tested as part of custom
transitional criteria allowing for sophisticated transition expressions that reason on contextual
state.

Note also that an action-state just like any other state can have one more on-entry actions
that are executed as a list from start to end.

茶陵後's avatar
茶陵後 已提交
68
## 6.3. Defining decision states
茶陵後's avatar
茶陵後 已提交
69 70 71 72 73 74 75 76 77 78 79

Use the `decision-state` element as an alternative to the action-state to make a routing decision using a convenient if/else syntax.
The example below shows the `moreAnswersNeeded` state above now implemented as a decision state instead of an action-state:

```
<decision-state id="moreAnswersNeeded">
	<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
			
```

茶陵後's avatar
茶陵後 已提交
80
## 6.4. Action outcome event mappings
茶陵後's avatar
茶陵後 已提交
81 82 83 84 85 86

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:

茶陵後's avatar
茶陵後 已提交
87

茶陵後's avatar
茶陵後 已提交
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

**Table 6.1. Action method return value to event id mappings**

|Method return type|Mapped Event identifier expression|
|------------------|----------------------------------|
| java.lang.String |         the String value         |
|java.lang.Boolean |  yes (for true), no (for false)  |
|  java.lang.Enum  |          the Enum name           |
|  any other type  |             success              |

This is illustrated in the example action state below, which invokes a method that returns a boolean value:

```
<action-state id="moreAnswersNeeded">
	<evaluate expression="interview.moreAnswersNeeded()" />
	<transition on="yes" to="answerQuestions" />
	<transition on="no" to="finish" />
</action-state>
		
```

茶陵後's avatar
茶陵後 已提交
109
## 6.5. Action implementations
茶陵後's avatar
茶陵後 已提交
110 111 112 113 114 115 116 117

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 `Action` interface or extend from the `MultiAction` 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.

茶陵後's avatar
茶陵後 已提交
118
### 6.5.1. Invoking a POJO action
茶陵後's avatar
茶陵後 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

```
<evaluate expression="pojoAction.method(flowRequestContext)" />
			
```

```
public class PojoAction {
	public String method(RequestContext context) {
		...
	}
}
			
```

茶陵後's avatar
茶陵後 已提交
134
### 6.5.2. Invoking a custom Action implementation
茶陵後's avatar
茶陵後 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

```
<evaluate expression="customAction" />
			
```

```
public class CustomAction implements Action {
	public Event execute(RequestContext context) {
		...
	}
}
			
```

茶陵後's avatar
茶陵後 已提交
150
### 6.5.3. Invoking a MultiAction implementation
茶陵後's avatar
茶陵後 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

```
<evaluate expression="multiAction.actionMethod1" />

			
```

```
public class CustomMultiAction extends MultiAction {
	public Event actionMethod1(RequestContext context) {
		...
	}

	public Event actionMethod2(RequestContext context) {
		...
	}

	...
}
			
```

茶陵後's avatar
茶陵後 已提交
173
## 6.6. Action exceptions
茶陵後's avatar
茶陵後 已提交
174 175 176 177

Actions often invoke services that encapsulate complex business logic.
These services may throw business exceptions that the action code should handle.

茶陵後's avatar
茶陵後 已提交
178
### 6.6.1. Handling a business exception with a POJO action
茶陵後's avatar
茶陵後 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

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.

```
<evaluate expression="bookingAction.makeBooking(booking, flowRequestContext)" />
			
```

```
public class BookingAction {
public String makeBooking(Booking booking, RequestContext context) {
	   try {
		   BookingConfirmation confirmation = bookingService.make(booking);
		   context.getFlowScope().put("confirmation", confirmation);
		   return "success";
	   } catch (RoomNotAvailableException e) {
		   context.addMessage(new MessageBuilder().error().
			   .defaultText("No room is available at this hotel").build());
		   return "error";
	   }
}
}
			
```

茶陵後's avatar
茶陵後 已提交
205
### 6.6.2. Handling a business exception with a MultiAction
茶陵後's avatar
茶陵後 已提交
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

The following example is functionally equivlant to the last, but implemented as a MultiAction instead of a POJO action.
The MultiAction requires its action methods to be of the signature `Event ${methodName}(RequestContext)`, providing stronger type safety, while a POJO action allows for more freedom.

```
<evaluate expression="bookingAction.makeBooking" />
			
```

```
public class BookingAction extends MultiAction {
public Event makeBooking(RequestContext context) {
	   try {
		   Booking booking = (Booking) context.getFlowScope().get("booking");
		   BookingConfirmation confirmation = bookingService.make(booking);
		   context.getFlowScope().put("confirmation", confirmation);
		   return success();
	   } catch (RoomNotAvailableException e) {
		   context.getMessageContext().addMessage(new MessageBuilder().error().
			   .defaultText("No room is available at this hotel").build());
		   return error();
	   }
}
}
			
```

茶陵後's avatar
茶陵後 已提交
233
### 6.6.3. Using an exception-handler element
茶陵後's avatar
茶陵後 已提交
234 235 236 237 238 239

In general it is recommended to catch exceptions in actions and return result
events that drive standard transitions, it is also possible to add an`exception-handler` sub-element to any state type with a`bean` attribute referencing a bean of type`FlowExecutionExceptionHandler`. This is an advanced
option that if used incorrectly can leave the flow execution in an invalid state.
Consider the build-in `TransitionExecutingFlowExecutionExceptionHandler`as example of a correct implementation.

茶陵後's avatar
茶陵後 已提交
240
## 6.7. Other Action execution examples
茶陵後's avatar
茶陵後 已提交
241

茶陵後's avatar
茶陵後 已提交
242
### 6.7.1. on-start
茶陵後's avatar
茶陵後 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262

The following example shows an action that creates a new Booking object by invoking a method on a service:

```
<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.xsd">

	<input name="hotelId" />

	<on-start>
		<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
				  result="flowScope.booking" />
	</on-start>

</flow>
			
```

茶陵後's avatar
茶陵後 已提交
263
### 6.7.2. on-entry
茶陵後's avatar
茶陵後 已提交
264 265 266 267 268 269 270 271 272 273 274 275

The following example shows a state entry action that sets the special `fragments` variable that causes the view-state to render a partial fragment of its view:

```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
	<on-entry>
		<render fragments="hotelSearchForm" />
	</on-entry>
</view-state>
			
```

茶陵後's avatar
茶陵後 已提交
276
### 6.7.3. on-exit
茶陵後's avatar
茶陵後 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

The following example shows a state exit action that releases a lock on a record being edited:

```
<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>
			
```

茶陵後's avatar
茶陵後 已提交
296
### 6.7.4. on-end
茶陵後's avatar
茶陵後 已提交
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

The following example shows the equivalent object locking behavior using flow start and end actions:

```
<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.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>
			
```

茶陵後's avatar
茶陵後 已提交
327
### 6.7.5. on-render
茶陵後's avatar
茶陵後 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

The following example shows a render action that loads a list of hotels to display before the view is rendered:

```
<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>
			
```

茶陵後's avatar
茶陵後 已提交
344
### 6.7.6. on-transition
茶陵後's avatar
茶陵後 已提交
345 346 347 348 349 350 351 352 353 354 355 356

The following example shows a transition action adds a subflow outcome event attribute to a collection:

```
<subflow-state id="addGuest" subflow="createGuest">
	<transition on="guestCreated" to="reviewBooking">
		<evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" />
	</transition>
</subfow-state>
			
```

茶陵後's avatar
茶陵後 已提交
357
### 6.7.7. Named actions
茶陵後's avatar
茶陵後 已提交
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

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.

```
<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>
			
```

In this example, the flow will transition to `showResults` when `thingTwo`completes successfully.

茶陵後's avatar
茶陵後 已提交
377
### 6.7.8. Streaming actions
茶陵後's avatar
茶陵後 已提交
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

Sometimes an Action needs to stream a custom response back to the client.
An example might be a flow that renders a PDF document when handling a print event.
This can be achieved by having the action stream the content then record "Response Complete" status on the ExternalContext.
The responseComplete flag tells the pausing view-state not to render the response because another object has taken care of it.

```
<view-state id="reviewItinerary">
	<transition on="print">
		<evaluate expression="printBoardingPassAction" />
	</transition>
</view-state>
			
```

```
public class PrintBoardingPassAction extends AbstractAction {
	public Event doExecute(RequestContext context) {
		// stream PDF content here...
		// - Access HttpServletResponse by calling context.getExternalContext().getNativeResponse();
		// - Mark response complete by calling context.getExternalContext().recordResponseComplete();
		return success();
	}
}
			
```

In this example, when the print event is raised the flow will call the printBoardingPassAction.
The action will render the PDF then mark the response as complete.

茶陵後's avatar
茶陵後 已提交
408
### 6.7.9. Handling File Uploads
茶陵後's avatar
茶陵後 已提交
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465

Another common task is to use Web Flow to handle multipart file uploads in combination with Spring MVC's`MultipartResolver`. Once the resolver is set up correctly [as described here](http://static.springsource.org/spring/docs/2.5.x/reference/mvc.html#mvc-multipart) and the submitting
HTML form is configured with `enctype="multipart/form-data"`, you can easily handle the file upload in a
transition action.

|                                                                                                  ![[Note]](images/note.png)                                                                                                   |Note|
|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|The file upload example below below is not relevant when using Web Flow with JSF. See[Section 13.8, “Handling File Uploads with JSF”](spring-faces.html#spring-faces-file-upload) for details of how to upload files using JSF.|    |

Given a form such as:

```
<form:form modelAttribute="fileUploadHandler" enctype="multipart/form-data">
	Select file: <input type="file" name="file"/>
	<input type="submit" name="_eventId_upload" value="Upload" />
</form:form>
			
```

and a backing object for handling the upload such as:

```
package org.springframework.webflow.samples.booking;

import org.springframework.web.multipart.MultipartFile;

public class FileUploadHandler {

	private transient MultipartFile file;

	public void processFile() {
		//Do something with the MultipartFile here
	}

	public void setFile(MultipartFile file) {
		this.file = file;
	}
}
			
```

you can process the upload using a transition action as in the following example:

```
<view-state id="uploadFile" model="uploadFileHandler">
	<var name="fileUploadHandler" class="org.springframework.webflow.samples.booking.FileUploadHandler" />
	<transition on="upload" to="finish" >
		<evaluate expression="fileUploadHandler.processFile()"/>
	</transition>
	<transition on="cancel" to="finish" bind="false"/>
</view-state>
			
```

The `MultipartFile` will be bound to the `FileUploadHandler` bean as
part of the normal form binding process so that it will be available to process during the
execution of the transition action.