Enhancing Spring MVC applications with Web Flow
5.4 Using action classes
PREREQUISITES
Installing and configuring SWF KEY TECHNOLOGIES
SWF
Background
You’ve learned that it’s possible to call methods on POJOs to do work and drive transi-tions between states using the <set> or <evaluate> elements.
Problem
Although it’s certainly easier to call POJOs directly, there are times when you would like to do more. Perhaps you want to read or set several variables in different flow scopes or invoke complex business logic that might be supported by multiple services.
What happens when a business exception is thrown? How can you handle and map this exception to something meaningful to SWF?
Solution
Those who have experience with Spring MVC may immediately think about control-lers. In Spring MVC, controllers provide a layer of indirection between the service layer and the view. Controllers typically delegate to application or business services to retrieve necessary data or invoke business operations. After completing its work, the controller selects a view to be rendered.
In SWF, the flow is considered to be the controller. Action classes provide a mecha-nism for you to provide a similar layer of indirection between the service layer and
SWF. Instead of selecting a view to be ren-dered directly, when an action is executed, it returns an Event providing an outcome that the flow can respond to.
You can create an action class by imple-menting the org.springframework.web-flow.execution.Action interface. SWF provides several Action implementations out of the box. We’ll focus our attention on the three implementations of the Action interface shown in figure 5.8.
An action is essentially a command that performs some work and returns an event.
The Action interface contains a single method:
public Event execute(RequestContext context) throws Exception;
The RequestContext, available as the flowRequestContext EL variable, gives informa-tion about the current flow execuinforma-tion and provides access to each of the variable scopes.
Revisiting the Spring Soccer demo, you can create a simple action class that encapsulates the call to the playerService. The class in the following listing would be configured like any other Spring-managed bean, allowing you to inject any necessary resources.
// Source project: sip05, branch: 04 (Maven Project) package com.springinpractice.ch05.mvc;
import com.springinpractice.ch05.service.PlayerService;
import com.springinpractice.ch05.domain.*;
import com.springinpractice.ch05.domain.search.PlayerSearchCriteria;
import org.springframework.webflow.execution.*;
public class FindExistingPlayerAction implements Action { private PlayerService playerService;
public void setPlayerService(PlayerService playerService) { this.playerService = playerService;
}
@Override
public Event execute(RequestContext context) { Event event = null;
PlayerSearchCriteria criteria =
(PlayerSearchCriteria)context.getFlowScope().get("playerSearchCriteria");
if (criteria != null) {
Player player = playerService.findExistingPlayer(criteria);
context.getFlowScope().put("player",player);
event = new Event(this, "success");
}
Listing 5.12 FindExistingPlayerAction.java
Figure 5.8 Although several classes imple-ment the Action interface out of the box, we’ll focus on AbstractAction, MultiAction, and FormAction.
else {
event = new Event(this, "error");
}
return event;
} }
With the FindExistingPlayerAction class in hand, the following line in the existing demo application
<evaluate expression="playerService.findExistingPlayer(playerSearchCriteria)"
result="flowScope.player"/>
can now be replaced with the following line. When implementing the Action inter-face, it’s no longer necessary to explicitly pass in the flowRequestContext EL variable:
<evaluate expression="findExistingPlayerAction" />
Although you can implement the Action interface directly, it’s more convenient to take advantage of the convenience methods available in the AbstractAction class.
We’ll talk about this briefly next.
ABSTRACTACTION
SWF provides an AbstractAction class that provides a convenient base implementa-tion for all of your acimplementa-tion classes. Specifically, this class provides a default implemen-tation of the InitializingBean interface, which provides a hook for you to do custom initialization or validation after your bean’s properties have been set by the container. In addition, the AbstractAction class implements the Action interface’s execute() method and provides a doExecute()method for you to place the code you would normally place in the execute() method, and two additional template meth-ods, doPreExecute()and doPostExecute(), which you can override to do pre- and postprocessing. The class provides several factory methods that create common Events, such as success and error.
MULTIACTION
If you prefer to group all of your actions together, the MultiAction class builds on the functionality provided by the AbstractAction class and allows you to bundle two or more execution methods in the same class. As shown next, the signature of each method needs to be in the same format as the execute() method in the Action class.
The only difference is that the name can be anything you like:
public Event ${method}(RequestContext context) throws Exception;
An example of a MultiAction class can be seen in the following listing.
// Source project: sip05, branch: 04 (Maven Project) package com.springinpractice.ch05.mvc;
import org.springframework.webflow.action.*;
Listing 5.13 PlayerActions.java
import org.springframework.webflow.execution.*;
import com.springinpractice.ch05.service.PlayerService;
import com.springinpractice.ch05.domain.*;
import com.springinpractice.ch05.domain.search.PlayerSearchCriteria;
public class PlayerActions extends MultiAction { private PlayerService playerService;
public void setPlayerService(PlayerService playerService) { this.playerService = playerService;
}
public Event findExistingPlayer(RequestContext context) { PlayerSearchCriteria criteria =
(PlayerSearchCriteria)context.getFlowScope().
➥ get("playerSearchCriteria");
if (criteria != null) {
Player player = playerService.findExistingPlayer(criteria);
context.getFlowScope().put("player",player);
return success();
} else {
return error();
} }
public Event doSomethingElse(RequestContext context) {...}
}
Here you reimplement the setPlayerService from listing 5.12 in a MultiAction class. This action can be invoked in two different ways. By default, the ID of the wrap-ping action state is treated as the method to execute. Look at the following example:
<action-state id="findExistingPlayer">
<evaluate expression="playerActions/>
...
</action-state>
This code snippet would be functionally equivalent to the more explicit action state that follows, where the action-execution method, findExistingPlayer(), is explicitly identified:
<action-state id="findExistingPlayer">
<evaluate expression="playerActions.findExistingPlayer"/>
...
</action-state>
Next let’s look at a more configuration-based action, FormAction.
FORMACTION
The FormAction class helps simplify working with forms by providing several conve-nience methods that make it easy to set up, validate, and bind form data. Each method takes in an instance of RequestContext:
Reimplemented in MultiAction class
■ setupForm()—Creates the form object as well as the errors object that accom-panies it. Unless an exception occurs, this method returns success.
■ bindAndValidate()—Binds all the available request parameters to the speci-fied form object and validates the data. If a validation error occurs, an error event is returned. Otherwise, a success event is returned.
■ bind()—Binds all the available request parameters to the specified form object without the additional validation step. This method returns a success event unless binding errors occur. In that case, an error event is returned.
■ validate()—Only validates the data and assumes that the form data had already been bound. If a validation error occurs, an error event is returned.
Otherwise, a success event is returned.
■ resetForm()—Reloads the underlying form object, causing the form to reset.
Unless an exception occurs, this method returns success.
There should be no reason to extend the FormAction class directly. Instead, you can configure the form action as a Spring bean, as show in the following listing.
<!-- Source project: sip05, branch: 04 (Maven Project) -->
<%-- Partial code listing below --%>
<bean id="findExistingPlayerFormAction"
class="org.springframework.webflow.action.FormAction">
<property name="formObjectClass"
value="com.springinpractice.ch05.domain.search.PlayerSearchCriteria"/>
<property name="propertyEditorRegistrar">
<bean class="com.springinpractice.
➥ ch05.beans.CustomPropertyEditorRegistrar"/>
</property>
</bean>
Three examples of using this bean were provided earlier, in listing 5.8, starting at
D
. Each of the classes we talked about in this recipe is configured in webflowCon-text.xml in the chapter’s source code (sip05, branch: 04). Look at the comments in the findExistingPlayer-flow.xml file for hints on how to use each. You can test the flow by pointing your browser at (adjusting for host name and port number) http://local-host:8080/sip/findExistingPlayer.Now we’ll take a deeper dive into form data binding in section 5.5 and validating form data using JSR-303 in section 5.6.