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 serveral 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 accessiable 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 entirly when use ZK with Spring. This tells the ZK framework NOT to spawn a new event thread for event handling and everyting 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 Secuirty 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.
- The <listener>
/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>
- 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 inwebflow-config.xml
configuration file. We will discuss it later). - 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 whichflow-builder-service
to use for these work flow definitions. Note here it uses zkFlowBuilderServices.
- The
- 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.
- The
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 componentaction
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.