Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

« Special Promotion - 50% off Roo In Action for 30 days | Main | My Maven doesn't build Codehaus anymore, what's wrong? »
Sunday
Oct032010

Webflow + Roo Again - a More Complex Example...

Anyone want more info on Roo and Webflow?

Sure, you all do...

Before I start, I have to tell a quick story. I wrote this example because I didn't see good samples for the newer convention-driven webflow service call syntax. I also wanted to see what the bare-bones webflow JSPX pages would look like with the new tags.

I also needed a good example for chapter 6 of Roo in Action, which covers WebFlow, GWT and Flex. To make sure I was getting everything right, I put together this work in progress.

After I got done, I did a Google search to see whether or not anybody else had done samples like mine, which is a simple (not complete) shopping cart. It turns out that Willie Wheeler wrote a very good shopping cart example back in 2008. I encourage you to read up on that one, which is much more comprehensive than mine.

However, this is a Roo + WebFlow example, so I think the example is still quite valid.

The Example - a Shopping Cart

Yeah, yeah, yeah, shopping carts. Everybody has them, and mine is more lame! But since I'm more concerned with WebFlow mechanics than use-case perfection, let's just accept that mine is a very rudimentary example.

We're just going to hard-code three products, and allow users to add a quantity of each to a fictional cart. We will also allow users to remove the elements from the cart as well. Later phases of the webflow remain uncoded; this is just a getting started guide for now.

Installing WebFlow with Roo

This is the easiest part - just open the Roo shell on an existing project, and type

web flow

This will install all support for Web Flow, and drop a sample flow in the WEB-INF/views/sampleflow directory. You can check that out to get a feel for the mechanics, but here are the basics:

  • Web Flows are XML-driven state machines. Each user interacts with a web flow in a miniature session, called the Flow Context.
  • Flows are comprised of states and transitions.
  • Flows can store information between requests in various scopes, including the Flow Scope, which exists until the user exits the flow by hitting an end state.
  • Web Flows can execute business logic within a number of places
  • Any Spring Bean in the Application Context is available by id
  • If you build a Spring Bean that extends the WebFlow MultiAction class, and name your methods in a specific way, you can refer to them without passing a full signature.
  • To trigger a transition, you submit back to the web flow, passing it a special form variable (as shown in the examples below) with the name of the transition.

The Shopping Cart flow

We start with the flow definition preamble:

<?xml version="1.0" encoding="UTF-8"?>
<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">

  <persistence-context />

We are using the persistence-context tag to enable JPA persistence tracking. Our JPA object, ShoppingCart, is held in this persistence context, and is our form object in the various views. You should note that if you want to use entities in your context, you should make them serializable.

Now, we start the flow. The first stage:

<var name="shoppingCart" class="com.chotchkies.model.ShoppingCart" />

<!-- A sample view state -->
<view-state id="show-cart">
  <transition on="add" to="show-products"/>
  <transition on="checkout" to="end-state"/>
  <transition on="removeItem" to="remove-item">
    <set name="flowScope.productId" value="requestParameters.productId" 
       type="java.lang.Long" />
  </transition>
  <transition on="empty">
    <evaluate expression="cartManager.clearCart" />
  </transition>
</view-state>

The first thing the flow does is pre-create our root JPA entity, ShoppingCart. This is stored within a special holder called the flow scope. Since it is a JPA entity, it is automatically flushed and persisted on each transition.

We then render our first view-state, show-cart. WebFlow tries to resolve the view name by looking up the definition in the Tiles view.xml file, located in the flow directory. I've replaced a more complex file with wildcard support, which was recently added in the newest Tiles release:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
    <definition extends="default" name="*">
        <put-attribute name="body" value="/WEB-INF/views/cart/{1}.jspx"/>
    </definition>
</tiles-definitions>

Note - this makes it possible to just drop new files in the flow definition directory, WEB-INF/views/cart, without modifying the views.xml file each time.

looks for a file named show-cart.jspx, within

We have several transitions, or exit paths, from this view-state:

  • add - transition to the show-products view-state
  • checkout - transition to the end-state
  • removeItem - transition to the remove-item state, but first pull the submitted productId and store it in the flowScope as productId
  • empty - a transition without a destination - this executes a method in the object named cartManager called clearCart - which removes the products from the user's shopping cart.

How do we execute these transitions? Here is a snippet from the button bar at the bottom of the cart page:

<form:form>
  <input type="submit" id="add" name="_eventId_add" value="Add Additional Items..." />
  <input type="submit" id="checkout" name="_eventId_checkout" value="Checkout" />
  <input type="submit" id="empty" name="_eventId_empty" value="Empty Cart" />
</form:form>

You can see the special names - they reference the transition name after the prefix _eventId and an additional underscore. When Web Flow sees these tags, it attempts to perform the transition attached to that tag.

More About Expressions and Convention

When navigating to the show-products state, an on-entry event is triggered, which fires off a call to a Spring Bean, CartManager. Here is the fragment:

<on-entry>
  <evaluate expression="cartManager.getAllProductsNotInCart" />
</on-entry>

The CartManagerImpl class, which extends the WebFlow MultiAction base class, has this method signature for getAllProductsNotInCart:

public Event getAllProductsNotInCart(RequestContext context)

Because it uses this syntax, we don't need to reference the parameters in the XML definition. Nice touch, eh? This pushes some of the details into the bean itself, but also can simplify the XML definition. Here is the full method:

@Override
public Event getAllProductsNotInCart(RequestContext context) {
  ShoppingCart cart = getCart(context);
  Set<Long> keySet = cart.getItems().keySet();
  List<Product> products = Product.findProductsNotIn(keySet);
  context.getViewScope().asMap().put("productList", products);
  return success();
}

private ShoppingCart getCart(RequestContext context) {
  ShoppingCart cart = (ShoppingCart) context.getFlowScope().get("shoppingCart");
  return cart;
}

This shows a few helpful conventions. First, the RequestContext is a class that provides access to all of the scopes, including flowScope and viewScope. In this case, because each time we show the products we want to re-evaluate whether they are in the cart, we place the information in the viewScope variable. This data is only held while rendering the view.

Our helper method, getCart, shows how to access the flow scope. Remember the shoppingCart variable defined in the var tag at the top of the flow? Yep, it's accessed using the context.getFlowScope() method.

Wrap-up - the full flow example

Those are some of the key conventions used by Web Flow. I'm working on this sample, which I'll be adding to a GIT repository soon. For now, here is the rest of the flow, and a complete sample page.

Cart Flow

Notice the end-state - it has a special attribute, commit=true. This makes sure that on exit of the end state, any in-flight JPA changes will be flushed and committed. This is the normal behavior on each step, but when leaving a flow in error, you may wish to have an error end state that does not commit.

<?xml version="1.0" encoding="UTF-8"?>
<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">

  <persistence-context />

  <var name="shoppingCart" class="com.chotchkies.model.ShoppingCart" />

    <!-- A sample view state -->
    <view-state id="show-cart">
      <transition on="add" to="show-products"/>
      <transition on="checkout" to="end-state"/>
      <transition on="removeItem" to="remove-item">
        <set name="flowScope.productId" value="requestParameters.productId" 
           type="java.lang.Long" />
      </transition>
      <transition on="empty">
        <evaluate expression="cartManager.clearCart" />
      </transition>
    </view-state>

    <action-state id="remove-item">     
      <evaluate expression="cartManager.removeItemFromCart" />
      <transition on="success" to="show-cart" />
    </action-state>

    <view-state id="show-products">
      <on-entry>
        <evaluate expression="cartManager.getAllProductsNotInCart" />
      </on-entry>
      <transition on="select" to="confirm-product">               
        <evaluate expression="cartManager.configureCartItem" />
      </transition>
      <transition on="cancel" to="show-cart" />
    </view-state>

    <view-state id="confirm-product" model="flowScope.currentItem">           
      <transition on="confirm" to="show-cart"/>             
      <transition on="cancel" to="show-cart">
        <evaluate expression="cartManager.removeItemFromCart" />
      </transition>
    </view-state>

  <action-state id="add-product">
    <!-- KJR - todo -->
    <transition to="show-cart"/>
  </action-state>

  <view-state id="enter-address">
    <transition on="continue" to="confirm-shipping"/>
    <transition on="back" to="checkout-cart"/>
  </view-state>

  <view-state id="confirm-shipping">
    <transition on="continue" to="end-state"/>
    <transition on="back" to="enter-address"/>
  </view-state>

  <end-state id="end-state" view="end-state" commit="true"/>

</flow>

The show-products.jspx view

Note the use of the special variable, ${flowExecutionKey}, which represents the proper webflow flow for the server. If you're writing your own links, and not posting the form, you need to include this. Also note that all scoped variables are 'just there' in the context by their names.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:page="urn:jsptagdir:/WEB-INF/tags/form" 
  xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" 
  xmlns:c="http://java.sun.com/jsp/jstl/core" 
  xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
  xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" 
  xmlns:form="http://www.springframework.org/tags/form" 
  version="2.0">
    <jsp:output omit-xml-declaration="yes"/>
    <h3>Shopping Cart Contents</h3>

    <ul>
      <c:forEach items="${productList}" var="product">
        <li>${product.name} - Price: ${product.price} -
         <c:url var="addUrl" value="">
          <c:param name="productId" value="${product.id}"/>
          <c:param name="_eventId" value="select"/>
          <c:param name="execution" value="${flowExecutionKey}" />
        </c:url>
          <a href="${addUrl}">Add...</a>
        </li>      
      </c:forEach>
    </ul> 

    <form:form>
      <input type="submit" id="cancel" name="_eventId_cancel" value="Cancel and return to cart..." />
    </form:form>

</div>

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (5)

The next version of Spring Web Flow will feature Java-based flow definitions:

https://jira.springframework.org/browse/SWF-295

October 12, 2010 | Unregistered CommenterRossen Stoyanchev

That's really awesome. BTW a huge fan of what they did in Grails with the flow closures. This to me is a useful thing - you get away from the artifice of the XML to java barrier a bit.

Thanks, Rossen.

October 12, 2010 | Registered CommenterKen Rimple

Hi Ken

Thanks for the very informative post, it's much appreciated.

I have a question about Web Flow that I just cannot seem to find out the definitive answer. So, I am asking you for your thoughts and maybe advice...

It is: If a user clicks on a page that is outside of the flow and later returns to the flow, are the contents (variables etc) of the first flow lost?

In the real world, a user, after adding a product to the cart, may well go off and search the site for another product to buy. In doing so, they have left the flow.

Thanks

Lyndon

March 24, 2011 | Unregistered CommenterLyndon

Lyndon,

The whole thing about web flows are that they post back to the flow URL with a special flow execution key - it's ?execution=ensn, where e means execution, and s means step. So you'll see things like ?execution=e3s12 - your user's Flow Execution ID 3, step 12. Every time you call the servlet again, it rewrites the URL and puts the next highest # on it. So you start with e1s1, then go to e1s2 and so on. If you remove the ?execution= from your URL, it starts a new webflow. And by default I think any given client is allowed 5 concurrent flows, and once you hit the sixth one, the first one is garbage collected.

There are a few variables, at least in the view that you can use - I'm not 100% sure if you have access to them in your executions or whether Web Flow adds them afterward (and it's something I'm going to research). One of them is flowExecutionKey, the other is flowExecutionUrl - each could be useful if you could preserve them. You probably have access to the numerical values via the RequestContext variable in your beans, if you injected it as flowRequestContext.

Another angle would be to create a flow execution listener, and capture the flow keys, storing them somewhere, like in your session. Then you could have a helper that builds the URL for you, and if no stored execution lives in your session, you could generate a URL without the ?execution= setting. There are probably too many edge conditions in this idea but it sounds like an avenue to research.

March 24, 2011 | Registered CommenterKen Rimple

Thanks for that Ken.

You are correct, you can leave a flow but when you return to the flow you MUST use the flowExecutionUrl otherwise a new flow instance is started.

The Cart can normally be accessed by two actions:
1. Add to Cart.
2. View Cart.

Of course every product on the site will have an 'Add to Cart' button but essentially it is just two actions. It should be possible to fix up both cases with the correct flowExecutionUrl.

March 28, 2011 | Unregistered CommenterLyndon

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>