Spring Web Flow is a form of "universal" application definition, whereby the application's structure becomes freed not only from a dependence on a particular web technology, but also on a particular delivery environment and even, in theory, programming language.
As of an early release (0.5) RSF featured a "lightweight" flow system modelled on a small subset of SWF, largely for demonstration purposes - this demonstrated the standard SWF "Number Guessing" sample running as an RSF application both in Servlets and Portlets. This demonstrated a somewhat "flatter" semantic on flows by allowing "back-transitions" in the flow without the expense of continuations.
In the intervening years, SWF and RSF both moved on considerably from their respective positions, and SWF is an even more attractive target for RSF integration than it was then. As of RSF version 0.7.2, RSF features a full-scale integration with SWF, in which an SWF application may be transparently "embedded" in the RSF environment, with full interoperability with RSF templates, EL and navigation structures, as well as creating an indistinguishable environment on the client-side, to any Javascript or DHTML definitions.
Since SWF is based strongly on the Spring framework on which RSF is also based, there is a general harmonisation of programming idioms and aims. This page provides a central reference in converting SWF applications and structures to RSF. The best starting point for a general tutorial is to follow the port of the standard Spring Web Flow Phonebook sample, and for more advanced issues the SellItem Samples.
SWF in RSF#
The SWF environment is presented within RSF as
- A set of standard "binding beans", SWFBindingBean, SWFEventBean and SWFLaunchBean, which allow SWF operations to be triggered by means of RSF EL expressions] on Action cycles
- A hierarchy of standard ViewParameters objects, rooted at the base class SWFViewParams, which allow SWF operations via render cycles.
- A set of framework-specific beans, generally hidden from the user, which coordinate i) the visibility of SWF flow-managed beans within RSF's request scope, and ii) the transfer and harmonisation of error messages and error states within the flow. In particular:
- All SWF beans visible at any of its flow scopes (in general, everything considered part of the "model" considered in SpringMVC terms) is both addressible by RSF EL and injectable from its request scope
- All binding and validation errors generated by the SWF system are transparently converted into the equivalent RSF TargettedMessageList list, suitable for distribution using the message-for: template syntax
Since the choice of passing bindings (form submissions) through the SWF system or handling it within RSF is simply a choice of binding target (SWFBindingBean or anything else), SWF-RSF applications can choose even on a bean-by-bean basis which binding system to use. This enables us to house both "standalone" SWF flows, like those in the basic SellItem sample, and "binding-free" flows, such as those in the JSF version of the same app, within the same integration, and even, if necessary, within the same application. This is discussed in more detail on the SellItem sample page.
Some examples#
Here are some examples of invoking typical SWF operations from the RSF-SWF environment.Starting a flow#
To launch a new flow (using a simple link), you can issue a component like this:UIInternalLink.make(tofill, "sellitem-flow-link", new SWFLaunchViewParams( "sellitem-flow"));The argument to the SWFLaunchViewParams is the ID of the flow you want launched.
Implementing a flow view state#
Within RSF-SWF, the label of an SWF view name simply corresponds to the viewID of an RSF view. So for a flow view state declared so:<view-state id="enterCategory" view="categoryForm">one would implement the target view within the RSF application as
public class CategoryFormProducer implements ViewComponentProducer { public final static String VIEW_ID = "categoryForm"; public String getViewID() { return VIEW_ID; }
Submitting form data through SWF#
To direct the submission of an RSF input component to a form object defined at the current flow action state, bind it to an EL path at the SWFBindingBean. Here is a little form from the phonebook:public void fillComponents(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker) { 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"); }This also demonstrates firing an SWF event named search - the corresponding flow state definition is
<view-state id="enterCriteria" view="searchCriteria"> <render-actions> <action bean="formAction" method="setupForm"/> </render-actions> <transition on="search" to="displayResults"> <action bean="formAction" method="bindAndValidate"/> </transition> </view-state>
Render and Action cycles - GET and POST#
One crucial web detail that is abstracted away by the Spring Web Flow system is the distinction between idempotent and non-idempotent requests. Whilst this has originated as an HTTP concept, the higher abstraction has become crucial in environments such as Portlets where render requests must be certainly re-issuable without disturbing application state. Spring Web Flow is careful to maintain the letter of the law, by always redirecting after a flow transition to a fully refreshable URL, and so every SWF app, like every RSF app, will work correctly in an environment where idempotency is crucial. However, RSF achieves this safety whilst maintaining the distinct abstraction of render and action cycles visible to the framework user, so we have a slight mismatch of idioms between the two frameworks. As well as having the two varieties of SWFViewParams for launching new flows, and firing events in existing ones, we must have the corresponding binding targets for Action cycles:
Render cycle (UILink, UIForm with RENDER_REQUEST) | Action cycle (UICommand, UIForm with ACTION_REQUEST) | |
---|---|---|
Launching a flow | new SWFLaunchViewParams(flowId, <params>) | new UICommand(..., "SWFLaunchBean.flowId") |
Firing an event | new SWFEventViewParams(eventId, <params>) | new UICommand(..., "SWFEventBean.eventId") |
To fire an SWF operation, then, using an action cycle, one can issue a method binding attached to a UICommand component, that will, after the form submission has been processed ("binding", in Spring Web terminology), cause navigation along the flow in just the same way we showed above using simple links