Wednesday, April 8, 2015

Spring Web Flow Basics

What is Web Flow?

A webflow is fundamentally a navigation graph in a web application. A webflow is roughly defined by a set of nodes or states - usually view states - and a set of transitions between states triggered by the user's GUI events.
 While the "default" state-management model of web applications is state-less, webflows are a stateful implementation. They ensure that the user follows a meaningful navigational path. They are thus a simpler way of implementing multiiple if-elses.

Key features

  • Implements web navigation stateful "flows"
  • Integration with Spring @MVC Framework and JSF
  • Check that users follow the meaningful navigation paths
  • Manages multiple windows issues by supporting multiple simulataneous webflow context inside the same session context
  • Provides scopes beyond request and session (conversation scope)
  • Addresses the double-submit problem elegantly
  • Give a consistent and expected "semantics" to the browser back-button
  • Use of declarative XML domain-specific language (Flow DSL) to define web-flows as (deterministic finite) state-transaction diagram or graph, where:
    • Graph states are mapped web pages or decision nodes
    • transitions between states are triggered by user actions (typically involving form actions)

Components of a webflow

  • Views or view-state: 

    • defines a step in the flow where a view is rendered to the user.
    • Should have a unique id
    • May have a view page (jsp or another) mapped. If not mapped then the id itself is the view page
    • Logical names are supported using ViewResolver (question)
    • Waits for user event to proceed
    • View may be bound to a model which can be used in forms
  • State Transitions:

    • Each state can have one or more transitions.
    • Must specify the event that will trigger the transition and the target state.
    • View Events: HTML buttons or HTML input types should specify the event name in the format "_eventId_proceed" where proceed is the event name.
  • Actions:

    •  Actions allow us to invoke business logic from within the flow.
       evaluate: Used to evaluate an expression and assign the result to scoped variable. <evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />
       The result type can be cast before it is assigned by specifying the type.
    • Actions can also be triggered in state transitions in which case the expression is evaluated before the transition occurs.
  • Actions-states:

    • Evaluate expressions and transit to other states based on the decision.
    • Some action execution examples:
      • on-start: when the flow starts
      • on-end: when the flow ends
      • on-entry: on entering a view state
      • on-exit: on exiting a view state
      • on-render: before the view is rendered
  • Decision-states:

    • Similar to action-states but used for boolean result based transition. 
      <if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
  • End-state:

    • When a flow transitions to an end-state it terminates and the outcome is processed or returned.

Scoped Variables

Each variable has a scope and value. When the scope is exited the variable is discarded and is no more accessible. If the scope is re-entered, a new instance of the variable is created.
ScopeScope VariableDescriptionSearch Order
ConversationconversationScopecurrent flow and all sub-flows5Stored in the HTTP Session.
FlowflowScopecurrent flow4
FlashflashScopecurrent view and next view2
ViewviewScopecurrent view (including refreshes)3Can be initialised before the view is rendered using <on-render>.
Render actions are executed on initial and all subsequent renders.
RequestrequestScopecurrent HTTP request1
If the variable type is a user defined java type, then When a variable is instantiated, any @Autowired transient references the variable type holds are (re)wired.
While assigning values to a new variable (in a statement like evaluate) it is mandatory to mention the scope variable. However, when assigning value to an existing variable (like in set) or while reading the values it is not required. Variables are searched in the order mentioned above.
All variables except requestScope need to be Serializable.

Predefined Variables

Apart from the above scoped variables, there are other implicit variables which can be referenced from within a flow. These include requestParameters, currentEvent, currentUser etc.

Input/Output

We can pass parameters to and from the flow using these parameters.
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />
This creates an input variable with name 'hotelId' and since type 'long' is specified a type conversion to long will be attempted. value is used for assigning an expression value to the variable.
<output name="confirmationNumber" value="booking.confirmationNumber" />
Output variable is used with end-state and can be assigned a value from expression. If no value is used, then the variable matching the name field will be used. This output value of the variable will be passed out of the web flow.
These variables are extremely useful in sub-flows. A flow can invoke a sub flow and wait for it to return. Then based on the value returned from the sub-flow, we can transition to the required states. The output of a sub flow can be checked in the outer flow directly by the variable name.

Rendering Views

  • Global transitions: These will apply to all views in the flow.
  • Event handlers: These transitions do not have targets. They just handle an event and render the same view. They may also render partial fragments of the view. <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="searchResultsFragment" /> </transition
  • A view can be shown as a popup by <view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
  • To stop the user from using the back button on browser from going to previous view, use <transition on="confirm" to="bookingConfirmed" history="discard">. Similary, history="invalidate" will discard all previous views. 

Data Binding

A view can be bound to a model for use in forms. This model may be initialised before the view is rendered.Before transition occurs, the model is bound to all request parameters. If the binding fails, the same page is re-rendered. 
The binding may be explicitly disabled for events like cancel by setting bind="false". Some properties may be marked as mandatory with required="true".
<view-state id="enterBookingDetails" model="booking">
 <binder> 
 <binding property="checkindate" converter="dateConverter" required="true"/> 
 </binder> 
 <transition on="proceed" to="reviewBooking" /> 
 <transition on="cancel" to="cancel" bind="false" />  
/view-state>
If there is an error in binding a property, for example typeMismatch then the flow will look for the message key booking.checkindate.typeMismatch and typeMismatch (in that order).

Model Validation 

  • Method1: Implementing a model validate method:
In the model, implement the validate${state} method where state is the name of the view-state. Thus inside the model itself we can have multiple validation  methods which will be invoked from respective views.
  • Method2: Implementing a Validator
Implement a validator ${modelName}Validator with validate method. Wherever this model is used, this validator will be invoked before a transition.
This validator can also contain validation methods for each individual state following the convention validate${state}
  • Validation can be supressed by validate="false" in the transition.

Flow Executor: Persistence and Security

The can be handled by mapping the correct listener to the flowExecutor. In our system, this has been done like this:
<!-- Executes flows: the entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="securityFlowExecutionListener" />
<webflow:listener ref="hibernateFlowExecutionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="hibernateFlowExecutionListener" class="org.etive.hibernate4.Hibernate4FlowExecutionListener">
<constructor-arg ref="sessionFactory" />
<constructor-arg ref="transactionManager" />
</bean>
<bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
The hibernate flow listener handles the persistence context, while the security flow listener ensures that the correct role is assigned to the user before allowing access to the flow. This is ensured by adding this to the flow:
<secured attributes="ROLE_ANONYMOUS,ROLE_USER,ROLE_ADMIN" match="any" />

Flow Registry

For a flow to be found, it must be registered with the flow registry. We may define a flow registry to register all flows in one place. This enables us to mention values like base-path which is where all flows will be found, flow-location-pattern which is a pattern for locating all flows etc.

References

  1. http://docs.spring.io/spring-webflow/docs/2.4.0.RELEASE/reference/html/index.html
  2. http://jpalace.org/document/35/spring-webflow-tutorial