• No se han encontrado resultados

de la Comisión Permanente

PREREQUISITES

Installing and configuring SWF KEY TECHNOLOGIES

SWF

Background

SWF is best applied when you need to navigate a user through a series of predefined and/or dynamic steps (states) to achieve a desired outcome or complete a unit of work. Now that you’ve installed SWF and validated that it’s working properly, your next

Figure 5.5 If everything is working correctly, you should see “Hello World” from SWF.

step is to use SWF to implement the different flows and states required by the Spring Soccer Club demo application introduced in section 5.3.

Problem

You would like to use SWF to manage a complex page flow representing a single unit of work.

Solution

Continuing with the Spring Soccer Club theme, your next step is to implement the different flows and states shown in figure 5.3. For a brief discussion of this demo appli-cation, refer back to section 5.3.

Because searching for an object is normally a reusable flow, you’ll split the overall flow into a registration main flow and a findExistingPlayer subflow. Figure 5.6 illus-trates this change.

CREATING THE FINDEXISTINGPLAYER SUBFLOW

Let’s get started putting together the findExistingPlayer subflow. The following list-ing contains the contents for the findExistlist-ingPlayer-flow.xml file. We’ll discuss the contents of this file in detail in the next several sections.

<?xml version="1.0" encoding="UTF-8"?>

<!-- Source project: sip05, branch: 03 (Maven Project) -->

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

Listing 5.8 /WEB-INF/flows/findExistingPlayer/findExistingPlayer-flow.xml

Figure 5.6 Being able to search for and select an existing player may be useful in several places in a larger application. As a result, you’ll separate the findExistingPlayer functionality into its own subflow. It will be called from the main registration flow.

start-state="findExistingPlayerForm">

<view-state id="findExistingPlayerForm">

<on-render>

<!-- Prepares the form object for display on a form -->

<evaluate expression="findExistingPlayerAction.setupForm"/>

</on-render>

<transition on="find" to="findExistingPlayerActionState">

<evaluate

expression="findExistingPlayerAction.bindAndValidate"/>

</transition>

</view-state>

<action-state id="findExistingPlayerActionState">

<evaluate expression="playerService.

findExistingPlayer(playerSearchCriteria)"

result="flowScope.player"/>

<transition on="success" to="displayFindExistingPlayerResult"/>

</action-state>

<!--

You can either end the subflow or reset the form and start over...

-->

<action-state id="newSearch">

<evaluate expression="findExistingPlayerFormAction.resetForm"/>

<transition on="success" to="findExistingPlayerForm"/>

</action-state>

-->

<view-state id="displayFindExistingPlayerResult">

<transition on="back" to="findExistingPlayerForm"/>

<transition on="newSearch" to="newSearchEndState"/>

<transition on="noneMatch" to="endState"/>

<transition on="existingAccountFound"

to="existingAccountFoundEndState"/>

</view-state>

<end-state id="newSearchEndState"/>

<end-state id="endState"/>

<end-state id="existingAccountFoundEndState" >

<output name="loginUsername" value="player.guardian.username"/>

</end-state>

<global-transitions>

<transition on="skip" to="endState"/>

</global-transitions>

</flow>

The previous listing contains many of the items we’ve talked about up to this point.

Because this listing is large, let’s start by focusing on the first several lines. At

B

, you explicitly set the starting state for the flow. At

C

is your first view state, named find-ExistingPlayerForm. Recalling our discussion from section 5.2.2, if you don’t explic-itly specify a view name, the view name will be the same as the id attribute by convention. At

D

you use an Action class to manage your form object. We’ll discuss handling forms in more detail later.

Specifies

The action state at

E

processes the search criteria submitted from the view state at

C

. Here you pass the criteria object to your search service. Because you’re focus-ing on SWF, this stub implementation always returns a single result.

On the search results view, the end user has an option to perform another search.

Clicking the New Search link fires off a newSearch event that currently transitions to newSearchEndState at

H

. Looking at a snippet of code from the parent flow, you can see that newSearchEndState is mapped back to the findExistingPlayer subflow state:

<subflow-state id="findExistingPlayer" subflow="findExistingPlayer">

<transition on="endState" to="newAccountForm"/>

<transition on="newSearchEndState" to="findExistingPlayer"/>

<transition on="existingAccountFoundEndState" to="sendToLoginEndState">

<set name="flowScope.loginUsername"

value="currentEvent.attributes.loginUsername"/>

</transition>

</subflow-state>

What is happening here is that you’re returning a newSearchEndState event from the subflow and mapping it back to another call to the findExistingPlayer subflow state.

Your original subflow and its state are destroyed, and you repeat the subflow using a new instance. This approach is useful when you have a complex subflow that has sev-eral steps because it ensures that all of the intermediary state gets cleared out.

F

in listing 5.8 provides another way to achieve the same behavior. Instead of destroying the current subflow and starting again, you can reset your form object and transition back to the findExistingPlayerForm to start your search again. All you have to do is change

G

to transition to the newSearch action state. Because you’re clearing out the state manually, this approach works best when there isn’t a lot of intermediary state being captured. Both approaches are valid.

I

demonstrates how a subflow can return information back to the parent flow. In this case, the user has identified that they have an existing account and have chosen to log in using that account. Figure 5.7 shows an unstyled snapshot of what the search results page looks like.

Figure 5.7 Showing the result of the displayFindExistingPlayerResult view state

Here’s a partial source listing for the displayFindExistingPlayerResult view.

<%-- Source project: sip05, branch: 03 (Maven Project) --%>

<%-- Partial code listing below --%>

<c:set var="foundPlayer"

scope="request" value="${!empty player}"/>

<head><title>Search Results</title></head>

<body>

<h2>Search Results</h2>

<c:choose>

<c:when test="${foundPlayer}">

<table border="1">

<td>${player.firstName}&nbsp;${player.lastName}</td>

<td>${player.guardian.address1}</td>

<a href="${flowExecutionUrl}&_eventId=existingAccountFound">

Login With This Account</a>

The implementation is simple. If an existing account is found, the user can choose to log in with the account listed. A link is used at

B

to fire the existingAccountFound event, which is later transitioned to existingAccountFoundEndState where the user name is returned to the parent flow.

Let’s look at the findExistingPlayerForm.jsp file next.

<%-- Source project: sip05, branch: 03 (Maven Project) --%>

<%@ include file="/WEB-INF/jsp/taglibs.jsp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

Listing 5.9 /WEB-INF/flows/findExistingPlayer/displayFindExistingPlayerResult.jsp

Listing 5.10 /WEB-INF/flows/findExistingPlayer/findExistingPlayerForm.jsp

Log in with this account

B

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<form:form commandName="playerSearchCriteria"

action="${flowExecutionUrl}" >

<label for="firstname">Player First Name</label>

<form:input path="firstName" /><br/>

<label for="lastName">Player Last Name</label>

<form:input path="lastName" /><br/>

<label for="birthDate">Birth Date</label>

<form:input path="birthDate" /><br/>

<label for="homePhone">Home Phone:</label>

<form:input path="homePhone" /><br/>

<input type="submit" name="_eventId_skip" spec-ify the name of the event you want to fire when the form is submitted. For more infor-mation, see the view-state discussion in section 5.2.2.

That about does it for the findExistingPlayer subflow. At this point, you should be able to test the flow by pointing your browser at the following address (adjusting for host name and port number): http://localhost:8080/sip/findExistingPlayer.

Keep in mind as you test the flow that because this subflow isn’t getting called from a parent flow yet, when you transition to an end state, the flow ends and all the state for the flow is destroyed. As a result, you’ll end up on the same URL that started the flow, causing a new instance of the flow to be created. Watch the value of the execu-tion variable in the browser. You’ll find that the execution represented by e is incre-mented every time the flow restarts. For more information about this variable, flip back to the view-state discussion in section 5.2.2.

Spring Form

CREATING THE REGISTRATION FLOW

With the findExistingPlayer subflow working, let’s configure the other states that are left in the example application. The following listing details the flow definition for the registration flow.

<?xml version="1.0" encoding="UTF-8"?>

<!-- Source project: sip05, branch: 03 (Maven Project) -->

<flow xmlns="http://www.springframework.org/schema/webflow"

<subflow-state id="findExistingPlayer" subflow="findExistingPlayer">

<transition on="endState" to="newAccountForm"/>

<transition on="newSearchEndState" to="findExistingPlayer"/>

<transition on="existingAccountFoundEndState" to="sendToLoginEndState">

<set name="flowScope.loginUsername"

<evaluate expression="newAccountFormAction.setupForm"/>

</on-render>

<transition on="next" to="confirmNewAccount">

<evaluate expression="newAccountFormAction.bindAndValidate"/>

</transition>

</view-state>

<view-state id="confirmNewAccount">

<transition on="back" to="newAccountForm" />

<transition on="next" to="processNewAccount" />

</view-state>

<action-state id="processNewAccount">

<set name="flowScope.loginUsername"

value="playerService.createNewAccount(newAccountForm)"/>

<transition on="success" to="sendToLoginEndState"/>

</action-state>

<end-state

id="sendToLoginEndState"

view="externalRedirect:contextRelative:/login.jsp

?username=#{flowScope.loginUsername}"/>

</flow>

At

B

, you explicitly define your start state as findExistingPlayer, which calls the findExistingPlayer subflow as its first step. At

C

, you see how to retrieve informa-tion from a subflow. currentEvent is a special EL variable that allows you to retrieve

Listing 5.11 /WEB-INF/flows/registration/registration-flow.xml

the attributes associated with the current Event. The attributes are returned in an AttributeMap, which is essentially an immutable interface that provides the read-only operations you would normally find on a map.

At

D

, you call a stub that pretends to create an account for the user. The service returns the username of the newly created account.

That wraps up the registration flow. We won’t cover the last two views, newAccoun-tForm and confirmNewAccount. They’re available in the chapter’s source. You can test the flow by pointing your browser at the following address (adjusting for host name and port number): http://localhost:8080/sip/registration.

In the “Declaring Variables” discussion in section 5.2.4, we spoke briefly about how the <set> element provides a subset of the functionality of the <evaluate> element.

At

D

in listing 5.11, you can see that the <set> element implicitly returns success.

When creating a real system, you would more than likely be coordinating with multi-ple services and would want more control over error handling and the resulting views that are returned. To do that, you need to use the <evaluate> element and put your logic into an Action class. Let’s talk about this next.

Documento similar