Integrating ZK and Spring Web Flow

From Documentation
Revision as of 04:16, 20 January 2022 by Hawk (talk | contribs) (correct highlight (via JWB))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
DocumentationSmall Talks2008DecemberIntegrating ZK and Spring Web Flow
Integrating ZK and Spring Web Flow

Author
Henri Chen, Principal Engineer, Potix Corporation
Date
December 3, 2008
Version
Applicable to ZK Spring Integragion Library 1.1RC (zkspring.jar) or later.
Applicable to ZK 3.5.2 and later.
Applicable to Spring Web Flow 2.0+


Introduction

This is the third article in a series regarding how to make Spring work with ZK Ajax framework. In the previous articles, this and this, we have discussed about how to secure ZK pages, ZK events, and Spring backend service methods using ZK and Spring Security 2.0 frameworks. In this article, we will demonstrate with an example the way to navigate a work flow with ZK, Spring Security, Spring MVC, and Spring Web Flow.

Spring Web Flow is the module of Spring for implementing flows. Basically, you define your Web page flows with a provided declarative flow definition language in XML configuration files. Then, per the definitions, Spring Web Flow engine transits the pages per the current page and user's action(e.g. when user press the next button). This article focus on illustrating the steps and ways how to configure the Web application and how to design the ZK pages such that ZK and Spring Web Flow work together seamlessly.


Demo

The Example

Same as in the previous articles, I borrowed the booking-faces sample(spring-webflow-2.0.3.RELEASE/projects/spring-webflow-samples/booking-faces/) provided by Spring Web Flow 2.0.3. It is a simple hotel searching and booking application. End users search hotels, pick one, and then book the hotel per the pre-defined work flow. The sample was implemented with JSF pages and I rewrote each JSF .xhtml page to a corresponding ZK .zul page and add necessary ZK libraries and configurations to make it work.


Deploy the war file

Download the zkspringwf.war file and deploy the war file to your servlet container. If you are using Tomcat 6.0, all you have to do is copy the war file to the webapps folder of the Tomcat server and then restart Tomcat. The zkspringwf.war war file shall deploy and create a new folder named zkspringwf (same name as the file) under the webapps. Now visit http://localhost:8080/zkspringwf/ and you shall see following page:

Zkspringwf-1.png

Configurations

Now lets start walking through the configurations:


/WEB-INF/zk.xml file: the ThreadLocal issue (IMPORTANT!)

The Spring Web Flow engine holds in the servlet thread several ThreadLocal variables for each request so the engine can refer it from time to time. These ThreadLocal variables contains important work flow related information and shall be accessible any time. However, ZK by default spawns a new event thread for each event handling job. That is, the ZK event thread will not have such important ThreadLocal variables and the original assumption is broken.

Therefore, remember always to disable the ZK event thread mechanism entirely when use ZK with Spring. This tells the ZK framework NOT to spawn a new event thread for event handling and everything shall back to it track. That is, configure the /WEB-INF/zk.xml file as follows:

<zk>
    ...
    <system-config>
        <disable-event-thread/>
    </system-config>
    ...
</zk>


/WEB-INF/web.xml

web.xml is the standard servlet configuration file. To make ZK and Spring works, you have to configure this file.

/WEB-INF/web.xml
<web-app ...>
    ...
    <!-- 
      - Spring configurations 
      -->
    <!-- The master configuration file -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/config/web-application-config.xml
        </param-value>
    </context-param>
    
    <!-- Enables Spring Security -->
     <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Loads the Spring web application context -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

      <!-- Serves static resource content from .jar files -->
    <servlet>
        <servlet-name>Resources Servlet</servlet-name>
        <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Resources Servlet</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>
    
    <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
    <servlet>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>

    <!-- 
      - ZK configurations 
      -->
    <listener>
        <description>
        Used to cleanup when a session is destroyed</description>
        <display-name>
        ZK Session Cleaner</display-name>
        <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
    </listener>

    <!-- ZK loder -->    
    <servlet>
        <description>
        ZK loader for ZUML pages</description>
        <servlet-name>zkLoader</servlet-name>
        <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
        <init-param>
            <param-name>update-uri</param-name>
            <param-value>/zkau</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>zkLoader</servlet-name>
        <url-pattern>*.zul</url-pattern>
    </servlet-mapping>
    
    <!-- ZK update engine -->    
    <servlet>
        <description>
        The asynchronous update engine for ZK</description>
        <servlet-name>auEngine</servlet-name>
        <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>auEngine</servlet-name>
        <url-pattern>/zkau/*</url-pattern>
    </servlet-mapping>

    <!-- Default welcome files -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>


  • Spring configuration:
    • The <context-param> tells Spring Framework context loader where to find the context parameter files.
    • The <listener> ContextLoaderListener defines the Spring Framework context loader that will load and parse the context parameter files.
    • The <filter> springSecurityFilterChain defines the entry servlet filter for Spring Security filter chains.
    • The <servlet> Resources Servlet defines the spring resource servlet which can be used to retrieve resource defined in library .jar files.
    • The <servlet> Spring MVC Dispatcher Servlet defines the entry servlet for Spring Web Flow. Any URL starts with /spring/* will go through the Spring Web Flow controller
The most important configuration might be the Spring MVC Dispatcher Servlet. It tell the servlet container that all URL that starts with /spring/* shall be served by this servlet. It is a dispatcher servlet so we will need extra configurations for Spring MVC and Spring Web Flow to bridge the work flow requests to real Spring Web Flow controller.
  • ZK Ajax framework configuration:
    • The <listener> HttpSessionListener is used to cleanup the session when it is destroyed.
    • The <servlet> zkLoader servlet is used to load a ZK page
    • The <servlet> auEngine servlet is used to update a ZK page (Ajax update)
This is a typical ZK application configuration. Nothing very special.


/WEB-INF/config/webmvc-config.xml

webmvc-config.xml is the additional configuration file specific to Spring MVC.

/WEB-INF/config/webmvc-config.xml
<beans ...>
    <!-- Maps request URIs to controllers -->            
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /main=flowController
                /booking=flowController
            </value>
        </property>
        <property name="defaultHandler">
            <!-- Selects view names to render based on the request URI: e.g. /main selects "main" -->    
            <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
        </property>
    </bean>

    <!-- Maps logical view names to real page URL (e.g. 'search' to '/WEB-INF/search.zul' -->
    <bean id="viewResolver" class="org.zkoss.spring.web.servlet.view.ZkResourceViewResolver">
        <property name="prefix" value="/WEB-INF/"/>
        <property name="suffix" value=".zul"/>
    </bean>
</beans>


  1. The <bean> SimpleUrlHandlerMapping works with Spring MVC will maps the request URI to real Controller. Here all the request URIs in the pattern /main and /booking would be dispatched to flowController(which is defined in webflow-config.xml configuration file. We will discuss it later).
  2. The <bean> viewResolver maps the logical view name returned by controller to real page URL. Note here the name viewResolver has to be given as is.


/WEB-INF/config/webflow-config.xml

webflow-config.xml is the configuration file specific to Spring Web Flow.

/WEB-INF/config/webflow-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:webflow="http://www.springframework.org/schema/webflow-config"
    xmlns:zksp="http://www.zkoss.org/2008/zkspring"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/webflow-config
       http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
       http://www.zkoss.org/2008/zkspring
       http://www.zkoss.org/2008/zkspring/zkspring.xsd">

    <!-- Executes flows: the central entry point into the Spring Web Flow system -->
    <webflow:flow-executor id="flowExecutor">
        <webflow:flow-execution-listeners>
            <webflow:listener ref="jpaFlowExecutionListener" />
            <webflow:listener ref="securityFlowExecutionListener" />
        </webflow:flow-execution-listeners>
    </webflow:flow-executor>
    <!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
    <bean id="jpaFlowExecutionListener"
        class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
        <constructor-arg ref="entityManagerFactory" />
        <constructor-arg ref="transactionManager" />
    </bean>
    <!-- Installs a listener to apply Spring Security authorities -->
    <bean id="securityFlowExecutionListener"
        class="org.springframework.webflow.security.SecurityFlowExecutionListener" />

    <!-- The registry of executable flow definitions -->
    <webflow:flow-registry id="flowRegistry"
        flow-builder-services="zkFlowBuilderServices">
        <webflow:flow-location path="/WEB-INF/flows/main/main.xml" />
        <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
    </webflow:flow-registry>

        
    <!-- Configures the ZK Spring Web Flow integration -->
    <zksp:flow-controller id="flowController" flow-executor="flowExecutor"/>
    <zksp:flow-builder-services id="zkFlowBuilderServices" />
</beans>


  • Spring <webflow:> configuration(in red):
    • The flowExecutor bean is the central entry point into the Spring Web Flow system. It adds two work flow listeners to handle the JPA data persistence and web flow security protection.
    • The flowRegistry bean is the registry of work flow definitions. It tells the Spring Web Flow system where to find the flow definition file. Also, it specifies which flow-builder-service to use for these work flow definitions. Note here it uses zkFlowBuilderServices.
  • ZK Spring <zksp:> configuration(in blue):
    • The flowController bean is the bridge between Spring MVC and ZK Spring Web Flow (flowExecutor). Note the name flowController has to be given as is.
    • The zkFlowBuilderServices bean is where those real web flow services are (expression parsing, view resolving, type conversion, etc.)
ZK adopts the Spring namespace configuration mechanism so all you need to do to enable the ZK Spring Web Flow Integration is by specifying these two beans.


Define a work flow

After finishing application configuration, it is time to define the work flow.

/WEB-INF/flows/main/main.xml
<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">
    
    <var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" />
    
    <view-state id="enterSearchCriteria">
        <on-render>
            <evaluate expression="bookingService.findBookings(currentUser.name)" result="viewScope.bookings" />
        </on-render>
        <transition on="search" to="reviewHotels">
            <evaluate expression="searchCriteria.resetPage()" />
        </transition>
        <transition on="cancelBooking">
            <evaluate expression="bookingService.cancelBooking(componentScope.booking)" />
            <render fragments="bookingsFragment" />
        </transition>
    </view-state>
    
    <view-state id="reviewHotels">
        <on-render>
            <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
        </on-render>
        <transition on="sort">
            <set name="searchCriteria.sortBy" value="requestParameters.sortBy" />
            <render fragments="searchResultsFragment" />
        </transition>
        <transition on="previous">
            <evaluate expression="searchCriteria.previousPage()" />
            <render fragments="searchResultsFragment" />
        </transition>
        <transition on="next">
            <evaluate expression="searchCriteria.nextPage()" />
            <render fragments="searchResultsFragment" />
        </transition>
        <transition on="select" to="reviewHotel">
            <set name="flowScope.hotel" value="self.attributes.hotel" />
        </transition>
        <transition on="changeSearch" to="changeSearchCriteria" />
    </view-state>
    
    <view-state id="reviewHotel">
        <transition on="book" to="bookHotel" />
        <transition on="cancel" to="enterSearchCriteria" />
    </view-state>

    <subflow-state id="bookHotel" subflow="booking">
        <input name="hotelId" value="hotel.id" />
        <transition on="bookingConfirmed" to="finish" />
        <transition on="bookingCancelled" to="enterSearchCriteria" />
    </subflow-state>

    <view-state id="changeSearchCriteria" view="enterSearchCriteria" popup="true">
        <on-entry>
            <render fragments="hotelSearchFragment" />
        </on-entry>
        <transition on="search" to="reviewHotels">
            <evaluate expression="searchCriteria.resetPage()"/>
        </transition>
    </view-state>
            
    <end-state id="finish" />
    
</flow>

A work flow is defined in a separate XML configuration file. A work flow basically is composed of states. Inside each state, we can defined transition to tell the flow engine which state to go on some predefined action event. Also, you can do some evaluate in the life cycle of the work flow. However, I am not going to explain every tag used in this work flow definition file. I will focus on those tags that related to the mapping web pages. If you need to know more details, you can download the spring-webflow-reference.pdf from the Spring website.

  • <view-state>: define the view state. Each view-state is mapped to a real web page. The view resolver configured in zkFlowBuilderServices bean(configured in webflow-config.xml) is used to resolve the view-state id into a Spring MVC view name
  • <transition>: define the transition condition. The attribute on=action-event" tells when to trigger a flow transition, and the optional attribute to="target-state" tells which target state to go when the specified transition action event is fired. If attribute to is omitted, flow engine will keep in this state. In such case, there shall be generally at least an <evaluate> tag is specified. As for how to fire a transition action event in the zul page, I will explain later with an example zul page.
  • <evaluate:>: define the evaluation expression. Each <evaluate> tag request to evaluate the specified expression as defined in attribute expression. These expression can be evaluated as the Unified Epression Language(EL). Note that you can use all ZK implicit objects and accessible variables here including self, event, desktop, page, and so on. That is, you can write ZK page controll code in the flow definition file if you like.


Define an associated view-state page

Here we discuss the associatation between a zul page and a view state.

/WEB-INF/flows/main/enterSearchCriteria.zul
<?page title="ZK Spring: Hotel Booking Sample Application" complete="true" ?>
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/WEB-INF/layouts/standard.zul"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" arg0="./hotelSearch"?>
<?variable-resolver class="org.zkoss.spring.DelegatingVariableResolver"?>
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
<zk:zk     xmlns="http://www.zkoss.org/2005/zk/native"       
    xmlns:zul="http://www.zkoss.org/2005/zul"
    xmlns:zk="http://www.zkoss.org/2005/zk">
    <zul:div id="hotelSearch" class="section" self="@{define(content) fragment(hotelSearchFragment)}">
        <span class="errors">
           <!-- <h:messages globalOnly="true" />  -->
        </span>
        <h2>Search Hotels</h2>
        <form id="mainForm">
            <fieldset>
                <div class="field">
                    <div class="label">
                        Search String:
                    </div>
                    <div class="input">
                        <zul:textbox id="searchString" value="@{searchCriteria.searchString}"
                            tooltiptext="Search hotels by name, address, city, or zip."/>
                    </div>
                </div>
                <div class="field">
                    <div class="label">
                        Maximum results:
                    </div>
                    <div class="input">
                        <zul:listbox id="pageSize" rows="1" mold="select" selectedItem="@{searchCriteria.pageSize}">
                            <zul:listitem forEach="" value="0" label=""/>
                        </zul:listbox>
                    </div>
                </div>
                <div class="buttonGroup">
                    <zul:button self="@{action(search)}" label="Find Hotels" onClick=""/>
                </div>
            </fieldset>
        </form>
    </zul:div>
    ...
</zk:zk>

Per the file name, you might have guessed this example view state page is associated to the view state enterSearchCriteria. Spring Work Flow maps a view state to a view state page URL by name convention. It is a combination of the view state id and the URL path of the work flow definition file.

Following is some important notes regarding a ZK view state page:

  • The <?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ...?> is required. ZK Spring Web Flow Integration assumes it. Basically, any settable work flow variables shall be specified as annotated data binding. E.g. the @{searchCriteria.searchString} and the @{searchCriteria.pageSize} in this example page.
  • The <?variable-resolver class="org.zkoss.spring.DelegatingVariableResolver"?> is a must. ZK rendering engine and data binder use this resolver to resove work flow variables defined in the work flow configuration file.
  • The <zul:button self="@{action(search)}" .../> is how you trigger a view state transition. Simply specify a component action annotation in the form of @{action(action-event)} and you are done; where the action-event is the transition action event name. That is, in this example, when an end user click this button, it tells Spring Web Flow engine that a transition action event search is fired for the current view state. Per the transition definition, <transition on="search" to="reviewHotels">, the Spring Web Flow engine will transit view state page from current enterSearchCriteria view state to the reviewHotels view state.


Summary

The Spring Web Flow apparently is designed with page based navigation in mind. It provides some minor Ajax effects (e.g. Popup and Fragment) and that is all. Then what role can a rich Ajax framework like ZK play? A good practice might be that doing fine grain Ajax operations in a page with ZK event programming whilst doing coarse work flow page transitions with Spring Web Flow definition. However, you can also define <transition> without the to attribute and make it doing <evalute> only so it would work like a pure ZK event listener. It is all up to you and your applications' requirement.

We now have made Spring Web Flow work with ZK. The next step shall be focused on enabling injection of ZK components into Spring beans. Integrating different frameworks together has not been an easy job and I might miss something. We welcome your feedback and suggestions so we can make ZK Spring Integration better.


Download

Download the example codes(.war file).




Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.