Integración de Spring Web Flow 2 con JSF2

4
17785

Integración de Spring Web Flow 2 con JSF2.

0. Índice de contenidos.


1. Introducción

JSF es el estándar de JEE para la creación de interfaces de usuario basadas en componentes visuales y Spring Web Flow es una extensión de Spring
basada en el patrón ApplicationController para gestionar
el flujo de navegación entre pantallas y mantener el estado de los objetos que participan en las mismas a modo de «conversación», un ámbito superior al
de petición e inferior al de sesión. El ejemplo típico de conversación es el ámbito de los pasos de un carrito de la compra, un registro de usuarios o
el proceso desde que seleccionamos un destino vacacional hasta que hacemos efectivo el pago del paquete en el que pueden verse implicados varios
productos: hotel, vuelo, coche de alquiler,.. .

JSF también tiene su propia gestión de navegabilidad pudiendo realizarla de forma declarativa a través de XML o por convención de nomenclatura
directamente en el resultado de las acciones. Si bien, antes de la versión 2 de JSF, la única manera de mantener un estado conversacional era usando
el framework Jboss Seam,
integrando la gestión de navegación con Spring Web Flow o
ADF Tasks Flows.

Ahora, en la versión 2 de JSF, ya disponemos de un ámbito flash que,
más que un ámbito, es un saco en el que ir metiendo objetos y mantenerlos de una vista a otra y, en la versión 2.2 de JSF,
se introduce el concepto de Faces Flow, que introduce un ámbito de flujo de navegación
(@FlowScoped), pero no estará disponible hasta Marzo de 2013.

Si no podemos esperar ;), o ya conocemos Spring Web Flow, en este tutorial vamos a ver cómo integrarlo con JSF2.

JSF proporciona puntos de extensión que permiten, en este caso, a Spring Web Flow tomar el control sobre el manejo de las reglas de navegación y
manejar el estado asociado con las interacciones de usuario, esto es, las conversaciones. De este modo, con Spring Web Flow podemos:

  • implementar reglas de navegación dinánicas que puede modificarse en caliente sin necesidad de reiniciar el servidor,
  • configurar forwards, refresh, redirect y navegación recursiva en el lenguaje de definición de flujos, rompiendo con el forward en el que se centra JSF y
  • enapsular y modularizar la lógica de navegación a través del concepto de flujo.

Spring Web Flow introduce 3 ámbitos de conversación que amplian los básicos de JSF (application, session y request):

  • conversación: es el ámbito de duración de un diálogo con el usuario,
  • flow: es el ámbito de duración de un flujo dentro de una conversación, y
  • flash: es el ámbito de duración de una vista dentro de un flujo; vendría a ser el ámbito de vista (@ViewScope) nuevo en JSF2.

Se dice que estos ámbitos son manejados porque la limpieza de los objetos implicados es automáticamente gestionada por el contexto y ahí está la gracia,
en poder mantener el estado de los objetos entre distintas vistas de navegación sin necesidad de usar la sesión y que sean manejados automáticamente por el
contexto de Spring Web Flow.

En este tutorial vamos a analizar cómo realizar la integración de estos dos frameworks a través de un ejemplo de uso muy simple, intentado emular el
escenario que proponíamos en el tutorial sobre JS2 Flash Scope.


2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.4 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.7.4
  • Spring 3.1.1.RELEASE
  • JSF2, Mojarra Impl. 2.1.12
  • Spring Web Flow 2.3


3. Configuración.

Lo primero, como siempre, haciendo uso de maven, es declarar las dependencias de nuestro proyecto:

<dependency>
	<groupId>com.sun.faces</groupId>
	<artifactId>jsf-api</artifactId>
</dependency>
<dependency>
	<groupId>com.sun.faces</groupId>
	<artifactId>jsf-impl</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.webflow</groupId>
	<artifactId>spring-faces</artifactId>
	<version>2.3.1.RELEASE</version>
</dependency>

Spring Web Flow se integra con JSF, entre otros, a través de tres puntos de extensión:

  • un listener propio del ciclo de vida de JSF, que permite manejar la ejecución de nuevos flujos cuando se invoca una petición por un
    cliente y que restaura el flujo de ejecución existente cuando se restaura la vista de JSF,
  • un VariableResolver propio que hace trasparente que las referencias a los managedBean sean manejadas por el contexto de Spring Web Flow, y
  • un NavigationHandler propio que permite invocar al comportamiento adecuado y asignar la siguiente vista conforme a la definición de los flujos de navegación.

Con todo ello, seguimos teniendo todas las ventajas de trabajar con los componentes nativos de JSF porque la integración es transparente y, además, disponemos del
modelo de flujo de navegación que proporciona Spring Web Flow.

Para configurar Spring Web Flow con JSF2 no es necesario declarar nada a nivel de JSF puesto que, al incluir las dependencias de la librería spring-faces,
esta incorpora su propio faces-config.xml que se carga automáticamente con la configuración necesaria para JSF.

Lo que sí es necesario es configurar a nivel de aplicación web (web.xml) ambos servlets: el punto de entrada de JSF2 y el de Spring MVC,
que sirve de enlace a Spring Web Flow. Si toda la aplicación va a estar gestionada por Spring Web Flow no haremos uso directo del servlet de JSF,
solo nos servirá para levantar el contexto.

<servlet>
	<servlet-name>Faces Servlet</servlet-name>
	<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
	<load-on-startup>1</load-on-startup>	
</servlet>

<servlet-mapping>
	<servlet-name>Faces Servlet</servlet-name>
	<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
	
<servlet>
	<servlet-name>Spring MVC Servlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/applicationContext-flow.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
		
<servlet-mapping>
	<servlet-name>Spring MVC Servlet</servlet-name>
	<url-pattern>/application/*</url-pattern>
</servlet-mapping>

Para cargar la configuración de Spring Web Flow dentro del fichero applicationContext-flow.xml que levanta en contexto de Spring MVC,
debemos incluir la siguiente configuración:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/webflow-config"
    xmlns:beans="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.xsd
                    http://www.springframework.org/schema/faces 
                    http://www.springframework.org/schema/faces/spring-faces-2.2.xsd
                    http://www.springframework.org/schema/webflow-config 
                    http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd">

	<!-- 
		Enable processing of JSF 2 resource requests. For example:
		/context/app/javax.faces.resource/jsf.xhtml?ln=javax.faces
	-->
	<faces:resources />

	<!--
		Maps request paths to flows in the flowRegistry; e.g. a path of
		/registration-flow looks for a flow with id "registration-flow"
	-->
	<beans:bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
		<beans:property name="flowRegistry" ref="flowRegistry" />
	</beans:bean>
	
	<!--
		Dispatches requests mapped to flows to FlowHandler implementations
	-->
	<beans:bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter">
		<beans:property name="flowExecutor" ref="flowExecutor" />
	</beans:bean>
	
	<!-- Executes flows: the central entry point into the Spring Web Flow system -->
	<flow-executor id="flowExecutor">
		<flow-execution-listeners>
			<listener ref="facesContextListener"/>
		</flow-execution-listeners>
	</flow-executor>

	<!-- The registry of executable flow definitions -->
	<flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF/flows">
		<flow-location-pattern value="/*-flow.xml" />
	</flow-registry>

	<!-- Configures the Spring Web Flow JSF integration -->
	<faces:flow-builder-services id="flowBuilderServices" development="true" />

	<!-- A listener to create and release a FacesContext -->
	<beans:bean id="facesContextListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener"/>

</beans:beans>   

Con esta configuración podemos ubicar los ficheros xml, con el sujifo -flow, con los flujos bajo el directorio WEB-INF/flows/… y, con development=true podemos incorporar o modificar la configuración de los flujos en caliente.


4. Flujo de navegación.

Como comentábamos al principio el ejemplo de uso será muy simple, este es solo un tutorial de integración y se basa en cubrir un escenario con tres vistas
que manejen un mismo objeto cuyo estado se mantenga estable entre las mismas, para ello, lo primero es definir el contenido de nuestro flujo de navegación,
un fichero registration-flow.xml ubicado en WEB-INF/flows/:

<?xml version="1.0" encoding="UTF-8"?>
<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="usuario" class="com.autentia.training.core.persistence.entities.Usuario" />

	<view-state id="step1" model="usuario">
		<transition on="next" to="step2"/>
	</view-state>

	<view-state id="step2" model="usuario">
		<transition on="previous" to="step1"/>
		<transition on="next" to="confirmation"/>
	</view-state>
	
	<end-state id="confirmation" view="confirmation.xhtml" />

</flow>

A diferencia del ejemplo que vimos con el ámbito de Flash propio de JSF2, quien crea el objeto y lo mantiene vivo durante
el flujo de navegación es Spring Web Flow, declarado mediante la variable <var name=»usuario»…

Las vistas, por defecto, se ubican en el mismo directorio y por convención tienen el mismo nombre que las acciones (to):

El contenido de las vistas es el siguiente:

step1.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:f="http://java.sun.com/</core"
	xmlns:h="http://java.sun.com/</html"
	xmlns:ui="http://java.sun.com/</facelets">
	
<ui:composition template="/WEB-INF/templates/defaultLayout.xhtml">

	<ui:define name="title">Paso 1 del registro</ui:define>
	<ui:define name="content">

		<h:form>
			<h:panelGrid columns="3" style="width:50%;">
				<h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" />
				<h:inputText id="nombre" value="#{usuario.nombre}"
					required="true" />
				<h:message for="nombre" />

				<h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" />
				<h:inputText id="apellidos" value="#{usuario.apellidos}"
					required="true" />
				<h:message for="apellidos" />

			</h:panelGrid>

			<h:commandButton value="#{msg['action.next']}"
				action="#{flowRegistrationStep1.next}" />

		</h:form>
	</ui:define>
</ui:composition>
</html>

step2.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:ui="http://java.sun.com/jsf/facelets">

<ui:composition template="/WEB-INF/templates/defaultLayout.xhtml">

	<ui:define name="title">Paso 2 del registro</ui:define>
	<ui:define name="content">
		<h:form>
			<h:panelGrid columns="3" style="width:50%;">
				<h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" />
				<h:outputText id="nombre" value="#{usuario.nombre}" />
				<h:message for="nombre" />

				<h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" />
				<h:outputText id="apellidos" value="#{usuario.apellidos}" />
				<h:message for="apellidos" />

				<h:outputLabel for="email" value="#{msg['Usuario.email']}" />
				<h:inputText id="email" value="#{usuario.email}"
					required="true" />
				<h:message for="email" />

				<h:outputLabel for="password" value="#{msg['Usuario.password']}" />
				<h:inputSecret id="password" value="#{usuario.password}" />
				<h:message for="password" />

			</h:panelGrid>
			<h:commandButton value="#{msg['action.previous']}"
				action="#{flowRegistrationStep2.previous}" immediate="true" />
			<h:commandButton value="#{msg['action.next']}"
				action="#{flowRegistrationStep2.next}" />
		</h:form>
	</ui:define>
</ui:composition>
</html>

confirmation.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:ui="http://java.sun.com/jsf/facelets">
	
	<ui:composition template="/WEB-INF/templates/defaultLayout.xhtml">
		<ui:define name="title">Confirmación del registro</ui:define>
		<ui:define name="content">
			<h:form>
				<h:panelGrid columns="2" style="width:50%;">
					<h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" />
					<h:outputText id="nombre" value="#{usuario.nombre}" />
	
					<h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" />
					<h:outputText id="apellidos" value="#{usuario.apellidos}" />
	
					<h:outputLabel for="email" value="#{msg['Usuario.email']}" />
					<h:outputText id="email" value="#{usuario.email}" />
	
				</h:panelGrid>
			</h:form>
		</ui:define>
	</ui:composition>
</html>

Los managedBeans de JSF tienen el siguiente código:

FlowRegistrationStep1.java

package com.autentia.training.web.sandbox.flow;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import com.autentia.training.core.persistence.entities.Usuario;

@ManagedBean
public class FlowRegistrationStep1 {

	@ManagedProperty("#{usuario}")
	private Usuario usuario;
	
	public void setUsuario(Usuario usuario){
		this.usuario = usuario;
	}
	
	public String next(){
		System.out.println(usuario);
		return "next";
	}
}

FlowRegistrationStep2.java

package com.autentia.training.web.sandbox.flow;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import com.autentia.training.core.persistence.entities.Usuario;

@ManagedBean
public class FlowRegistrationStep2 {

	@ManagedProperty("#{usuario}")
	private Usuario usuario;
	
	public void setUsuario(Usuario usuario){
		this.usuario = usuario;
	}
	
	public String previous(){
		System.out.println(usuario);
		return "previous";
	}
	
	public String next(){
		System.out.println(usuario);
		return "next";
	}
}

Lo que cambia en los ManagedBeans es como obtienen la referencia del objeto; la obtienen por injección de dependencias puesto que se encuentra en el ámbito
de los mismos y el contexto de JSF es capaz de encontrar un objeto dentro del contexto de Spring, porque ambos están integrados, mediante la siguiente configuración
en el fichero faces-config.xml:

	...
		<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
	</application>

Para arrancar el flujo de navegación debemos invocar a la siguiente url http://localhost:8080/context-web/application/registration-flow gestionada por Spring MVC; mediante las reglas de navegación
cargará la primera vista generada por el ciclo de vida de JSF:

tras pulsar sobre siguiente se generará un evento de JSF que gestionará Spring Web Flow en vez de la gestión de navegación propia de JSF para redirigir al siguiente paso del flujo, las fases previas del ciclo de vida de JSF se ejecutan como una petición normal.

Del mismo modo entrará en juego la definición del flujo para navegar hacía el siguiente paso o volver atrás en el flujo; en caso de continuar se mostrará la última vista.

El ámbito del objeto del modelo se ha destruido en esta última vista, si recargamos la página nos redirecciona a la primera vista del flujo, si intentamos recuperar un id de conversación anterior nos redirecciona a la primera vista y no podemos acceder a un paso intermedio cuando la instancia del flujo, esto es, la conversación ha terminado.

Este es un punto cualitativo que diferencia este ejemplo del de Flash Scope de JSF2, en el que todos esos controles corren de nuestra cuenta.


6. Referencias.


7. Conclusiones.

Ahora solo queda abrir el abanico de posibilidades que nos proporciona Spring Web Flow:

  • integración del filtro de seguridad,
  • gestión de transacciones unida al flujo de navegación, o
  • explorar los listeners para marcar una estrategia de auditoria del uso de flujos por nuestros usuarios.

Un saludo.

Jose

jmsanchez@autentia.com

4 COMENTARIOS

  1. Hola Jose! me gusto mucho tu material para flujos de navegacion y me interesaría saber si hay alguna manera de agregarle discriminacion por roles de usuario en las redirecciones. Por ejemplo, supongamos que para dar de alta un usuario utilizo la pagina createUser.xhtml que es la que contiene el formulario; tanto cuando un usuario se quiere registrar como cuando el admin quiere dar de alta uno, acceden a la misma pagina que tiene su respectivo toolbarFooter que es el contenedor de los botones de accion guardar y cancelar. Ahora el problema está en que el flujo de navegacion depende del rol de usuario ya que los dos vienen de lugares distintos y van a lugares distintos. Espero entiendas mi inquietud y puedas ayudarme… gracias!

  2. Hola llevo años viendo vuestros tutoriales y aunque resultan utiles, siempre encuentro el mismo problema en todos!!! FALTAN DATOS DE CONFIGURACION , para que sirve un tutorial si no somos capaces de reproducirlo y probarlo.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad