Tuesday, March 24, 2009

Spring Webflow Tutorial

Tutorial: Build a Shopping Cart with Spring Web Flow 2.0

This step-by-step tutorial shows you how to build a shopping cart using the new Spring Web Flow 2.0 framework.

This article shows you how to get started with Spring Web Flow (SWF) 2.0 by building a simple shopping cart application. We'll do this as a series of steps:

  1. First we'll just get Spring MVC working.
  2. We'll use Spring Web Flow to create a minimal flow with a single state.
  3. Finally we'll build the app out to contain multiple flows and subflows that make calls against backend logic.

SWF 2.0 is at the time of this writing very new, and there are several differences as compared to SWF 1.0.x. We will not however spend much time discussing those differences; instead we'll just focus on SWF 2.0.

To get the most out of this article, you should already be familiar with Spring in general and Spring MVC in particular.


An Overview of Spring Web Flow

Spring Web Flow builds upon Spring MVC to support user-level, application-directed control flows. For example, an e-commerce application may need to guide the end user through a checkout process that spans several web pages. The flow is not simply a multipage form (Spring MVC itself already supports that); rather it is a multistep process involving standard control flow constructions such as decisions ("please confirm or cancel your order"), loops ("we recommend products x, y, z... add as many as you like to your order") and subflows ("are you a new customer? please create an account"). In general, implementing such flows is not trivial. SWF is an elegant solution to just this sort of problem.

To understand better, contrast application-directed interactions with the user-directed interactions that typically constitute the main part of an application's functionality. In user-directed interactions, the end user picks some function from a set of available functions, provides some parameters around that function, and then asks the application to carry it out. For instance, the end user tells Photoshop to fill a selected region with "evil green" (RGB 00FF00). The app dutifully does just that, and control returns to the end user.

There are many cases, however, in which we want the application to drive a complex interaction between itself and the end user. Perhaps the most obvious example is the one I mentioned above: the e-commerce checkout process. See Figure 1 for one possible flow (and in fact this is the flow we're going to implement):

Figure 1. A sample checkout flow.
Figure 1. A sample checkout flow.

In the checkout flow above, we have a starting state, an end state, and several intermediate states. In the first state, we show the customer the contents of a shopping cart, along with some recommended products that the customer may want to buy. The customer can add any of those products to the shopping cart, or he might decide to move forward by indicating whether he is a new or returning customer. If he's a new customer, he'll need to create an account; otherwise he can just log in with his existing account. In either case, he'll need to select a payment method, provide shipping information, and so forth. At any step along the way he can cancel out of the checkout process.

Benefits of using Spring Web Flow

While you can certainly implement that flow without SWF, there are some challenges involved if you're doing it from scratch. Let's take a look at some of those.

Understanding flow logic. For one, because the logic behind the flow itself is reasonably complex, it would be nice to be able to isolate the flow and its logic from the various pieces (like JSP pages, business logic, etc.) that make it up. But the most straightforward implementation of flow logic would most likely involve distributing the flow logic (such as state transitions) across lots of different files. So one challenge is that you either have to do a lot of extra work to externalize the flow logic, or else you have to live with that logic being distributed, which makes it much harder to understand.

Reusing flow logic. Another challenge is related to the fact that a flow has a logical structure that stands on its own, and in many cases you'd like to be able to reuse that—either across multiple apps, or else in multiple places within a single app. This is hard to accomplish without an easy way to isolate that flow.

Getting the implementation right. A third challenge is just that it's easy to get the technical details wrong. For instance, what happens if the end user hits the browser's back button in the middle of the flow? If the previous page had a form, we don't usually want the browser to confuse the user with a warning about resubmitting data or whatever. Coding up flows from scratch means that you have to handle this sort of thing explicitly. It would be nice not to have to mess around with this kind of thing—to have it handled automatically for you.

That's enough of an overview for us to get started. Let's now look at setting up our sample project.


Spring MVC Setup

Since Spring Web Flow is build on Spring MVC, let's start by getting Spring MVC working. We'll create a simple home page for our shopping cart, and we'll serve this up using a plain old Spring controller (well, it will be annotated) rather than serving it using Spring Web Flow. That's because the home page itself isn't part of any particular flow; it simply provides entry points into various flows, such as creating an account, logging in, adding an item to a shopping cart, and checking out. Besides allowing us to make sure we have Spring MVC working before moving forward, this approach will also allow us to see how to integrate Spring MVC and Spring Web Flow.

For your convenience, here's a download of the minimalistic (i.e. only Spring MVC, no SWF) shopping cart we're about to take a look at:

The download above does not include its dependencies. You will need to grab those separately. I've provided the links below.

Dependencies for mycart1.zip

These are all part of the Spring 2.5.4 distribution. Spring Web Flow 2.0 requires Spring 2.5.4 or higher. [download]

  • spring.jar (located in /spring-framework-2.5.4/dist)
  • spring-webmvc.jar (located in /spring-framework-2.5.4/dist/modules)
  • commons-logging.jar (located in /spring-framework-2.5.4/jakarta-commons)

We will be adding dependencies as we progress; for the moment we're just getting Spring MVC set up.

Create a Spring MVC Controller

Here's a very simple Spring MVC controller. We'll be updating this over the course of the article.

Code listing: mycart.CartController
  1. package mycart;

  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.RequestMapping;

  4. @Controller
  5. public class CartController {

  6. @RequestMapping("/home.do")
  7. public void doHome() {
  8. }
  9. }

This controller doesn't do much at all. Basically we're using annotations to map /home.do requests to a JSP. For more details on how that works, please see my article Annotation-Based MVC in Spring 2.5.

Create a JSP

Here's the home page JSP I just mentioned. Like CartController, we'll be updating this.

Code listing: /WEB-INF/jsp/home.jsp

  1. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2. <html>
  3. <head>
  4. <title>Products for Geeks - GeekWareztitle>
  5. head>
  6. <body>
  7. <h1>Welcome to GeekWarezh1>
  8. body>
  9. html>

Create web.xml

Here's our web.xml file:

Code listing: /WEB-INF/web.xml
  1. xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://java.sun.com/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  5. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  6. version="2.5">

  7. mycart org.springframework.web.servlet.DispatcherServlet mycart *.do

All we're doing here is creating the Spring MVC DispatcherServlet front controller. Because we've named it mycart, the default behavior for DispatcherServlet is to look for a Spring application context configuration file at /WEB-INF/mycart-servlet.xml, which we are about to see.

Eventually this front controller will handle not only our "normal" non-SWF requests, but also our SWF requests. However I'm getting ahead of myself.

Create the Spring application context file

Here's mycart-servlet.xml, which DispatcherServlet loads as just explained:

Code listing: /WEB-INF/mycart-servlet.xml
  1. xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context-2.5.xsd">



  9. <context:annotation-config/>

  10. <context:component-scan base-package="mycart"/>


  11. <bean class=
  12. "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>


  13. <bean class=
  14. "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>


  15. <bean id="viewResolver" class=
  16. "org.springframework.web.servlet.view.InternalResourceViewResolver">
  17. <property name="prefix" value="/WEB-INF/jsp/"/>
  18. <property name="suffix" value=".jsp"/>
  19. bean>
  20. beans>

Nothing special here assuming you already know Spring MVC.

Milestone 1: Spring MVC is Working

At this point you should be able to deploy the application. Point your browser at

http://localhost:8080/mycart1/home.do

and you should get a very simple home page. If so, congratulations, Spring MVC is working.

Now it's time to create our first flow using Spring Web Flow.


Creating a Simple Flow: Spring Application Context Configuration

Now we have Spring MVC working, so it's time to expand on that and get Spring Web Flow working too. We'll start by creating a very simple flow—one that has only a single view state. First we'll look at the updated Spring application context configuration. After that we'll look at the flow definition itself.

To make it easier for you to follow along, here's the updated, one-state version of our shopping cart:

Dependencies for mycart2.zip

Because we're now using SWF, we have additional dependencies. Here's the full set so far:

These come from the Spring 2.5.4 distribution. [download]

  • spring.jar (located in /spring-framework-2.5.4/dist)
  • spring-webmvc.jar (located in /spring-framework-2.5.4/dist/modules)
  • commons-logging.jar (located in /spring-framework-2.5.4/jakarta-commons)

You will need the following JARs from the Spring Web Flow 2.0 distribution. [download]

  • spring-webflow-2.0.0.jar
  • spring-binding-2.0.0.jar
  • spring-js-2.0.0.jar

You will need the following JAR from the OGNL web site:

  • ognl-2.6.9.jar

Spring Web Flow uses either OGNL or Unified EL for parsing expressions. For this article I arbitrarily picked OGNL though you can use jboss-el.jar (currently the only Unified EL implementation that will work with SWF) too.

web.xml and mycart.CartController

For now we aren't making any changes to either of these at all.

Figure 2. Overview of SWF components.
Figure 2. Overview of SWF components.

Spring application context overview

Before jumping into the code, let's do a quick overview of the various components involved. Please see Figure 2.

We already saw DispatcherServlet in web.xml. (So it's not part of mycart-servlet.xml; we're discussing it here just to show the relationship between the servlet and the FlowController.) DispatcherServlet is the Spring MVC front controller and it receives any Spring MVC requests—including SWF requests, as SWF is based on Spring MVC—according to your web.xml configuration.

We're using SimpleUrlHandlerMapping to map flow requests from DispatcherServlet to FlowController.

So now FlowController has the request. FlowController is just a Spring MVC controller that receives flow requests and passes them to FlowExecutor for actual processing.

FlowExecutor contains the actual logic for processing Spring Web Flow requests. Among other things it determines for any given request which flow is involved and figures out what state transition to enact, based on the request.

When FlowExecutor needs a flow, it grabs the flow from the FlowRegistry, which is responsible for loading the flow from a flow configuration file and maintaining it on behalf of the FlowExecutor.

FlowBuilderServices is just a container for various services that FlowRegistry needs when constructing flows. This includes, for example, a service to provide for the creation of view factories. In our example, we will see that we're specifically using a MvcViewFactoryCreator, which creates view factories for Spring MVC views.

Finally, we define a ViewResolver, which is a Spring MVC interface that carries logical view names to physical resources (such as JSPs). For example, a ViewResolver might carry the logical name checkout/viewcart to the physical resource /WEB-INF/jsp/checkout/viewcart.jsp.

Spring application context configuration file

And finally here's the configuration file itself:

Code listing: /WEB-INF/mycart-servlet.xml
  1. xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:flow="http://www.springframework.org/schema/webflow-config"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context-2.5.xsd
  10. http://www.springframework.org/schema/webflow-config
  11. http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">






  12. <context:annotation-config/>

  13. <context:component-scan base-package="mycart"/>


  14. <bean class=
  15. "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>


  16. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  17. <property name="mappings">
  18. <value>
  19. /account/register.do=flowController
  20. value>
  21. property>
  22. <property name="alwaysUseFullPath" value="true"/>
  23. bean>


  24. <bean class=
  25. "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>


  26. <bean class=
  27. "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>


  28. <bean id="viewResolver" class=
  29. "org.springframework.web.servlet.view.InternalResourceViewResolver">
  30. <property name="prefix" value="/WEB-INF/jsp/"/>
  31. <property name="suffix" value=".jsp"/>
  32. bean>




  33. <bean id="flowController" class=
  34. "org.springframework.webflow.mvc.servlet.FlowController">
  35. <property name="flowExecutor" ref="flowExecutor"/>
  36. bean>

  37. <flow:flow-executor id="flowExecutor" flow-registry="flowRegistry"/>


  38. <flow:flow-registry id="flowRegistry"
  39. flow-builder-services="flowBuilderServices">
  40. <flow:flow-location path="/WEB-INF/flows/register.xml"/>
  41. flow:flow-registry>

  42. <flow:flow-builder-services id="flowBuilderServices"
  43. view-factory-creator="viewFactoryCreator"/>

  44. <bean id="viewFactoryCreator" class=
  45. "org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
  46. <property name="viewResolvers">
  47. <list>
  48. <ref bean="viewResolver"/>
  49. list>
  50. property>
  51. bean>
  52. beans>

Now let's look at our basic flow definition, which will initially at least be much simpler than the application context file.


Creating a Simple Flow: Flow Definition File and JSPs

We'll now add a SWF flow definition file, update our original home page JSP, and add a new JSP for registering as a new customer.

The flow definition file

Here's a definition for our first flow, which will be a bare-bones registration process. We'll add more flows to the application but this is our starting point just to get SWF working. You'll need to create a directory /WEB-INF/flows, and then add the following flow definition file to that directory, naming the file register.xml.

Code listing: /WEB-INF/flows/register.xml
  1. xml version="1.0" encoding="UTF-8"?>
  2. <flow xmlns="http://www.springframework.org/schema/webflow"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/webflow
  5. http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">


  6. <view-state id="register" view="account/registerForm">
  7. <transition on="submitRegistration" to="accountAdded"/>
  8. <transition on="cancelRegistration" to="cancelRegistration"/>
  9. view-state>

  10. <end-state id="accountAdded"
  11. view="externalRedirect:contextRelative:/home.do"/>
  12. <end-state id="cancelRegistration"
  13. view="externalRedirect:contextRelative:/home.do"/>
  14. flow>

Here we have a single state, called a view state, and all it does is show us the hardcoded JSP that we created earlier. We're identifying the state itself as register, and the logical view name associated with this view state is account/registerForm. This is the name that the view resolver we defined in the Spring app context file will map to a physical location; in this case the physical location will be /WEB-INF/jsp/account/registerForm.jsp.

By default, SWF interprets the first state in the file as being the start state, or entry point into the flow. There must be exactly one start state. The flow itself is called register (this is because we've named the flow definition file register.xml). As it happens we've also named the first state register though there's no requirement for the start state to have the same name as the flow.

I've defined a couple of transitions out of the register state. One transition, submitRegistration, responds to registration submission events on the user interface, such as the user clicking a submit button on a registration form. The other transition, cancelRegistration, responds to cancelation events on the UI, such as the user clicking a cancel link. Each of these transitions leads to another state in the flow. In this case, both of the transitions lead to end states, which are exits out of the flow, but transitions can lead to other view states (or even other sorts of state) as well. As shown there can be multiple end states for a flow. Here my end states happen to be doing the same thing; they're redirecting the user to a context-relative (as in relative to the servlet context) path /home.do, which you will recall is just the home page. There are some other relativizations you can do as well:

Relativization Example
Servlet mapping-relative externalRedirect:/hotels/index
Servlet-relative path externalRedirect:servletRelative:/hotels/index
Context-relative path externalRedirect:contextRelative:/dispatcher/hotels/index
Server-relative path externalRedirect:serverRelative:/springtravel/dispatcher/hotels/index
Absolute URL externalRedirect:http://www.paypal.com/
Table 1. Relativizations for externalRedirect.

Anyway we're doing the context-relative redirection in this case. This is how we jump out of SWF and return control to the application. (Or how we return control to a calling flow, but we'll get to that.)

The JSPs

Now let's revisit our home page JSP. It's still pretty similar but I'm adding a registration link.

Code listing: /WEB-INF/jsp/home.jsp

  1. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2. <html>
  3. <head>
  4. <title>Products for Geeks - GeekWareztitle>
  5. head>
  6. <body>
  7. <h1>Welcome to GeekWarezh1>

  8. <div><a href="account/register.do">Registera>div>
  9. body>
  10. html>

The registration link takes us into the register flow, since the last path segment is register.do. In our Spring app context we told the flow registry about register.xml, so SWF will know to map requests for /account/register.do to the register flow.

Now we need a registration form. The following page is our current version of a registration "form", but as you can see it isn't really a form at all (yet). It is just a couple of links:

Code listing: /WEB-INF/jsp/account/registerForm.jsp

  1. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2. <html>
  3. <head>
  4. <title>Register - GeekWareztitle>
  5. head>
  6. <body>
  7. <h1>Registerh1>

  8. <div>
  9. <a href="${flowExecutionUrl}&_eventId=submitRegistration">Submita> |
  10. <a href="${flowExecutionUrl}&_eventId=cancelRegistration">Cancela>
  11. div>
  12. body>
  13. html>

The point of the two links is to give the user a way to generate submitRegistration and cancelRegistration events. We saw in the flow definition that these two events trigger state transitions. Of special importance is the URL for each of these links. Notice that by using ${flowExecutionUrl}, we're still pointing the user at the same /account/register.do servlet path. That's because we're still working within the register flow. The ${flowExecutionUrl} URL includes an execution HTTP parameter whose value is a key that SWF uses for flow-tracking purposes. In addition we add an _eventId parameter that we use to drive state transitions. The event IDs are what we're referencing when we define transitions in the flow definition file.

So really so far all we can do at this point is move back and forth between the home page and the registration page, but we're doing it with Spring Web Flow.

Milestone 2: Spring Web Flow is Working

Point your web browser at

http://localhost:8080/mycart2/home.do

making any adjustments you need to make for the port or application context. Also note that the context is now mycart2 instead of mycart1 like it was in the first version of the code. If you're able to click back and forth between the home page and the registration page, then congrats, Spring Web Flow 2.0 is working! Celebrate!


Building Out the Application

Now that we've gotten Spring MVC and SWF working, it's time to build out the shopping cart application. Please download the following file before continuing:

We won't be looking at every aspect of this application, but I'll highlight the most interesting pieces from a SWF point of view.

Dependencies for mycart3.zip

Besides the dependencies we had for mycart2.zip, there are four others you'll need:

After you download mycart3.zip (and its dependencies), you can deploy it and then point your browser at

http://localhost:8080/mycart/home.do

Here are some notes to keep in mind:

  • My registration and login flows don't actually "do" anything. They're not hooked up to Spring Security and they're not hooked up to a database. So when you register you can just hit the submit button, and when you log in you can just hit the login button. I just wanted to focus on the flows themselves.
  • I'm using Sitemesh to ensure a unified layout across the pages. Even if you are not familiar with Sitemesh, don't worry: it is very straightforward. Basically you just put a servlet filter in front of the pages and the filter decorates the pages with a template (/WEB-INF/jsp/pagetemplate.jsp in this case) according to a simple configuration file (/WEB-INF/decorators.xml). If however you don't want to use Sitemesh, simply remove the filter from web.xml and Sitemesh is gone.

Multiple flows

Any given app may contain multiple flows, and mycart3 is such an app. We have four different flows:

  • addToCart: add an item to a shopping cart
  • checkout: shopping cart checkout process
  • login: log into the app
  • register: register for a new user account

In some of the cases we have what we would intuitively consider a flow in that there are multiple states involved; in other cases there is only one state to speak of and so it may seem strange to regard the flow as being a flow. However we'll see how that can make sense in the following section.

To create multiple flows, you will need to do the following. First, create the flow definition files and put them in your /WEB-INF/flows directory (or wherever you decided to put them). Then add the flow locations to the flow registry, and the flow URL mappings to your SimpleUrlHandlerMapping (if that's what you're using), in your Spring application context, like so:

Code listing: /WEB-INF/mycart-servlet.xml
  1. ...

  2. <bean id="flowUrlMappings" class=
  3. "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  4. <property name="mappings">
  5. <value>
  6. /addToCart.do=flowController
  7. /checkout.do=flowController
  8. /account/login.do=flowController
  9. /account/register.do=flowController
  10. value>
  11. property>
  12. <property name="alwaysUseFullPath" value="true"/>
  13. bean>

  14. <flow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
  15. <flow:flow-location path="/WEB-INF/flows/addToCart.xml"/>
  16. <flow:flow-location path="/WEB-INF/flows/checkout.xml"/>
  17. <flow:flow-location path="/WEB-INF/flows/login.xml"/>
  18. <flow:flow-location path="/WEB-INF/flows/register.xml"/>
  19. flow:flow-registry>

  20. ...

Once you have those in place you should be set up for multiple flows. Point your links at them and try them out!


Subflows

Going hand-in-hand with the idea of multiple flows is the idea that some flows might be subflows of other flows. In mycart3, all four flows can be independently accessed, but in addition to that we have the addToCart, login and register flows being subflows to the checkout flow. See Figure 3.

Figure 3. Subflows.
Figure 3. Subflows.

Here's the idea. The three flows we've identified as subflows are defined as separate flows because clearly there are use cases where it makes sense for them to be accessed outside of a checkout process. For example, we want to be able to add products to a shopping cart while we're browsing the product catalog. And of course we want people to be able to register and login even if they're not in a checkout process.

But those are also flows that we might want to include as part of a checkout process too. During checkout, we might want to recommend products to the customer. Or if the user hasn't yet logged in or registered, we'd want them to do that as part of the checkout process rather than forcing them to do that before they could enter the checkout process.

By defining the login, registration and add-to-cart as flows, we make them available both independently (as top-level flows) and also as part of a larger flow (like the checkout flow). That's why we have these defined as flows even though they are currently implemented as single-state flows. (It is however easy to imagine these being multi-state flows. For example, the login flow may ask you to answer a challenge question if it detects that you're coming in from an unusual IP address, or the add-to-cart flow may ask you to enter a quantity before proceeding.)

So here is the checkout flow:

Code listing: /WEB-INF/flows/checkout.xml
  1. xml version="1.0" encoding="UTF-8"?>
  2. <flow xmlns="http://www.springframework.org/schema/webflow"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/webflow
  5. http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">


  6. <on-start>
  7. <set name="flowScope.products" value="cartService.products"/>
  8. <set name="flowScope.shippingOptions" value="cartService.shippingOptions"/>
  9. on-start>


  10. <view-state id="viewCart" view="viewcart">

  11. <set name="requestScope.shoppingCart" value="shoppingCart"/>
  12. <set name="requestScope.recommendations" value="cartService.recommendations"/>
  13. on-render>
  14. <transition on="addToCart" to="addProductToCart"/>
  15. <transition on="register" to="register"/>
  16. <transition on="login" to="login"/>
  17. view-state>

  18. <subflow-state id="addProductToCart" subflow="addToCart">

  19. <subflow-state id="register" subflow="register">
  20. <transition on="accountAdded" to="paymentAndShipmentOptions"/>
  21. <transition on="cancelRegistration" to="viewCart"/>
  22. subflow-state>


  23. <subflow-state id="login" subflow="login">

  24. <view-state id="paymentAndShipmentOptions" view="options">
  25. <transition on="submit" to="confirmOrder"/>
  26. <transition on="back" to="viewCart"/>
  27. view-state>


  28. <view-state id="confirmOrder" view="confirmorder">
  29. <on-render>
  30. <set name="requestScope.shoppingCart" value="shoppingCart"/>
  31. on-render>
  32. <transition on="continue" to="thankYou">
  33. <evaluate expression="cartService.submitOrderForPayment()"/>
  34. transition>
  35. view-state>


  36. <view-state id="thankYou" view="thanks">
  37. <transition on="continue" to="shop"/>
  38. view-state>


  39. <end-state id="shop" view="externalRedirect:contextRelative:/home.do"/>

  40. <global-transitions>
  41. <transition on="cancelCheckout" to="shop"/>
  42. global-transitions>
  43. flow>

In the code above, we are using the element to call a subflow from a parent flow. The subflow attribute specifies one of the flows you registered with the flow registry in the Spring configuration. The flow starts at the subflow's start state, and continues until the subflow hits an end state. The end state IDs provide keys that you can reference from the calling flow to effect state transitions. For example, in the flow above, accountAdded is one of the end states for the register flows, and so one of the elements references that end state.

Recall from register.xml (see page 4) that the end states specified a view attribute. If the register flow is called directly, then SWF will use the view attribute to decide which view to show the user when the flow reaches a given end state. If, however, the flow is called as part of a subflow (instead of being called directly), SWF will ignore the view attribute and instead follow whatever transition is defined in the calling .


Using EL to Call Services

SWF allows you to make calls against service beans using an expression language (EL). You can use either Unified EL or else OGNL for that, as mentioned earlier in the article. We happen to be using OGNL though I understand that Unified EL and OGNL are mostly the same, at least where their core syntax is concerned.

There are various places in your flow where you might want to invoke service beans, and SWF provides various mechanisms for doing that. Here's a table showing how to call services from different locations in your flow definition file:

Location How to call the service bean
On flow start /flow/on-start element
On state entry /flow/view-state/on-entry element
Immediately before rendering a view /flow/view-state/on-render element
On state exit /flow/view-state/on-exit element
On state transition /flow/view-state/transition/evaluate element
On flow end /flow/on-end element
Table 2. How to call service beans from different locations.

Note that the above does not represent a 100% complete list of places where you can use EL, but it gives you most of the major cases and also the basic idea.

In checkout.xml we've used a number of the methods above. For example, we use to grab the product catalog and shipping options from the cart service and place them on the flow scope, which allows them to be used for the duration of the flow. The value attribute on the set element is specified using EL. (It looks a lot like JSP EL, if you are familiar with that.) The cartService bean is available because we defined a Spring bean with that ID.

We also use to prepare the viewCart state for rendering. In this case, we place the user's shopping cart on the request scope so the JSP can easily reference it, and we also pull some recommendations off of the cart service and place those on the request scope as well. Once again the value attribute is specified using EL.

Global Transitions

At the end of checkout.xml, you will see that I've defined a element. This allows me to define a transition that applies to all states in the flow. In this case, any time the user raises the cancelCheckout event, the global transition I've defined will kick in and carry the user to the shop state. Pretty handy for transitions that occur in multiple places throughout the flow.

That's Our Whirlwind Tour

That concludes this tutorial on Spring Web Flow 2.0. We've really only scratched the surface—for instance, we haven't even touched the new AJAX support that SWF 2.0 introduces—but this should give you an overall feel for how things work. The SWF 2.0 distribution comes with a hotel booking sample application that shows you how to get SWF working with form validation and persistence as well (areas I've suppressed in this article so that I could focus on flow definition).

If you have suggestions and especially corrections to the article or the code, please leave them below. SWF 2.0 is new so I have no doubt that you will be able to show me places where I could be doing something better. Thanks!

3 comments:

  1. Copy paste, u do nothing...

    ReplyDelete
  2. Discover stunning Webflow events templates for your website! Create memorable experiences with ease. Boost engagement and conversions now!

    flowgiri

    ReplyDelete
  3. Uncover Striking Webflow Templates for Your Website Events! Effortlessly Craft Unforgettable Experiences. Elevate Engagement and Drive Conversions with Style!

    ReplyDelete