Proyecto de Integración
 

Aplicación web con JSF

Introducción

Vamos a refactorizar la aplicación web tal y como estaba antes de la introducción de Struts, en la sesión de integración de JSP, pero conservando (y refactorizando) las modificaciones de la capa de negocio introducidas en el Proyecto Web.

El stack JSF va a estar formado por:

  • Proyecto común
  • Capa persistencia con JPA
  • Capa de lógica de negocio que usa JPA
  • Capa web con JSF

La siguiente figura muestra los proyectos del stack y las dependencias entre ellos:

Vamos a llevar a cabo los siguientes pasos:

  • Capa de negocio:
    • Refactorizar la capa de negocio llevando las interfaces de negocio al proyecto común
    • Refactorizar el proyecto jbib-persistencia-jpa
    • Crear el proyecto jbib-negocio-jpa
  • Capa de presentación:
    • Plantilla principal
    • Listados de libros de usuario
    • Página de login
    • Reservas de libros usuario
    • Listado de libros de bibliotecario
    • Altas, bajas y modificaciones de libros

Hay dos elementos fundamentales que no vamos a tratar en esta sesión: seguridad y localización. En la segunda parte del especialista vamos a continuar con este stack JSF y los añadiremos en algún momento. Por ejemplo, la seguridad la introduciremos en la próxima sesión de integración, en la que portaremos todo el stack a GlassFish y NetBeans.

En esta sesión continuaremos trabajando en parejas, por lo que sería interesante dividir y repartir las tareas. Por ejemplo, una persona podría responsabilizarse de los cambios en las capas de negocio y persistencia y otra persona de la capa web JSF.

Proyecto jbib-comun

Las interfaces de negocio se encuentran en la actualidad en el proyecto jbib-negocio, junto con su implementación utilizando DAOs. Queremos añadir una nueva implementación de la lógica de negocio utilizando JPA. ¿Dónde la creamos? ¿En el proyecto existente? ¿Creamos un nuevo proyecto?. La decisión de crear un nuevo proyecto se justifica por motivos de modularidad e independencia en el desarrollo y el despliegue. En nuestro caso ya hemos separado la capa de persistencia en los proyectos jbib-persist-dao y jbib-persist-jpa, dos módulos independientes que definen dos versiones distintas del mismo proyecto (dos stacks). Debemos hacer lo mismo con las implementaciones de la capa de negocio: jbib-negocio y jbib-negocio-jpa. Ello nos obliga a sacar las interfaces de jbib-negocio y colocarlas en un proyecto aparte, del que dependen los dos proyectos de implementación.

Nota
En lugar de jbib-negocio sería mejor llamar al proyecto jbib-negocio-dao para reforzar la idea de su dependencia con la capa de persistencia DAO. Sin embargo vamos a dejarlo así, para evitar problemas en la refactorización.

Colocamos todas las interfaces de negocio y la factoría abstracta FactoriaAbstractaBos en el proyecto común jbib-comun:

La clase FactoriaAbstractaBos define los métodos con los que obtener los objetos de negocio. Deberá ser extendida por las distintas implementaciones FactoriaBosDao y FactoriaBosJpa que se implementarán en los distintos proyectos. Más adelante realizaremos una tercera implementación usando EJBs.

package org.especialistajee.jbib.bo;

import org.especialistajee.jbib.bo.libro.ILibroBo;
import org.especialistajee.jbib.bo.operacion.IOperacionBo;
import org.especialistajee.jbib.bo.usuario.IUsuarioBo;

public abstract class FactoriaAbstractaBos {
   public abstract ILibroBo getLibroBo();
   public abstract IOperacionBo getOperacionBo();
   public abstract IUsuarioBo getUsuarioBo();
}

Cambiamos la capa de negocio ya existente jbib-negocio para que continue funcionando correctamente con estos cambios.

Proyecto jbib-persistencia-jpa

Vamos a añadir la clase singleton PersisteceManager al proyecto jbib-persistencia-jpa para que sea más sencillo obtener los entity managers desde la capa de negocio. Esta clase va a encapsular la creación de EntityManagers:

package org.especialistajee.jbib.eao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class PersistenceManager {
   static private final String PERSISTENCE_UNIT_NAME = "proyint";
   protected static PersistenceManager me = null;
   EntityManagerFactory emf = null;
   
   private PersistenceManager() {
      emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
   }
   
   public static PersistenceManager getInstance() {
      if (me == null) {
         me = new PersistenceManager();
      }
      return me;
   }

   public EntityManager crateEntityManager() {
      return emf.createEntityManager();
   }
}

Esta clase hace muy sencilla la obtención de entity managers en operaciones de negocio. El patrón típico de un método de negocio será el siguiente:

public miTipoDomain miMetodo() throws miExcepcionNegocio {
   EntityManager em = PersistenceManager.getInstance().crateEntityManager();
   ClaseEao objetoEao = new ClaseEao();
   objetoEao.setEntityManager(em);
   em.getTransaction().begin(); // Si es necesario

   // Llamamos a los objetos EAO para trabajar con entidades
   // Convertimos las entidades en objetos de dominio
   // Cerramos la transacción y el entity manager

   em.getTransaction().commit(); // Si es necesario
   em.close();

   return objetoDomain;
}

Proyecto jbib-negocio-jpa

Debemos crear un proyecto Maven similar al proyecto jbib-negocio, pero que realice una implementación utilizando JPA en lugar de JDBC.

La estructura de clases será idéntica a la del proyecto jbib-negocio, pero añadiendo a las clases el sufijo Jpa. Por ejemplo, LibroBoJpa o UsuarioBoJpa.

Debes implementar las clases necesarias para realizar las operaciones que se incluían en el proyecto JSP. Puedes utilizar el método realizarReserva que ya tenías de la sesión de JPA.

Resumen de operaciones:

  • Login
  • Operaciones usuario
    • Listado de todos los libros
    • Listado de libros de usuario
    • Mostrar detalle del libro
    • Reservar libro
    • Anular reserva de libro
  • Operaciones bibliotecario
    • Listar todos los libros
    • Añadir libro
    • Borrar libro
    • Modificar libro

Podéis utilizar la implementación de JDBC para comprobar los valores a devolver y las excepciones. Como ejemplo, mostramos la operación recuperaLibroOperaciones(String isbn); una operación nueva que hay que incluir en el interfaz ILibroBo y que devuelve un LibroTo con las operaciones activas, y sus usuarios, de un libro.

public LibroTo recuperaLibroOperaciones(String isbn) throws LibroException {
   // Obtenemos el entity manager y los EAO
   EntityManager em = PersistenceManager.getInstance().crateEntityManager();
   LibroEao libroEao = new LibroEao();
   OperacionEao operacionEao = new OperacionEao();
   libroEao.setEntityManager(em);
   operacionEao.setEntityManager(em);

   // Buscamos el libro y sus operaciones activas
   LibroEntity libro = libroEao.find(isbn);
   List<OperacionEntity> operaciones =
      operacionEao.findOperacionesLibro(isbn, EstadoOperacion.ACTIVA);

   // Copiamos los datos en objetos de dominio
   LibroDomain libroDomain = libro.obtenerDomain();
   List<OperacionDomain> operacionesDomain = new ArrayList<OperacionDomain>();
   for (OperacionEntity operacion : operaciones) {
      OperacionDomain opDomain = operacion.obtenerDomain();
      opDomain.setUsuario(operacion.getUsuario().obtenerDomain());
      operacionesDomain.add(opDomain);
   }

   // Construimos el transfer object que devuelve toda la información
   LibroTo libroTo = new LibroTo(libroDomain, operacionesDomain);
   return libroTo;
}

Proyecto jbib-web-jsf

Se debe crear un proyecto jbib-web-jsf que tenga las mismas funcionalidades del proyecto JSP, pero utilizando JSF como framework de presentación. Deberemos crear el proyecto utilizando el arquetipo de RichFaces visto en las sesiones de JSF:

mvn archetype:generate -DarchetypeGroupId=org.richfaces.archetypes \
-DarchetypeArtifactId=richfaces-archetype-simpleapp  -DarchetypeVersion=4.0.0.20101226-M5 \
-DgroupId=org.especialistajee.proyint -DartifactId=jbib-web-jsf -Dversion=1.0 \
-Dpackage=org.especialistajee.jbib.jsf

Debemos construir el proyecto definiendo los distintos elementos de una aplicación JSF:

  • Páginas XHTML con las vistas de la aplicación. Como punto de partida utilizaremos la misma hoja de estilos CSS que en el proyecto web y etiquetas básicas <h:> de JSF. Animamos a sustituir alguna etiqueta por algún componente más avanzado de RichFaces.
  • Beans gestionados con las propiedades a mostrar en las vistas y los métodos de acción que enlazan con la capa de negocio.
  • Definición de la navegación entre las vistas mediante el fichero de configuración faces-config.xml.

A continuación detallamos algunas partes de la aplicación, incluyendo ejemplos de código. Considerarlos ejempos iniciales que podéis modificar y ampliar.

No incluimos el código de los beans gestionados ni de la navegación entre vistas. Recomendamos utilizar anotaciones para los primeros y el entorno visual de JSF Toolbox para el segundo.

Página de login

Comenzamos con la página de login, copiando la página de login del proyecto web existente y adaptándola a JSF. Utilizamos el bean gestionado loginBean.

Fichero login.xhtml:

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <title>Biblioteca jTech</title>
</h:head>
<h:body>

<h:outputStylesheet library="css" name="style.css"/>

<div style="position: absolute; left: 30%; top: 10em; width: 600px">

<h:form>
<table style="border: 1px solid #D7D6D6">
   <tr style="height: 5em">
      <td style="width: 250px">
      <h3>Bienvenido a la biblioteca jTech</h3>
      </td>
      <td style="width: 350px">Por favor introduzca el usuario y la
   contraseña para entrar en la aplicación</td>
   </tr>
   <tr style="vertical-align: bottom; height: 100px">
      <td style="text-align: right; background-color: #D7D6D6">
         <strong>usuario</strong>:
      </td>
      <td><h:inputText value="#{loginBean.login}" /></td>
   </tr>
   <tr style="vertical-align: top; height: 100px">
      <td style="text-align: right; background-color: #D7D6D6">
         <strong>contraseña</strong>:
      </td>
      <td> <h:inputSecret value="#{loginBean.password}"/> 
           <h:commandButton value="Entrar" action="#{loginBean.doLogin}" /></td>
   </tr>
</table>
</h:form>
</div>
</h:body>
</html>

Plantilla principal de la aplicación

La plantilla principal se define en el fichero templates/principal.xhtml. La plantilla incluye la cabecera, el menú de acciones a la izquierda, un contenido variable y el pie de página.

Fichero templates/principal.xhtml:

<!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:h="http://java.sun.com/jsf/html"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <title>Biblioteca jTech</title>
</h:head>

<h:body>

   <h:outputStylesheet library="css" name="style.css"/>

   <div id="wrap">
      <ui:include src="includes/cabecera.xhtml"/> 
      <ui:include src="includes/menu.xhtml"/>
   <div id="content"><ui:insert name="contenido">
      <p>Contenido</p>
   </ui:insert></div>
   <ui:include src="includes/pie.xhtml" /></div>
</h:body>
</html>

En la plantilla se utiliza la etiqueta <h:outputStylesheet> para definir la localización del fichero CSS que se utiliza en la aplicación. La librería que se define en el atributo library representa el nombre del directorio dentro del directorio estándar resources de JSF 2.0 (que debe estar en la raíz de la aplicación web). El nombre definido por el atributo name representa el nombre del fichero.

La cabecera se define aparte, en el fichero template/include/cabecera.xhtml. También utilizamos la librería (directorio) de recursos colocada en el directorio resources de la aplicación web. En esta ocasión se accede al directorio imagenes para obtiener el fichero banner-java.gif.

Fichero template/include/cabecera.xhtml:

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

   <ui:composition>

   <table width="100%">
      <tr>
         <td><h1>Biblioteca jTech</h1></td>
         <td>&nbsp;</td>
         <td align="right">
            <h:graphicImage library="imagenes" name="banner-java.gif"/>
         </td>
      </tr>
      <tr>
         <td colspan="3" align="right">
            <h:form>
               <h:commandLink value="Cerrar sesión"
                  action="#{usuarioBean.doLogout}"/>
            </h:form>
         </td>
      </tr>
      <tr>
         <td colspan="3" align="right">Usuario: 
            <h:outputText value="#{usuarioBean.usuario.login}"/> - 
            Rol: <h:outputText value="#{usuarioBean.usuario.tipo}" />
         </td>
      </tr>
   </table>

   </ui:composition>
</html>

El menú se define en un fichero separado que se incluye en la plantilla. Se utiliza la etiqueta <h:link> para generar un enlace a las páginas listadoLibros.xhtml y misLibros.xhtml en las que se realizan los listados correspondientes.

Fichero includes/menu.xhtml

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

   <ui:composition>
   
   <div id="sidebar">
      <h2>Libros</h2>
      <fieldset><legend>Listados</legend>
         <ul>
         <li><h:link outcome="listadoLibros" value="Todos"/></li>
   <li><h:link outcome="misLibros" value="Mis libros"/></li>
</ul>
</fieldset>
</div>
   </ui:composition>
</html>

Y el pie de página se define en el fichero templates/includes/pie.xhtml

Fichero templates/includes/pie.xhtml:

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

   <ui:composition> 
      <div id="footer">
         <p>&copy; 2010-11 Especialista Universitario Java 
         Enterprise  - <a href="http://especialistajee.org">especialistajee.org</a></p>
      </div>
      </ui:composition>
</html>

Para mostrar esta plantilla principal hay que utilizar el siguiente esquema:

<ui:composition template="/templates/principal.xhtml">
   <ui:define name="contenido">
      &lt;!--Contenido  -->
   <ui:define/>
</ui:composition>

El resultado de llamar a la plantilla incluyendo el listado de todos los libros de un usuario es el siguiente:

Listados de libros de usuarios

Para el listado de libros puedes utilizar una <dataTable> tal y como se muestra en el siguiente ejemplo.

Fichero usuario/listadoLibros.xhtml:

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

	<ui:composition template="/templates/principal.xhtml">
	   <ui:define name="contenido">
	   <h2>Listado de libros</h2>
	   <h:form>
       <h:dataTable value="#{usuarioBean.todosLibros}" var="libro">
	         <h:column>
	            <f:facet name="header"> 
	               <h:outputText value="ISBN"/>
	            </f:facet>
	            <h:outputText value="#{libro.isbn}" />
             </h:column> 
            <h:column>
	            <f:facet name="header"> 
	               <h:outputText value="Título"/>
	            </f:facet>
   			 	<h:outputText value="#{libro.titulo}"/>
   			 </h:column>

   			 <h:column> 
   			 	<f:facet name="header"> 
	               <h:outputText value="Autor"/>
	            </f:facet>   			 
   			    <h:outputText value="#{libro.autor}"/>
   			 </h:column>
   			 
   			 <h:column> 
   			 	<f:facet name="header"> 
	               <h:outputText value="Paginas"/>
	            </f:facet>   			 
   			    <h:outputText value="#{libro.numPaginas}"/>
   			 </h:column>
   			 
   			 <h:column>
   			    <f:facet name="header">
   			       <h:outputText value="Operación"/>
   			    </f:facet>
   			    <h:commandLink value="Información" 
                               action="#{usuarioBean.doDetalleLibro}">
   			       <f:setPropertyActionListener 
                                  target="#{usuarioBean.libroSeleccionado}" 
                                  value="#{libro}" />
   			    </h:commandLink>
   			 </h:column>
	      </h:dataTable>
	      </h:form>
	     </ui:define>
	</ui:composition>
</html>

Acciones del usuario

Las acciones del usuario sobre un libro se lanzan desde la vista usuario/accionLibro.xhtml. Dependiendo del valor de la propiedad estadoLibro del bean de respaldo usuarioBean. Esa propiedad se actualiza desde la acción doDetalleLibro que muestra el libro.

Fichero usuario/accionLibro.xhtml:

<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:c="http://java.sun.com/jsp/jstl/core">

   <ui:composition template="/templates/principal.xhtml">
      <ui:define name="contenido">
         <h:form>
            <ui:include src="/libro/detalleLibro.xhtml"></ui:include>
            <p>El libro se encuentra <h:outputText style="color:red" 
                 value="#{usuarioBean.estadoLibro}" /></p>
            <c:choose>
               <c:when test="#{usuarioBean.estadoLibro == 'DISPONIBLE'}">
                  <!-- Mensaje y enlace para reservar -->
               </c:when>
               <c:when test="#{usuarioBean.estadoLibro == 'RESERVADO-A-USUARIO'}">
                  <!-- Mensaje y enlace para anular reserva -->
               </c:when>
               <c:when test="#{usuarioBean.estadoLibro == 'RESERVADO-A-OTRO'}">
                  <!-- Mensaje con información -->
               </c:when>
            </c:choose>
            <p><h:commandLink action="listado-libros" value="Volver"/></p>
         </h:form>
      </ui:define>
   </ui:composition>
</html>

Bibliotecario

Tenemos que implementar también la parte de la web del bibliotecario, con un listado de todos los libros y la posibilidad de realizar altas, bajas y modificaciones.

La página principal del bibliotecario será un listado de todos los libros, con el menú de opciones a la izquierda que incluye la opción de añadir un libro nuevo. Cada libro puede ser borrado o editado.

El formulario de edición de un libro permitirá modificar los datos de un libro:

El formulario de añadir libro permitirá añadir un libro nuevo:

Incluir algún ejemplo de validación de JSF en alguno de los campos de un libro nuevo o modificado.

Resumen

Proyecto proy-web-jsf Aplicación web