Ken Rimple heads Chariot Solutions' training and mentoring programs, and has developed and/or delivered courseware and seminars in a variety of technologies such as Maven, OSGi, Groovy, Grails and Spring. Throughout his career, Ken has always made it a priority to teach others what he has learned. Ken has served as the technical co-chair of both the Fall Forecast 2008 Cloud Computing Conference and the 2009 - 2012 Emerging Technologies for the Enterprise conferences. He hosts a popular podcast, the Chariot TechCast, and has led or participated in projects written in Java since Java 1.0.2. Ken taught the first Philadelphia-area Sun Introduction to Java course in the late 1990s. He is the co-author (along with Srini Penchikala) of Spring Roo in Action for Manning Publications. He is also an avid photographer and jazz drummer. Ken is a DZone MVB and is not an employee of DZone and has posted 35 posts at DZone. You can read more from them at their website. View Full User Profile

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

02.01.2011
| 9904 views |
  • submit to reddit

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>



References
Published at DZone with permission of Ken Rimple, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Thomas Kern replied on Thu, 2012/09/06 - 10:51am

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.

http://www.java-tips.org 

James Walker replied on Sun, 2012/10/14 - 6:55am

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. The McMinn Law Firm

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.