Over a million developers have joined DZone.

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

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

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>



Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Published at DZone with permission of Ken Rimple, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}