Add new attachment

Only authorized users are allowed to upload new attachments.

This page (revision-1) was last changed on 10-Sep-2008 21:35 by UnknownAuthor

Only authorized users are allowed to rename pages.

Only authorized users are allowed to delete pages.

Difference between version and

At line 1 added 215 lines
On this page we will discuss more advanced integration issues for [Spring Web Flow (SWF)|SpringWebFlow] 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)|https://source.caret.cam.ac.uk/rsf/projects/RSFSamples/trunk/SWFSellItem/orig] | [RSF SWF version|https://source.caret.cam.ac.uk/rsf/projects/RSFSamples/trunk/SWFSellItem/RSFSWF]
\\
[Try out Sell Item RSF SWF on our server|http://ponder.org.uk/SWFSellItem-RSFSWF/faces]
!! Porting the SWF SellItem Sample Application
* [Porting the SellItem application is covered on its own page|PortingSellItemSWFSampleToRSF], please read that page for information about the sample app and where to get the source that will be referred to below.
!! 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|PhonebookSWFSample], 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 [UIForm|PrimitiveComponents]s, UIInternalLinks and [UISelect]s 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|https://source.caret.cam.ac.uk/rsf/projects/RSFSamples/trunk/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.
%%(text-align:center)
[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|RequestWriteableBeans]:
{{{
<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|http://www.fluidproject.org/]. 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|Flows] 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.
Version Date Modified Size Author Changes ... Change note
10-Sep-2008 21:35 17.814 kB UnknownAuthor
« This page (revision-) was last changed on 10-Sep-2008 21:35 by UnknownAuthor