On this page we will discuss more advanced integration issues for Spring Web Flow (SWF) in RSF in the context of the SWF "Sell Item" sample. This page is meant for those who are familiar with SWF and RSF. if you are new to either one you should start on the RSF-SpringWebFlow page.

Sample Code (SVN): original code (with maven2 build) RSF SWF version

Try out Sell Item RSF SWF on our server

Porting the SWF SellItem Sample Application#

Advanced issues#

Having dealt with the basic mechanics and concepts of RSF-SWF in the above two pages, on this page we will consider some more advanced issues.
  • RSF Courtesies to SWF Developers - two slightly different behaviours of RSF-SWF apps to plain SWF apps by default, that arguably result in a better user experience. These are automatic error redirects, and refreshable flow end states.
  • Binding states within flows - "JSF-style" flows such as those in SellItem-JSF must omit binding steps since these are handled by the framework. RSF-SWF supports these kinds of flows as well as the JSP/SpringMVC style where the binding is performed by Spring Web Flow itself.

Here we will just look at some of the more interesting application points which the SellItem app specifically brings up, which are mainly related to form bindings and submissions.

Form submission bindings in RSF-SWF#

RSF supplies a standard framework bean SWFBindingBean which acts as a proxy for RSF's EL bindings for "whichever bean is the current submission target in the current SWF flow". By selecting the SWFBindingBean, we pass incoming request data through SWF's (Spring Web) binding system, typically based around the FormAction class, as well as any outgoing data for rendering passing through the PropertyEditor system on the way out.

In the phonebook sample, we rendered a simple form with two fields as follows, in our producer:

    UIForm form = UIForm.make(tofill, "search-form");
    UIInput.make(form, "first-name", "SWFBindingBean.firstName");
    UIInput.make(form, "last-name", "SWFBindingBean.lastName");
    UICommand.make(form, "submit", "SWFEventBean.search");

In the SellItem sample, things are more varied. For user input, there is little choice, since we need to pass through SWF bindings. However, for output, sometimes the JSP views pass through the binding system using a tag such as the one below (from costOverview.jsp:

<spring:bind path="sale.shipDate">
  ${status.value}
</spring:bind>

At other times, the flow-scoped bean value is addressed directly, like this:

<td>Type:</td>
<td>${sale.shippingType}</td>

This is a prime example of coupling between the template structure and application logic, since in this case, the reason for the difference is that the developer knows that the date property has to be converted for rendering using the CustomDateEditor we registered, whereas the shippingType property requires no conversion.

However, since we are porting this app completely faithfully, the RSF version needs to reflect this difference in binding behaviour. In the RSF version, it is no longer visible at the templating layer, but is visible in the choice of bindings we issue in the producer. The above two JSP tags are both rendered the same in HTML, as a straight <td> tag, but in the producer CostOverviewProducer.java, they are rendered respectively as

  UIOutput.make(tofill, "sale-shippingType", null, "sale.shippingType");
  UIOutput.make(tofill, "sale-shipDate", null, "SWFBindingBean.shipDate");

This distinction is actually less intrusive at the code level than it was in the original JSP, since it just amounts to a different binding path in a String. Other than this, there are no real extra issues raised within the code of our port of SellItem. It is filled with UIForms, UIInternalLinks and UISelects bound to EL paths, like any other RSF app - all the SWF flow definitions "just work". However, there is a new issue raised by the behaviour of the app, particular as the flow ends in the costOverview view - this is discussed towards the bottom of the page in the section on "courtesies".

This binding issue is much more interesting as we now turn to porting the JSF version of this SellItem sample, "SellItem-JSF", directly to RSF-SWF - that is, we will remove JSF as the view technology for this sample, and port its template and flow definition to an RSF-SWF app like the one we have just looked at. Interestingly, the app behaves almost identically to SellItem-RSF-SWF, but it is not written quite the same way.

Porting SellItem-JSF#

Our port to RSF-SWF is held in SVN at SWFSellItem-JSF-RSFSWF

The crucial difference between SellItem and SellItem-JSF lies in the flow definition.

In the JSF environment, to be a first-class framework citizen, we must use the JSF standard binding and validation phases for processing incoming form inputs through the JSF component tree. Therefore we cannot use the standard SWF FormAction system for data binding. For example, the first flow state in the "simple" JSP version of the SellItem flow sellitem-simple-flow.xml looks like this:

<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
  <render-actions>
    <action bean="formAction" method="setupForm"/>
  </render-actions>	
  <transition on="submit" to="enterCategory">
    <action bean="formAction" method="bindAndValidate">
      <attribute name="validatorMethod" value="validatePriceAndItemCount"/>
    </action>
  </transition>
</view-state>

However, the initial state in the JSF flow is much more simple, since the data binding must be handled by the JSF framework itself:

<view-state id="enterPriceAndItemCount" view="/priceAndItemCountForm.jsp">
  <transition on="submit" to="enterCategory"/>
</view-state>

RSF also allows flows of this sort to be hosted directly in RSF-SWF without changes. Note that to start with, RSF-SWF will automatically strip off the leading / and any file extension from view names that it encounters in flow files in order to arrive at an RSF ViewID.

Our main task here is translate each of the SWF environmental elements into their RSF equivalents. For the most part, these simply translate into swapping the SWFBindingBean prefix on each of our input elements to use sale - this will direct RSF's data binding apparatus to process the incoming submissions, rather than SWF's FormAction. However, there are two special application elements we will need to handle separately - the validator, used for checking the price and item count greater than 0, and the property editor used for converting the shipping date field to and from textual form.

ImageHolder/swfsellitem-jsf-rsf.png

Starting with a POST#

One detail we have not seen in our other sample apps is how to start a flow with a POST submission rather than a GET link. This is fairly straightforward and follows the standard RSF pattern of a UICommand together with a NavigationCase rule - here is the code from IndexProducer:
 public void fillComponents(UIContainer tofill, ViewParameters viewparams,
      ComponentChecker checker) {
    // the original two ways of launching the JSF flow - via POST or GET
    UIForm form = UIForm.make(tofill, "jsf-launch-form");
    UICommand.make(form, "submit");
    UIInternalLink.make(tofill, "sellitem-jsf-flow-link", 
      new SWFLaunchViewParams("sellitem-jsf-flow"));
  }

  public List<NavigationCase> reportNavigationCases() {
    List<NavigationCase> togo = new ArrayList<NavigationCase>();
    togo.add(new NavigationCase(new SWFLaunchViewParams("sellitem-jsf-flow")));
    return togo;
  }

Actually though we "say" that the JSF view would start with a link, since all JSF transitions are in fact POSTs, this flexibility was a bit illusory. In the RSF version the transitions genuinely are GET and POST respectively, although by SWF requirements, both of these flow starts would be immediately followed by a client redirect.

Validator#

In the JSF version of this app, rather than the self-contained Spring Validator, the validation rule was written in a fairly "ad hoc" way into the template itself:
  <h:inputText id="price" value="#{sale.price}" required="true">
    <f:validateDoubleRange minimum="0.01"/>
  </h:inputText>
  <h:inputText id="itemCount" value="#{sale.itemCount}" required="true">
    <f:validateLongRange minimum="1"/>
  </h:inputText>

The good news in RSF-SWF is that we can use the original Spring validator from the non-JSF version of the app directly. Since it is not part of the flow definition now, however, we must register it with RSF against the request context in general rather than acting as part of the flow. This is done in RSF via a BeanGuard declaration, an application scope Spring definition that specifies a declarative rule to be triggered on any data access matching a path rule:

  <bean parent="writeGuardParent">
    <property name="guard">
      <bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/>
    </property>
    <property name="guardedPath" value="sale"/>
  </bean>

Note that the *location* of the guarded path itself is still determined within the flow - sale is still a flow-scoped variable, even though SWF has handed off all data conversion tasks to the outside framework. From sellitem-jsf-flow.xml:

<var name="sale" class="org.springframework.webflow.samples.sellitem.Sale" 
       scope="conversation"/>

Note that to free up this EL path for access over the request by RSF form submissions, we must explicitly declare it as request addressible:

  <bean parent="requestAddressibleParent">
    <property name="value" value="sale"/>
  </bean>

Date converter#

Again, since RSF is so closely aligned with Spring web fundamentals, we can also use the original CustomDateEditor converter that the non-JSF version of SellItem used. Very similarly to the validator, the converter is registered as a declarative rule against the context in general rather than scoped to a flow:
  <bean class="uk.org.ponder.mapping.DataConverter">
    <property name="targetPath" value="sale.shipDate" />
    <property name="converter">
      <bean class="org.springframework.webflow.samples.sellitem.rsf.DateEditorFactory" />
    </property>
  </bean>

This specifies that any read or write access (via EL) to the path sale.shipDate in the context must first pass through our Date converter. Again, whilst we can use the same converter, we can't actually use the Spring packaging via the PropertyEditorRegistrar system, since the requirements of the RSF environment and form submission process are slightly different. Instead we must deliver the PropertyEditor versus an RSF (or rather PUC) PropertyEditorFactory:

public class DateEditorFactory implements PropertyEditorFactory {
  public PropertyEditor getPropertyEditor() {
    return new CustomDateEditor(new SimpleDateFormat("dd/MM/yyyy"), true);
  }
}

Since this factory is a standard Spring bean, it would be easy to transfer PropertyEditors from another part of the context, if there were a centralised pool of them, by injection. As well as javax.beans PropertyEditors, other options for data conversion within RSF include LeafObjectParser, DARReshaper and BeanResolver, as well as the use of Transit beans for more complex data transformation requirements (in general, where the conversion multiplicity is not 1-1, but for example where we are focusing 3 values onto 1, or splitting 1 into 3, as you might for a field-based date control). An even better way of managing this date input within RSF is use of the standard date widget, part of the RSFComponents package - this is presented a little on the BuildingRSFComponents page. The date widget is fully internationalised, injecting the server-side locale down to the client side, as well as being vetted for accessibility and usability, shortly to become shepherded by the Fluid UI Project. This widget would be usable equally in an RSF-SWF, RSF-"JSF"-SWF or straight RSF app just with a change of EL bindings.

The only behavioural difference between the original SWF-JSF sample and the SWF-SpringMVC-JSP is that in the original JSF version, the date input field is left out entirely.

The SellItem-JSF-SWFRSF and SellItem-SWFRSF apps behave identically from the user perspective in our ports.

Further universal portability#

Inside SellItem-JSF-RSFSWF there is some "demonstration" code to illustrate a strategy that could be used for housing both of the kinds of SWF flows for SellItem (both "straight" and "JSF" versions) within the same app, and targetting them at the same set of RSF producers. This code is currently inactivated for clarity of explanation. It consists of a core factory bean, "BindingComputer", that introspects into the currently executing flow, and returns the binding base String SWFBindingBean if it is one of the straight flows, else sale for the JSF flow. This String is transferred to a request-scope variable via a factory-method, and could then be injected "in bulk" into all the producers by deriving them from a Spring parent definition:
  <bean id="bindingComputer" 
      class="org.springframework.webflow.samples.sellitem.rsf.BindingComputer">
    <property name="viewParameters" ref="parsedViewParameters"/>
    <property name="flowExecutor" ref="flowExecutor" />
    <property name="externalContext" ref="SWFExternalContext"/>
  </bean>
  
  <bean id="bindingBase" factory-bean="bindingComputer" factory-method="getBindingBase"/>
  
  <bean abstract="true" id="producerBase" 
      class="org.springframework.webflow.samples.sellitem.rsf.producers.ProducerBase">
    <property name="bindingBase" ref="bindingBase"/>
  </bean>

ProducerBase then makes available convenient utility methods for producer code getBinding and makeSaleOutputs which automatically targets its products against the appropriate path for the current flow, depending on whether the SWF or RSF binding system is required. This is a little esoteric, but demonstrates the enormous power of Spring-style development to deliver portable, environment-independent code, which can be fully realised within RSF. Binding parameterisation is a powerful means for gaining reusability for RSF producers, by enabling them to be targetted at any variety of bean targets.

RSF Courtesies to SWF Developers#

After playing around with the RSF-SWF versions of these samples, you may notice that although the application behaviour is essentially identical, there are a couple of differences in behaviour, primarily in error cases.

Automatic error redirects #

The most obvious behaviour is on entering an expired or invalid flow URL, either by using the back button or by entering an incomplete or invalid flowId/Token. The default behaviour on JSP/SpringMVC is to render a stack trace to the browser, whereas the default RSF behaviour, under the Level1Error system, is to redirect to the default view. In both frameworks these defaults are easy to configure differently, but it is arguable that the RSF behaviour is more useful and pleasant to users "out of the box". This is a general RSF default and not specific to the RSF-SWF integration.

Refreshable end flow view#

A more subtle issue is to do with the URL and server environment as a SWF flow ends. By default, the SWF flow end is somewhat brutally abrupt - the flow technically ends partway through the processing of the final POST request, and in the Sell Item application, for example, if we require to render data which depends on flow variables, we must rely on these being "resurrected" very briefly out of the flow by a FormAction setupForm action happening on entry to the end flow state. By the time rendering occurs, the flow itself is already expired and all flow variables deallocated.
<end-state id="finish" view="costOverview">
	<entry-actions>
		<action bean="formAction" method="setupForm"/>
	</entry-actions>
</end-state>

There is not even time to perform a standard "POST-to-GET" redirect on this step.

The result of this behaviour is that if the user hits the refresh button on this final view, they will receive an error message rather than the page refresh they are expecting. This could be undesirably brittle, especially considering the fact that a network error or some other failure may prevent the user seeing the initial rendering attempt at the end flow state.

By default, RSF provides more robust behaviour on flow end, at the cost of a little server state - an RSF "flow end state" is glued on to the end of an SWF flow end, to provide a small URL environment where the view may be refreshed by the user for a while, even whilst the flow proper has expired.

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-) was last changed on 10-Sep-2008 21:35 by UnknownAuthor