Spring Web Flow provides a JSF integration that simplifies using JSF with Spring. It lets you use the JSF UI Component Model with Spring MVC and Spring Web Flow controllers. Along with the JSF integration Spring Web Flow provides a small Facelets component library (called Spring Faces) for use in JSF 1.2 environments and a Spring Security tag library for use in both JSF 1.2 and JSF 2.0 environments (see Section 13.11, “Using the Spring Security Facelets Tag Library” for more details).
Starting with version 2.2 the JSF integration in Web Flow supports
JSF 2.0 including Sun Mojarra and Apache MyFaces runtime environments.
Please, note however that JSF 2 partial state saving is not yet supported
with Apache MyFaces and needs to be disabled with the
javax.faces.PARTIAL_STATE_SAVING
context parameter in
web.xml
.
Also note that the Spring Faces component library, which provides
Ajax and client-side validation capabilities is for JSF 1.2 environments
only and will not be upgraded to JSF 2.0. Applications are encouraged to
use 3rd party JSF 2 component libraries such as PrimeFaces and RichFaces.
The swf-booking-faces
sample in the Spring Web Flow
distribution for example is built with JSF 2 and PrimeFaces
components.
Spring Web Flow also supports using JSF in a portlet environment. Spring Web Flow's portlet integration supports Portlets API 2.0 and JSF 1.2 only. Currently JSF 2 is not supported in combination with portlets. See Chapter 14, Portlet Integration for more on Spring Web Flow's portlet integration.
Spring Web Flow complements the strengths of JSF, its component model, and provides more sophisticated state management and navigation. In addition you have the ability to use Spring MVC @Controller or flow definitions as controllers in the web layer.
JSF applications using Spring Web Flow applications gain benefits in the following areas:
Managed bean facility
Scope management
Event handling
Navigation
Modularization and packaging of views
Cleaner URLs
Model-level validation
Progressivy-enhancement sytle client-side validation
Progressive-enhancement style Ajax requests with partial page updates
Using these features significantly reduce the amount of configuration required in faces-config.xml. They provide a cleaner separation between the view and controller layers along with better modularization of application functionals. These features are detailed in the sections to follow. The majority of these features build on the flow definition language of Spring Web Flow. Therefore it is assumed that you have an understanding of the foundations presented in Chapter 3, Defining Flows.
The first step is to route requests to the
DispatcherServlet
in the web.xml
file. In this
example, we map all URLs that begin with /spring/
to the
servlet. The servlet needs to be configured. An init-param
is
used in the servlet to pass the contextConfigLocation
. This
is the location of the Spring configuration for your web
application.
<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>/WEB-INF/web-application-config.xml</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>
In order for JSF to bootstrap correctly, the
FacesServlet
must be configured in web.xml
as it
normally would even though you generally will not need to route requests
through it at all when using JSF with Spring Web Flow.
<!-- Just here so the JSF implementation can initialize, *not* used at runtime --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Just here so the JSF implementation can initialize --> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping>
The use of Facelets instead of JSP typically requires this in web.xml:
!-- Use JSF view templates saved as *.xhtml, for use with Facelets --> <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.xhtml</param-value> </context-param>
When using the JSF 1.2 Spring Faces component library, you also need to configure a servlet for serving CSS and JavaScript resources. This servlet must be mapped to /resources/* in order for the URL's rendered by the components to function correctly.
<!-- Serves static resource content from .jar files such as spring-faces.jar --> <servlet> <servlet-name>Resource Servlet</servlet-name> <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <!-- Map all /resources requests to the Resource Servlet for handling --> <servlet-mapping> <servlet-name>Resource Servlet</servlet-name> <url-pattern>/resources/*</url-pattern> </servlet-mapping>
For optimal page-loading performance use the Spring Faces
components includeStyles
and includeScripts
.
These components will eagerly load the necessary CSS stylesheets and
JavaScript files at the position they are placed in your JSF view
template. In accordance with the recommendations of the Yahoo
Performance Guildlines, these two tags should be placed in the
head
section of any page that uses the Spring Faces
components. For example:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jstl/core" xmlns:sf="http://www.springframework.org/tags/faces" contentType="text/html" encoding="UTF-8"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Hotel Booking Sample Application</title> <sf:includeStyles /> <sf:includeScripts /> <ui:insert name="headIncludes"/> </head> ... </html> </f:view>
This shows the opening of a typical Facelets XHTML layout template that uses these components to force the loading of the needed CSS and JavaScript resources at the ideal position.
The includeStyles
component includes the necessary
resources for the Dojo widget theme. By default, it includes the
resources for the "tundra" theme. An alternate theme may be selected by
setting the optional "theme" and "themePath" attributes on the
includeStyles
component. For example:
<sf:includeStyles themePath="/styles/" theme="foobar"/>
will try to load a CSS stylesheet at "/styles/foobar/foobar.css" using the Spring JavaScript ResourceServlet.
This section explains how to configure Web Flow with JSF. The next section provides more details specific to using Web Flow with JSF 2. The following is sample configuration for Web Flow and JSF:
<?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:faces="http://www.springframework.org/schema/faces" si:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.2.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="facesContextListener"/> </webflow:flow-execution-listeners> </webflow:flow-executor> <!-- The registry of executable flow definitions --> <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF"> <webflow:flow-location-pattern value="**/*-flow.xml" /> </webflow:flow-registry> <!-- Configures the Spring Web Flow JSF integration --> <faces:flow-builder-services id="flowBuilderServices" /> <!-- A listener maintain one FacesContext instance per Web Flow request. --> <bean id="facesContextListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener" /> </beans>
The main points are the installation of a
FlowFacesContextLifecycleListener
that manages a single
FacesContext for the duration of Web Flow request and the use of the
flow-builder-services
element from the faces
custom namespace to configure rendering for a JSF environment.
See the swf-booking-faces reference application in the distribution for a complete working example.
In a JSF 2 environment you'll also need this Spring MVC related configuration:
<?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:faces="http://www.springframework.org/schema/faces" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.2.xsd"> <faces:resources /> <bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter"> <property name="flowExecutor" ref="flowExecutor" /> </bean> </beans>
The resources
custom namespace element delegates JSF 2
resource requests to the JSF 2 resource API. The
JsfFlowHandlerAdapter
is a replacement for the
FlowHandlerAdapter
normally used with Web Flow. This adapter
initializes itself with a JsfAjaxHandler
instead of the
SpringJavaSciprtAjaxHandler
previously used with Spring Faces
components.
In JSF 1.2 you need to provide the below configuration in
faces-config.xml
in order to use Facelets. If you are using
JSP and not using the Spring Faces components, you do not need to add
anything to your faces-config.xml
<faces-config> <application> <!-- Enables Facelets --> <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> </application> </faces-config>
In JSF 2.0 your faces-config.xml should use the faces-config schema version 2.0. Also you should remove the FaceletViewHandler shown above (if it is present) as Facelets are now the default rendering technology in JSF 2.
<?xml version='1.0' encoding='UTF-8'?> <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0"> </faces-config>
When using JSF with Spring Web Flow you can completely replace the JSF managed bean facility with a combination of Web Flow managed variables and Spring managed beans. It gives you a good deal more control over the lifecycle of your managed objects with well-defined hooks for initialization and execution of your domain model. Additionally, since you are presumably already using Spring for your business layer, it reduces the conceptual overhead of having to maintain two different managed bean models.
In doing pure JSF development, you will quickly find that request scope is not long-lived enough for storing conversational model objects that drive complex event-driven views. In JSF 1.2 the only available option is to begin putting things into session scope, with the extra burden of needing to clean the objects up before progressing to another view or functional area of the application. What is really needed is a managed scope that is somewhere between request and session scope. JSF 2 provides flash and view scopes that can be accessed programmatically via UIViewRoot.getViewMap(). Spring Web Flow provides access to flash, view, flow, and conversation scopes. These scopes are seamlessly integrated through JSF variable resolvers and work the same in JSF 1.2 and in JSF 2.0 applications.
The easiest and most natural way to declare and manage the model is through the use of flow variables. You can declare these variables at the beginning of the flow:
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
and then reference this variable in one of the flow's JSF view templates through EL:
<h:inputText id="searchString" value="#{searchCriteria.searchString}"/>
Note that you do not need to prefix the variable with its scope when referencing it from the template (though you can do so if you need to be more specific). As with standard JSF beans, all available scopes will be searched for a matching variable, so you could change the scope of the variable in your flow definition without having to modify the EL expressions that reference it.
You can also define view instance variables that are scoped to the current view and get cleaned up automatically upon transitioning to another view. This is quite useful with JSF as views are often constructed to handle multiple in-page events across many requests before transitioning to another view.
To define a view instance variable, you can use the
var
element inside a view-state
definition:
<view-state id="enterSearchCriteria"> <var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/> </view-state>
Though defining autowired flow instance variables provides nice modularization and readability, occasions may arise where you want to utilize the other capabilities of the Spring container such as AOP. In these cases, you can define a bean in your Spring ApplicationContext and give it a specific web flow scope:
<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/>
The major difference with this approach is that the bean will not be fully initialized until it is first accessed via an EL expression. This sort of lazy instantiation via EL is quite similar to how JSF managed beans are typically allocated.
The need to initialize the model before view rendering (such as by loading persistent entities from a database) is quite common, but JSF by itself does not provide any convenient hooks for such initialization. The flow definition language provides a natural facility for this through its Actions . Spring Web Flow provides some extra conveniences for converting the outcome of an action into a JSF-specific data structure. For example:
<on-render> <evaluate expression="bookingService.findBookings(currentUser.name)" result="viewScope.bookings" result-type="dataModel" /> </on-render>
This will take the result of the
bookingService.findBookings
method an wrap it in a custom
JSF DataModel so that the list can be used in a standard JSF DataTable
component:
<h:dataTable id="bookings" styleClass="summary" value="#{bookings}" var="booking" rendered="#{bookings.rowCount > 0}"> <h:column> <f:facet name="header">Name</f:facet> #{booking.hotel.name} </h:column> <h:column> <f:facet name="header">Confirmation number</f:facet> #{booking.id} </h:column> <h:column> <f:facet name="header">Action</f:facet> <h:commandLink id="cancel" value="Cancel" action="cancelBooking" /> </h:column> </h:dataTable>
In the example above result-type="dataModel" results in the
wrapping of List<Booking> with custom
DataModel
type. The custom
DataModel
provides extra conveniences such as
being serializable for storage beyond request scope as well as access to
the currently selected row in EL expressions. For example, on postback
from a view where the action event was fired by a component within a
DataTable, you can take action on the selected row's model
instance:
<transition on="cancelBooking"> <evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" /> </transition>
Spring Web Flow provides two custom DataModel types:
OneSelectionTrackingListDataModel
and
ManySelectionTrackingListDataModel
. As the names
indicate they keep track of one or multiple selected rows. This is done
with the help of a
SelectionTrackingActionListener
listener, which
responds to JSF action events and invokes the appopriate methods on the
SelectinAware
data models to record the currently
clicked row.
To understand how this is configured, keep in mind the
FacesConversionService
registers a
DataModelConverter
against the alias "dataModel"
on startup. When result-type="dataModel" is used in a flow definition it
causes the DataModelConverter
to be used. The
converter then wraps the given List with an instance of
OneSelectionTrackingListDataModel
. To use the
ManySelectionTrackingListDataModel
you will need
to register your own custom converter.
Spring Web Flow allows you to handle JSF action events in a decoupled way, requiring no direct dependencies in your Java code on JSF API's. In fact, these events can often be handled completely in the flow definiton language without requiring any custom Java action code at all. This allows for a more agile development process since the artifacts being manipulated in wiring up events (JSF view templates and SWF flow definitions) are instantly refreshable without requiring a build and re-deploy of the whole application.
A simple but common case in JSF is the need to signal an event
that causes manipulation of the model in some way and then redisplays
the same view to reflect the changed state of the model. The flow
definition language has special support for this in the
transition
element.
A good example of this is a table of paged list results. Suppose
you want to be able to load and display only a portion of a large result
list, and allow the user to page through the results. The initial
view-state
definition to load and display the list would
be:
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> </view-state>
You construct a JSF DataTable that displays the current
hotels
list, and then place a "More Results" link below the
table:
<h:commandLink id="nextPageLink" value="More Results" action="next"/>
This commandLink signals a "next" event from its action attribute.
You can then handle the event by adding to the view-state
definition:
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> </transition> </view-state>
Here you handle the "next" event by incrementing the page count on
the searchCriteria instance. The on-render
action is then
called again with the updated criteria, which causes the next page of
results to be loaded into the DataModel. The same view is re-rendered
since there was no to
attribute on the
transition
element, and the changes in the model are
reflected in the view.
The next logical level beyond in-page events are events that require navigation to another view, with some manipulation of the model along the way. Achieving this with pure JSF would require adding a navigation rule to faces-config.xml and likely some intermediary Java code in a JSF managed bean (both tasks requiring a re-deploy). With the flow defintion language, you can handle such a case concisely in one place in a quite similar way to how in-page events are handled.
Continuing on with our use case of manipulating a paged list of
results, suppose we want each row in the displayed DataTable to contain
a link to a detail page for that row instance. You can add a column to
the table containing the following commandLink
component:
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>
This raises the "select" event which you can then handle by adding
another transition
element to the existing
view-state
:
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> </transition> <transition on="select" to="reviewHotel"> <set name="flowScope.hotel" value="hotels.selectedRow" /> </transition> </view-state>
Here the "select" event is handled by pushing the currently
selected hotel instance from the DataTable into flow scope, so that it
may be referenced by the "reviewHotel" view-state
.
JSF provides useful facilities for validating input at field-level before changes are applied to the model, but when you need to then perform more complex validation at the model-level after the updates have been applied, you are generally left with having to add more custom code to your JSF action methods in the managed bean. Validation of this sort is something that is generally a responsibility of the domain model itself, but it is difficult to get any error messages propagated back to the view without introducing an undesirable dependency on the JSF API in your domain layer.
With Web Flow, you can utilize the generic and low-level
MessageContext
in your business code and any messages added
there will then be available to the FacesContext
at render
time.
For example, suppose you have a view where the user enters the
necessary details to complete a hotel booking, and you need to ensure
the Check In and Check Out dates adhere to a given set of business
rules. You can invoke such model-level validation from a
transition
element:
<view-state id="enterBookingDetails"> <transition on="proceed" to="reviewBooking"> <evaluate expression="booking.validateEnterBookingDetails(messageContext)" /> </transition> </view-state>
Here the "proceed" event is handled by invoking a model-level
validation method on the booking instance, passing the generic
MessageContext
instance so that messages may be recorded.
The messages can then be displayed along with any other JSF messages
with the h:messages
component,
JSF 2 provides built-in support for sending Ajax requests and performing partial processing and rendering on the server-side. You can specify a list of id's for partial rendering through the <f:ajax> facelets tag.
In Spring Web Flow you also have the option to specify the ids to use for partial rendering on the server side with the render action:
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="hotels:searchResultsFragment" /> </transition> </view-state>
Most JSF component providers include some form of 'file upload' component. Generally when working
with these components JSF must take complete control of parsing multi-part requests and Spring MVC's
MultipartResolver
cannot be used.
Spring Web Flow has been tested with file upload components from PrimeFaces and RichFaces. Check the documentation of your JSF component library for other providers to see how to configure file upload.
PrimeFaces provides a <p:fileUpload>
component for uploading files. In order
to use the component you need to configure the org.primefaces.webapp.filter.FileUploadFilter
servlet filter. The filter needs to be configured against Spring MVC's
DispatcherServlet
in your web.xml
:
<filter> <filter-name>PrimeFaces FileUpload Filter</filter-name> <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class> </filter> <filter-mapping> <filter-name>PrimeFaces FileUpload Filter</filter-name> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> </filter-mapping>
For more details refer to the PrimeFaces documentation.
RichFaces provides a <rich:fileUpload>
component for uploading files. No
special configuration is required to use the component, however, you will need to perform
some additional steps in your fileUploadListener
.
Here is some typical XHTML markup. In this example the fileUploadBean
refers
to Spring singleton bean.
<rich:fileUpload id="upload" fileUploadListener="#{fileUploadBean.listener}" acceptedTypes="jpg, gif, png, bmp"> </rich:fileUpload>
Within your fileUploadBean
you need to tell Web Flow that the response has been
handled and that it should not attempt any redirects. The org.springframework.webflow.context.ExternalContext
interface provides a recordResponseComplete()
for just such purposes.
In addition, it is imperative that some partial response data is returned to the client. If your
<rich:fileUpload>
component does not specify a render
attribute you
may need to call processPartial(PhaseId.RENDER_RESPONSE)
on the JSF
PartialViewContext
.
public class FileUploadBean { public void listener(FileUploadEvent event) throws Exception{ FacesContext.getCurrentInstance().getPartialViewContext().processPartial(PhaseId.RENDER_RESPONSE); ExternalContextHolder.getExternalContext().recordResponseComplete(); UploadedFile file = event.getUploadedFile(); // Do something with the file } }
For more details refer to the RichFaces documentation.
For JSF 1.2 the Spring Faces UICommand
components
have the ability to do Ajax-based partial view updates. These components
degrade gracefully so that the flow will still be fully functional by
falling back to full page refreshes if a user with a less capable
browser views the page.
Revisiting the earlier example with the paged table, you can
change the "More Results" link to use an Ajax request by replacing the
standard commandButton
with the Spring Faces component
version (note that the Spring Faces command components use Ajax by
default, but they can alternately be forced to use a normal form submit
by setting ajaxEnabled="false" on the component):
<sf:commandLink id="nextPageLink" value="More Results" action="next" />
This event is handled just as in the non-Ajax case with the
transition
element, but now you will add a special
render
action that specifies which portions of the
component tree need to be re-rendered:
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="hotels:searchResultsFragment" /> </transition> </view-state>
The fragments="hotels:searchResultsFragment"
is an
instruction that will be interpreted at render time, such that only the
component with the JSF clientId "hotels:searchResultsFragment" will be
rendered and returned to the client. This fragment will then be
automatically replaced in the page. The fragments
attribute
can be a comma-delimited list of ids, with each id representing the root
node of a subtree (meaning the root node and all of its children) to be
rendered. If the "next" event is fired in a non-Ajax request (i.e., if
JavaScript is disabled on the client), the render
action
will be ignored and the full page will be rendered as normal.
In addition to the Spring Faces commandLink
component, there is a corresponding commandButton
component
with the same functionality. There is also a special
ajaxEvent
component that will raise a JSF action even in
response to any client-side DOM event. See the Spring Faces tag library
docs for full details.
An additional built-in feature when using the Spring Faces
Ajax-enabled components is the ability to have the response rendered
inside a rich modal popup widget by setting popup="true"
on
a view-state
.
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true"> <on-entry> <render fragments="hotelSearchFragment" /> </on-entry> <transition on="search" to="reviewHotels"> <evaluate expression="searchCriteria.resetPage()"/> </transition> </view-state>
If the "changeSearchCriteria" view-state
is reached
as the result of an Ajax-request, the result will be rendered into a
rich popup. If JavaScript is unavailable, the request will be processed
with a full browser refresh, and the "changeSearchCriteria" view will be
rendered as normal.
By default when a flow enters a view state, it executes a client-side redirect before rendering the view. This approach is known as POST-REDIRECT-GET. It has the advantage of separating the form processing for one view from the rendering of the next view. As a result the browser Back and Refresh buttons work seamlessly without causing any browser warnings.
Normally the client-side redirect is transparent from a user's perspective. However, there are situations where POST-REDIRECT-GET may not bring the same benefits. For example sometimes it may be useful to embed a flow on a page and drive it via Ajax requests refreshing only the area of the page where the flow is rendered. Not only is it unnecessary to use client-side redirects in this case, it is also not the desired behavior with regards to keeping the surrounding content of the page intact.
To indicate a flow should execute in "page embedded" mode all you need to do is pass an extra flow input attribute called "mode" with a value of "embedded". Below is an example of a top-level container flow invoking a sub-flow in an embedded mode:
<subflow-state id="bookHotel" subflow="booking"> <input name="mode" value="'embedded'"/> </subflow-state>
When launched in "page embedded" mode the sub-flow will not issue flow execution redirects during Ajax requests.
If you'd like to see examples of an embedded flow please refer to the webflow-primefaces-showcase project. You can check out the source code locally, build it as you would a Maven project, and import it into Eclipse:
cd some-directory svn co https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase cd webflow-primefaces-showcase mvn package # import into Eclipse
The specific example you need to look at is under the "Advanced Ajax" tab and is called "Top Flow with Embedded Sub-Flow".
By default Web Flow does a client-side redirect even it it remains in the same view state as long as the current request is not an Ajax request. This is quite useful after form validation failures for example. If the user hits Refresh or Back they won't see any browser warnings. They would if the Web Flow didn't do a redirect.
This can lead to a problem specific to JSF 2 environments where a specific Sun Mojarra listener component caches the FacesContext assuming the same instance is available throughout the JSF lifecycle. In Web Flow however the render phase is temporarily put on hold and a client-side redirect executed.
The default behavior of Web Flow is desirable and it is unlikely JSF 2 applications will experience the issue. This is because Ajax is often enabled the default in JSF 2 component libraries and Web Flow does not redirect during Ajax requests. However if you experience this issue you can disable client-side redirects within the same view as follows:
<webflow:flow-executor id="flowExecutor"> <webflow:flow-execution-attributes> <webflow:redirect-in-same-state value="false"/> </webflow:flow-execution-attributes> </webflow:flow-executor>
To use the library you'll need to create a .taglib.xml
file and register it in web.xml
.
For JSF 2 create the file
/WEB-INF/springsecurity.taglib.xml
with the following
content:
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://www.springframework.org/security/tags</namespace> <tag> <tag-name>authorize</tag-name> <handler-class>org.springframework.faces.security.FaceletsAuthorizeTagHandler</handler-class> </tag> <function> <function-name>areAllGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAllGranted(java.lang.String)</function-signature> </function> <function> <function-name>areAnyGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAnyGranted(java.lang.String)</function-signature> </function> <function> <function-name>areNotGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areNotGranted(java.lang.String)</function-signature> </function> <function> <function-name>isAllowed</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean isAllowed(java.lang.String, java.lang.String)</function-signature> </function> </facelet-taglib>
For JSF 1.2 also create the file
/WEB-INF/springsecurity.taglib.xml
but with the following
content instead:
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://www.springframework.org/security/tags</namespace> <tag> <tag-name>authorize</tag-name> <handler-class>org.springframework.faces.security.Jsf12FaceletsAuthorizeTagHandler</handler-class> </tag> <function> <function-name>areAllGranted</function-name> <function-class>org.springframework.faces.security.Jsf12FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAllGranted(java.lang.String)</function-signature> </function> <function> <function-name>areAnyGranted</function-name> <function-class>org.springframework.faces.security.Jsf12FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAnyGranted(java.lang.String)</function-signature> </function> <function> <function-name>areNotGranted</function-name> <function-class>org.springframework.faces.security.Jsf12FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areNotGranted(java.lang.String)</function-signature> </function> <function> <function-name>isAllowed</function-name> <function-class>org.springframework.faces.security.Jsf12FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean isAllowed(java.lang.String, java.lang.String)</function-signature> </function> </facelet-taglib>
Next, register the above file taglib in web.xml:
<context-param> <param-name>javax.faces.FACELETS_LIBRARIES</param-name> <param-value>/WEB-INF/springsecurity.taglib.xml</param-value> </context-param>
Now you are ready to use the tag library in your views. You can use the authorize tag to include nested content conditionally:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:sec="http://www.springframework.org/security/tags"> <sec:authorize ifAllGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> <sec:authorize ifNotGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> <sec:authorize ifAnyGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> </ui:composition>
You can also use one of several EL functions in the rendered or other attribute of any JSF component:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:sec="http://www.springframework.org/security/tags"> <!-- Rendered only if user has all of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAllGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user does not have any of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areNotGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user has any of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAnyGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user has access to given HTTP method/URL as defined in Spring Security configuration --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:isAllowed('/secured/foo', 'POST')}"/> </ui:composition>
JSF and Web Flow combine to provide an extensive server-side validation model for your web application, but excessive roundtrips to the server to execute this validation and return error messages can be a tedious experience for your users. The Spring Faces component library provides a number of client-side rich validation controls that can enhance the user experience by applying simple validations that give immediate feedback. Some simple examples are illustrated below. See the Spring Faces taglib docs for a complete tag reference.
Simple client-side text validation can be applied with the
clientTextValidator
component:
<sf:clientTextValidator required="true"> <h:inputText id="creditCardName" value="#{booking.creditCardName}" required="true"/> </sf:clientTextValidator>
This will apply client-side required validation to the child
inputText
component, giving the user a clear indicator if
the field is left blank.
Simple client-side numeric validation can be applied with the
clientNumberValidator
component:
<sf:clientTextValidator required="true" regExp="[0-9]{16}" invalidMessage="A 16-digit credit card number is required."> <h:inputText id="creditCard" value="#{booking.creditCard}" required="true"/> </sf:clientTextValidator>
This will apply client-side validation to the child
inputText
component, giving the user a clear indicator if
the field is left blank, is not numeric, or does not match the given
regular expression.
Simple client-side date validation with a rich calendar popup can
be applied with the clientDateValidator
component:
<sf:clientDateValidator required="true" > <h:inputText id="checkinDate" value="#{booking.checkinDate}" required="true"> <f:convertDateTime pattern="yyyy-MM-dd" timeZone="EST"/> </h:inputText> </sf:clientDateValidator>
This will apply client-side validation to the child
inputText
component, giving the user a clear indicator if
the field is left blank or is not a valid date.
The validateAllOnClick
component can be used to
intercept the "onclick" event of a child component and suppress the
event if all client-side validations do not pass.
<sf:validateAllOnClick> <sf:commandButton id="proceed" action="proceed" processIds="*" value="Proceed"/>  </sf:validateAllOnClick>
This will prevent the form from being submitted when the user clicks the "proceed" button if the form is invalid. When the validations are executed, the user is given clear and immediate indicators of the problems that need to be corrected.
The Spring Web Flow JSF integration strives to be compatible with any third-party JSF component library. By honoring all of the standard semantics of the JSF specification within the SWF-driven JSF lifecycle, third-party libraries in general should "just work". The main thing to remember is that configuration in web.xml will change slightly since Web Flow requests are not routed through the standard FacesServlet. Typically, anything that is traditionally mapped to the FacesServlet should be mapped to the Spring DispatcherServlet instead. (You can also map to both if for example you are migrating a legacy JSF application page-by-page.) In some cases, a deeper level of integration can be achieved by configuring special flow services that are "aware" of a particular component library, and these will be noted in the examples to follow.
To use the Rich Faces component library with Spring Web Flow, the following filter configuration is needed in web.xml (in addition to the other typical configuration already shown):
<filter> <display-name>RichFaces Filter</display-name> <filter-name>richfaces</filter-name> <filter-class>org.ajax4jsf.Filter</filter-class> </filter> <filter-mapping> <filter-name>richfaces</filter-name> <servlet-name>Spring Web MVC Dispatcher Servlet</servlet-name> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping>
For deeper integration (including the ability to have a view with combined use of the Spring Faces Ajax components and Rich Faces Ajax components), configure the RichFacesAjaxHandler on your FlowController:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController"> <property name="flowExecutor" ref="flowExecutor" /> <property name="ajaxHandler"> <bean class="org.springframework.faces.richfaces.RichFacesAjaxHandler"/> </property> </bean>
RichFaces Ajax components can be used in conjunction with the
render
tag to render partial fragments on an Ajax request.
Instead of embedding the ids of the components to be re-rendered
directly in the view template (as you traditionally do with Rich Faces),
you can bind the reRender
attribute of a RichFaces Ajax
component to a special flowRenderFragments
EL variable. For
example, in your view template you can have a fragment that you would
potentially like to re-render in response to a particular event:
<h:form id="hotels"> <a4j:outputPanel id="searchResultsFragment"> <h:outputText id="noHotelsText" value="No Hotels Found" rendered="#{hotels.rowCount == 0}"/> <h:dataTable id="hotels" styleClass="summary" value="#{hotels}" var="hotel" rendered="#{hotels.rowCount > 0}"> <h:column> <f:facet name="header">Name</f:facet> #{hotel.name} </h:column> <h:column> <f:facet name="header">Address</f:facet> #{hotel.address} </h:column> </h:dataTable> </a4j:outputPanel> </h:form>
then a RichFaces Ajax commandLink
to fire the
event:
<a4j:commandLink id="nextPageLink" value="More Results" action="next" reRender="#{flowRenderFragments}" />
and then in your flow definition a transition
to
handle the event:
<transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="hotels:searchResultsFragment" /> </transition>
The Apache MyFaces Trinidad library has been tested with the Spring Web Flow's JSF integration and proven to fit in nicely. Deeper integration to allow the Trinidad components and Spring Faces components to play well together has not yet been attempted, but Trinidad provides a pretty thorough solution on its own when used in conjunction with the Spring Web Flow JSF integration.
NOTE: An AjaxHandler
implementation for Trinidad is
not currently provided out-of-the-box. In order to fully integrate with
Trinidad's PPR functionality, a custom implementation should be
provided. An community-provided partial example can be found here:
SWF-1160
Typical configuration when using Trinidad with Web Flow is as follows in web.xml (in addition what has already been shown):
<context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param> <context-param> <param-name> org.apache.myfaces.trinidad.CHANGE_PERSISTENCE </param-name> <param-value>session</param-value> </context-param> <context-param> <param-name> org.apache.myfaces.trinidad.ENABLE_QUIRKS_MODE </param-name> <param-value>false</param-value> </context-param> <filter> <filter-name>Trinidad Filter</filter-name> <filter-class> org.apache.myfaces.trinidad.webapp.TrinidadFilter </filter-class> </filter> <filter-mapping> <filter-name>Trinidad Filter</filter-name> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> </filter-mapping> <servlet> <servlet-name>Trinidad Resource Servlet</servlet-name> <servlet-class> org.apache.myfaces.trinidad.webapp.ResourceServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>resources</servlet-name> <url-pattern>/adf/*</url-pattern> </servlet-mapping>