Proyecto de Integración
 

Proyecto Web

Introducción

El objetivo principal de las sesiones del proyecto web es refactorizar la aplicación de la biblioteca para dotarla de una calidad semejante a la de un proyecto empresarial real. Esto se traduce en un subconjunto de actividades:

  • Crear un esquema de navegación apropiado
  • Crear un interfaz de usuario amigable
  • Separar las operaciones de negocio en su propia capa
  • Aplicar una nomenclatura adecuada a todo el proyecto
  • Reforzar los conocimientos adquiridos tanto de Struts como de JSTL
  • Internacionalización de la aplicación en castellano e inglés
  • Mejorar la calidad del código implementado
Aclaración
A lo largo de la sesión nos vamos a dedicar a retocar/reescribir código previamente implementado, pero ¿porqué no lo hemos hecho bien desde el principio? El planteamiento del supuesto inicial, conjunto al desarrollo incremental de cada sesión, fomenta que cada sesión adopte las necesidades del proyecto a las tecnologías empleadas. Ahora es el momento de rehacer gran parte de la aplicación y formar una base estable para futuros desarrollos.

Refactorizando el Login

Vamos a empezar por la puerta de entrada de la aplicación. Nuestro departamento de diseño gráfico nos ha entregado la hoja de estilo que será común para toda la web (que ya tenemos colocada en WebContent/css/style.css) y un conjunto de imágenes (que colocaremos en la carpeta WebContent/imagenes). Además, nos anexan los siguientes pantallazos con la apariencia de la aplicación:

  • Login

Pantalla de Login

Podemos observar 3 bloques importantes: las cajas de login/password, la zona para los mensajes de error al entrar al sistema, y los enlaces para cambiar el idioma de la aplicación.

  • Web del Usuario

Web del Usuario

Página de login

Esta página es la puerta de entrada a la aplicación. La página ya maquetada en html y con los tags de struts, la colocaremos en WebContent/login.jsp. Recordar de añadir las traducciones en el mensajes.properties adecuado.

<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>jTech - <bean:message key="login.titulo"/></title>
<link rel="stylesheet" type="text/css" href="css/style.css" title="800px style" media="screen,projection" />
</head>
<body>
<div style="position:absolute; left:30%; top:10em; width:600px">
<form action="j_security_check" METHOD="POST">
<table style="border: 1px solid #D7D6D6">
  <tr style="height:5em">
    <td style="width: 250px"><h3><bean:message key="login.titulo"/></h3></td>
    <td style="width: 350px"><bean:message key="login.instrucciones"/></td>
  </tr>
  <tr style="vertical-align:bottom; height: 100px">
    <td style="text-align:right; background-color:#D7D6D6">
      <strong><bean:message key="login.login"/></strong>:
    </td>
    <td><input type="text" name="j_username"/></td>
  </tr>
  <tr style="vertical-align:top; height: 100px">
    <td style="text-align:right; background-color:#D7D6D6">
      <strong><bean:message key="login.password"/></strong>:
    </td>
    <td>
      <input type="password" name="j_password"/>&nbsp;
	  <input type="submit" value="<bean:message key="login.entrar"/>"/>
    </td>
  </tr>
  <tr>
    <td colspan="2" style="text-align: right">
    <html:link page="/CambiaIdioma.do?idioma=es">
      <bean:message key="login.espanol" /></html:link>&nbsp;
    <html:link page="/CambiaIdioma.do?idioma=en">
      <bean:message key="login.ingles" /></html:link></td>
  </tr>
</table>
</form>
	
</div>
</body>
</html>

Para añadir el mensaje de error, utilizaremos una nueva fila que contenga una capa con id="box":

<div class="box">Mensaje de error en idioma correspondiente</div>

Problemas de la Seguridad Declarativa

Muchas aplicaciones web colocan el formulario de login en cada página pública que es accesible, normalmente en la parte superior de la página, ya sea a izquierda o derecha. Pero con la seguridad declarativa, el contenedor solo puede referenciar al login desde la página fijada con login-config.

Del mismo modo, no podemos controlar mensajes de error al realizar el login. Por esto, la solución planteada para mostrar los mensajes de error no es la más elegante, ya que el uso de la seguridad declarativa reduce el control que tenemos sobre el comportamiento de la aplicación.

Sin Seguridad Declarativa
Si no utilizásemos la seguridad declarativa, al hacer login, introduciríamos dentro de la sesión algún atributo (lo más ligero objeto, por ejemplo, el login del usuario y el rol del usuario), el cual comprobaríamos que existe para cada acción que requiera seguridad. Para evitar la redundancia en múltiples acciones, sobreescribiríamos parte del controlador, en concreto el método processRoles de la clase RequestProcessor. Más información en www.devarticles.com/c/a/Java/Securing-Struts-Applications/
Optativo I
Si duplicamos las páginas de login.jsp y errorLogin.jsp, al realizar una modificación en una de ellas, tendremos que volver a modificar la otra. Realiza fragmentos de página intermedios y únelos mediante includes.

LoginAccion

Una vez el usuario y la contraseña es correcta, en vez de redirigirlo directamente al Action de listado de libros, vamos a refactorizarlo y crear un nuevo Action que almacene el rol en la sesión, y dependiendo de éste, redirija a una vista u otra, de modo que la parte de 'Controlador' se haga aqui conjunto a su ActionMapping.

public class LoginAccion extends Action {
  private static Log logger = LogFactory.getLog(LoginAccion.class.getName());

  @Override
  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse res)
      throws Exception {

    ActionForward forward = null;

    String login = request.getUserPrincipal().getName();
    
    IUsuarioDao iud = FactoriaDaos.getInstance().getUsuarioDao();
    UsuarioDomain usuario = iud.getUsuario(login);
		
    // Metemos el rol en la sesion para pintarlo en la cabecera
    request.getSession().setAttribute(Tokens.SES_ROL, usuario.getTipo().toString());
    logger.debug("Login con usuario " + usuario);

    if (request.isUserInRole(TipoUsuario.ADMINISTRADOR.toString())) {
      forward = mapping.findForward(Tokens.FOR_ADMIN);
    } else if (request
        .isUserInRole(TipoUsuario.BIBLIOTECARIO.toString())) {
      forward = mapping.findForward(Tokens.FOR_BIBLIO);
    } else if (request.isUserInRole(TipoUsuario.ALUMNO.toString())
        || request.isUserInRole(TipoUsuario.PROFESOR.toString())) {
      if (usuario.getEstado() == EstadoUsuario.MOROSO) {
        forward = mapping.findForward(Tokens.FOR_MOROSO);
      } else {
        forward = mapping.findForward(Tokens.FOR_SOCIO);
      }
    } else {
      forward = mapping.getInputForward();
    }
    return forward;
  }
}

Como podemos observar, tenemos diferentes forwards, que posteriormente definimos en el archivo struts-config.xml:

<action path="/Login"
  type="org.especialistajee.jbib.struts.acciones.LoginAccion" roles="BIBLIOTECARIO,PROFESOR,ALUMNO">
  <forward name="indexAdmin" path="/jsp/error.jsp" />
  <forward name="indexBiblio" path="/LibroListTodosBiblio.do" />
  <forward name="indexSocio" path="/LibroListTodosSocio.do" />
  <forward name="indexMoroso" path="/jsp/usuario/moroso.jsp" />
</action>

Web de Bibliotecario

Respecto a la web del bibliotecario, únicamente nos vamos a centrar en mejorar su interfaz gráfico.

Una vez nos hemos autenticado, y nuestro controlador nos lleva a la página adecuada al rol autenticado (el cual hemos de mostrar al usuario), hemos de mostrar un menú con las posibles opciones a realizar, así como ofrecer la posibilidad de invalidar la sesión.

Menú del Bibliotecario

El esquema de las páginas html de los tres roles es similar, siendo el menú de opciones lo único que cambian, y a partir de las opciones mostradas, las diferentes acciones que puede realizar un determinado rol.

El equipo de maquetación nos entrega el código html de la web del bibliotecario (para facilitar el trabajo, un programador ha "mejorado" la navegación, y tenemos la página separada en fragmentos de página para separar y reutilizar tanto el pie como la cabecera). Este esquema ya lo hemos utilizado desde el proyecto de jsp y con Struts.

Plantillas
A la hora de implementar una aplicación de este tipo, el uso de un gestor de plantillas, del tipo de Tiles (tiles.apache.org), SiteMesh (www.opensymphony.com/sitemesh) o Velocity (velocity.apache.org), facilita mucho el desarrollo, minimizando la cantidad de código a implementar y la redundancia de HTML.

/jsp/cabecera.jspf

La cabecera va a mostrar el logo de la aplicación, así como qué usuario ha entrado al sistema, y cual es el rol de dicho usuario. Dentro de la cabecera también se ofrece la posibilidad de cerrar la sesión, mediante la llamada a un action, el cual deberá invalidar el objeto Session de la request.

El siguiente código lo colocaremos en /jsp/cabecera.jspf

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%>
<%@ page import="org.especialistajee.jbib.struts.Tokens"%>

<table width="100%">
  <tr>
    <td><h1><bean:message key="cabecera.titulo"/></h1></td>
    <td>&nbsp;</td>
    <td align="right"><html:img page="/imagenes/banner-java.gif" /></td>
  </tr>
  <tr>
    <td colspan="3" align="right"><html:link action="Logout">
      <bean:message key="cabecera.cerrarSesion" /> &raquo;</html:link></td>
  </tr>
  <tr>
    <td colspan="3" align="right"><bean:message key="cabecera.usuario" />:
      <strong><%=request.getUserPrincipal().getName()%></strong>
      - <bean:message key="cabecera.rol" />:
      <strong><%=((String) session.getAttribute(Tokens.SES_ROL)) .toLowerCase()%></strong>
    </td>
  </tr>
</table>

Para poder obtener el rol, en el action del login, previamente deberemos haber guardado dicho rol dentro de la sesión.

/jsp/pie.jspf

En el pie, únicamente vamos a poner una firma. El siguiente código lo colocaremos en /jsp/pie.jspf

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

/jsp/biblio/menu.jspf

El menú va a depender de cada rol existente, y por tanto, vamos a repetir este archivo con diferentes opciones tanto para los usuarios bibliotecario, como los administradores, socios y registrados. El siguiente código lo colocaremos en /jsp/biblio/menu.jspf

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<div id="sidebar">
<h2><bean:message key="mbiblio.libros"/></h2>

<fieldset><legend><bean:message key="mbiblio.listados"/></legend>
<ul>
  <li><html:link action="/LibroListTodosBiblio">
    <bean:message key="mbiblio.todos"/></html:link></li>
</ul>
</fieldset>

<fieldset><legend><bean:message key="mbiblio.gestion"/></legend>
<ul>
  <li><html:link action="/LibroPreAdd">
    <bean:message key="mbiblio.alta"/></html:link></li>
</ul>
</fieldset>

</div>

Alta de Libros

La opción de alta de libros está accesible desde el menú del rol bibliotecario. Tal como habeis hecho en la sesiones anteriores, tendremos un Action para la precarga donde haremos un saveTokens para evitar el submit doble. Tras ello, mostraremos un formulario para dar de alta un libro, similar al siguiente pantallazo:

Alta de Libro

/jsp/biblio/altaLibro.jsp

El equipo de maquetación nos envía el siguiente jsp con los tags de Struts, el cual colocaremos en /jsp/biblio/altaLibro.jsp. Fijaros que ya está activada la validación en Javascript del formulario mediante el Validator de Struts:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html:html>
<head>
<html:base />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><bean:message key="altaLibro.titulo" /></title>
<link rel="stylesheet" type="text/css" href="<html:rewrite page="/css/style.css" />"
    title="800px style" media="screen,projection" />
</head>
<body>

<div id="wrap"><%@include file="/jsp/cabecera.jspf"%>
<%@include file="/jsp/biblio/menu.jspf"%>

<div id="content">

<h2><bean:message key="altaLibro.titulo" /></h2>

<html:form action="/libroAdd">
<bean:message key="altaLibro.introISBN" />
<fieldset><legend><bean:message key="altaLibro.datosLibro" /></legend>
    <table>
        <tr>
            <td><bean:message key="libro.ISBN" />:</td>
            <td><html:text property="isbn" /> <html:errors property="isbn" /></td>
        </tr>
    </table>
</fieldset>
<bean:message key="altaLibro.introDatos" />
<fieldset><legend><bean:message key="altaLibro.datosPersonales" /></legend>
    <table>
        <tr>
            <td><bean:message key="libro.titulo" />:</td>
            <td><html:text property="titulo" /> <html:errors
                property="titulo" /></td>
        </tr>
        <tr>
            <td><bean:message key="libro.autor" />:</td>
            <td><html:text property="autor" /> <html:errors property="autor" /></td>
        </tr>
        <tr>
            <td><bean:message key="libro.paginas" />:</td>
            <td><html:text property="numPaginas" /> <html:errors
                property="numPaginas" /></td>
        </tr>
        <tr>
            <td></td>
            <td><html:submit>
                <bean:message key="mbiblio.alta" />
            </html:submit></td>
        </tr>
    </table>
</fieldset>
</html:form>
<br />
</div>

<%@include file="/jsp/pie.jspf"%></div>

</body>
</html:html>

Tras insertar un libro, vamos a ir al listado de todos los libros, mostrando un mensaje de que el libro se ha insertado correctamente.

Mensaje tras Alta de Libro

Edición y Borrado de Libros

La edición de un libro se basa en el mismo formulario que el de alta, con la diferencia que el campo ISBN no va a ser editable. En cuanto al borrado, al pulsar sobre la papelera, debería pedirle confirmación al usuario, mediante una validación en Javascript (optativo)

En ambas operaciones, una vez realizada la acción, se devolverá el control al listado de todos los libros, informando al usuario que la edición o el borrado se ha realizado con éxito.

Web del Usuario

Una vez un usuario entra a la aplicación, ya sea un alumno o un profesor, va a poder visualizar los libros existentes, y una vez seleccionado uno, dependiendo del estado de libro, realizar o anular una reserva. Además, podrá visualizar todas las operaciones que ha realizado, ya sean reservas o prestamos.

El interfaz gráfico va a ser similar al mostrado anteriormente, siendo las opciones de su menú las siguientes:

Menú de un usuario

Optativo II - Buscador
Se plantea como ejercicio optativo la realización del buscador. El buscador devolverá un listado de libros (aprovechando que la vista será la misma que el listado de todos los libros) con aquellos libros cuyo isbn, titulo o autor coincide con parte del texto introducido en el cuadro de texto.
En el caso de que el resultado de la búsqueda sea un único libro, debe directamente redirigirlo a la página de información sobre el libro en cuestión.

Listado de Mis Libros

Respecto al listado de mis libros, hay que mostrar la siguiente información:

Listado de Mis Libros

Listado de Libros

El listado de libros es el lugar que va a desencadenar las acciones, pudiéndose realizar cualquier operación sobre un determinado libro (de momento, para un biblioteca únicamente editar y borrar, y para un usuario, visualizar el libro y realizar o anular una reserva).

LibroListTodosAccion.java

En cuanto al Actin de listados, ya no hacemos referencia al usuario que esta en el sistema, la operación es la misma, sólo cambia la vista, y por tanto, esto es decisión del controlador, no del modelo.

public class LibroListTodosAccion extends Action {
  static Log logger = LogFactory.getLog(LibroListTodosAccion.class);

  @Override
  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    ActionForward result = null;

    try {
      ILibroDao ild = FactoriaDaos.getInstance().getLibroDao();
      List<LibroDomain> lista = ild.getLibros();
      request.setAttribute(Tokens.RES_LIBROS, lista);

      result = mapping.findForward(Tokens.FOR_OK);

    } catch (BibliotecaException ex) {
      ActionMessages am = new ActionMessages();
      am.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage(
          Tokens.MSJ_LIBRO_LIST_ERROR, ex));
      saveErrors(request, am);
      logger.error("Error obteniendo el listado de libros: " + ex, ex);
      result = mapping.findForward(Tokens.FOR_ERROR);
    }
    return result;
  }
}

Para poder reutilizar el Action dependiendo del usuario, lo que se hace es crear 2 ActionMappings, que referencian al mismo Action, pero que luego redirigen a diferentes vistas. Así pues, en el struts-config.xml tendremos:

<action path="/LibroListTodosSocio"
	type="org.especialistajee.jbib.struts.acciones.libro.LibroListTodosAccion"
	roles="PROFESOR,ALUMNO">
	<forward name="OK" path="/jsp/usuario/listadoLibros.jsp" />
</action>
<action path="/LibroListTodosBiblio"
	type="org.especialistajee.jbib.struts.acciones.libro.LibroListTodosAccion"
	roles="BIBLIOTECARIO">
	<forward name="OK" path="/jsp/biblio/listadoLibros.jsp" />
</action>
¿Negocio en el Action?
Si el Action es el encargado de llamar al DAO, a la hora de realizar una reserva, entonces deberá obtener la fecha actual (fecha de inicio) y dependiendo del estado del usuario, a partir del número de días que tiene derecho, calcular la fecha de fin. Previamente, deberá obtener el número de reservas o prestamos activos, para comprobar si ya tiene completo el cupo de libros. ¿Y todo esto lo colocamos en lo que llamamos controlador?

Capa de Negocio

Ya hemos visto cómo estamos poniendo lógica de negocio en nuestros Actions. Por ejemplo, al hacer una inserción de un libro, el Action le pone la fecha de alta al libro asignandole la fecha actual, o cuando hacemos una reserva/préstamo, el Action se está encargando obtener la fecha actual, y dependiendo del rol, poner la fecha de finalización. Todo esto es lógica de negocio que debe residir en una capa aparte.

Para ello, vamos a crear un POJO (Plain Old Java Object) que simule el patrón Business Object por cada subsistema. Este BO, cuando la aplicación pase a ser distribuida, se convertirá en el delegador del negocio (patrón Business Delegate). Para ello, del mismo modo que tenemos una factoría que hace de puerta de entrada a la capa de los datos, vamos a añadir otra factoría que nos sirve para entrar al negocio.

Capa de Negocio

Proyecto y Paquete
La capa de negocio es independiente de la capa de presentación, y por tanto no tenemos porque crear estos objetos en el proyecto web. Tiene mucho más sentido crearlos en un nuevo proyecto, al que denominaremos jbib-negocio.
Además, colocaremos todas las clases dentro del paquete org.especialistajee.jbib.bo

Recordar que para crear el proyecto lo haremos desde Maven, con el siguiente comando:

$ cd /home/especialista/proy-int
$ mvn archetype:generate 
-DarchetypeArtifactId=maven-archetype-quickstart 
-DgroupId=org.especialistajee.proyint -DartifactId=jbib-negocio 
-Dversion=1.0 -Dpackage=org.especialistajee.jbib.bo

Por lo tanto, la dependencia de nuestros proyectos será similar a la del siguiente diagrama:

Dependencias de proyectos

Como ejemplo, vamos a refactorizar el Action de LibroAddAccion de modo que llame a la capa de negocio y le ceda el control.

ILibroBO.java

Así pues, primero crearemos el interfaz con las operaciones de negocio

package org.especialistajee.jbib.bo.libro;

// imports

/**
 * Operacion de Negocio de los Libros
 * 
 * @author $Author: amedrano $
 * @version $Revision: 1.22 $
 */
public interface ILibroBO {

    /**
     * Selecciona un libro de la BD
     * 
     * @param isbn Isbn del libro a seleccionar
     * @return Libro seleccionado
     * @throws LibroException
     */
    LibroDomain recuperaLibro(String isbn) throws LibroException;

    /**
     * Añade un libro en la BD
     * 
     * @param libro Libro a añadir.
     * @throws LibroException
     */
    void anyadeLibro(LibroDomain libro) throws LibroException;

    /**
     * El libro pasado por parámetro es borrado de la BD
     * 
     * @param isbn libro a eliminar
     * @return Número de registros afectados por el borrado
     * @throws LibroException
     */
    void eliminaLibro(String isbn) throws LibroException;

    /**
     * Devuelve una lista con todos los libros en la BD
     * 
     * @return Lista con todos los libros
     * @throws LibroException
     */
    List<LibroDomain> listaLibros() throws LibroException;

    /**
     * Modifica los datos de un libro
     * 
     * @param libro Libro a modificar
     * @throws LibroException
     */
    void actualizaLibro(LibroDomain libro) throws LibroException;
}

De este código podemos observar cómo los métodos lanzan la excepción LibroException. Hemos creado una excepción de aplicación para abstraer las capas posteriores del negocio. ¡La presentación no debe saber que hay detrás del lado oscuro!

LibroException.java

Así pues, por cada subsistema crearemos una o más excepciones de aplicación.

package org.especialistajee.jbib.bo.libro;

import org.especialistajee.jbib.BibliotecaException;

public class LibroException extends BibliotecaException {

    private static final long serialVersionUID = 6158132279964132104L;
    
    public LibroException() {
        super();
    }
    
    public LibroException(String message) {
        super(message);
    }
    
    public LibroException(String message, Throwable cause) {
        super(message, cause);
    }
}

LibroBO.java

A partir de la interfaz, crearemos la implementación, en la cual colocaremos las reglas de negocio y realizará las llamadas a la capa de datos.

package org.especialistajee.jbib.bo.libro;

// imports ...

/**
 * Operaciones de Negocio de los Libros
 * 
 * @author $Author: amedrano $
 * @version $Revision: 1.22 $
 */
public class LibroBO implements ILibroBO {

    public void actualizaLibro(LibroDomain libro) throws LibroException {
        if (libro == null) {
            throw new IllegalArgumentException("Se esperaba un libro");
        }

        ILibroDao dao = FactoriaDaos.getInstance().getLibroDao();

        try {
            int numAct = dao.updateLibro(libro);
            if (numAct == 0) {
                throw new LibroException("No ha actualizado ningun libro");
            }
        } catch (DaoException daoe) {
            throw new LibroException("Error actualizando libro", daoe);
        }
    }

    public void anyadeLibro(LibroDomain libro) throws LibroException {
        if (libro == null) {
            throw new IllegalArgumentException("Se esperaba un libro");
        }

        ILibroDao dao = FactoriaDaos.getInstance().getLibroDao();

        try {
            libro.setFechaAlta(new Date());
            dao.addLibro(libro);
        } catch (DaoException daoe) {
            throw new LibroException("Error insertando libro", daoe);
        }

    }

    // resto de métodos

    public List<LibroDomain> listaLibros() throws LibroException {
        List<LibroDomain> result = null;
        ILibroDao dao = FactoriaDaos.getInstance().getLibroDao();

        try {
            result = dao.getLibros();
        } catch (DaoException daoe) {
            throw new LibroException("Error listando libros", daoe);
        }

        return result;
    }

}

La capa de negocio debe funcionar como puerta de entrada a la inteligencia de la aplicación, y por lo tanto, no debe dar por sentado que los datos de entrada son correctos. Al menos se debe comprobar que los parámetros llegan con valores rellenados, y dependiendo de la robustez deseada, duplicar algunas de las validaciones realizadas en los ActionForm.

Factoría de BOs
Del mismo modo que con los DAOs, hemos de crear una factoría de BOs encargada de desacoplar la creación de las implementaciones de sus interfaces.

LibroAddAccion.java

Como ejemplo, refactorizaremos este Action llevando la lógica de negocio a su correspondiente capa. Dentro del método execute teníamos:

// ...
// Recogemos los datos del formulario
LibroDomain libro = new LibroDomain();
BeanUtils.copyProperties(libro, (LibroForm) form);
libro.setFechaAlta(new Date());

// Anyadimos el libro
ILibroDao dao = FactoriaDaos.getInstance().getLibroDao();
dao.addLibro(libro);
// ...

Al refactorizar y llevar la asignación de la fecha de alta y la invocación al DAO, nos queda lo siguiente:

// ...
LibroDomain libro = new LibroDomain();
BeanUtils.copyProperties(libro, (LibroForm) form);

// Anyadimos el libro
ILibroBo bo = FactoriaBos.getInstance().getLibroBo();
bo.anyadeLibro(libro);
// ...

Como ejercicio, se plantea refactorizar toda la aplicación para incluir la capa de negocio. Todas las comprobaciones que no sean de entradas de un Action se deben realizar en negocio.

Reserva de un Libro

Para comprobar el uso de la capa de negocio, vamos a repasar el flujo de llamadas. Para realizar una reserva desde el rol bibliotecario, primero hemos de elegir que libro se quiere reservar, para posteriormente, en el caso de que sea posible, realizar la reserva.

Datos de un libro disponible

En esta página se muestran los datos de libro que hemos seleccionado previamente, y a continuación, dependiendo de las operaciones que estén activas, el usuario podrá o no realizar la reserva.

La reglas de negocio a la hora de realizar una reserva son:

  1. Si el libro esta disponible, se puede reservar.
  2. Si el libro esta reservado por otro usuario, me mostrará los días que quedan para que finalice la reserva.
  3. Si el libro esta reservado por mi, me mostrará los días que me quedan antes de finalizar la reserva, y ofrecerá la opción de anular la reserva.
  4. Si el libro está prestado, y no tiene ninguna reserva, también lo podemos reservar.
  5. Si el libro está prestado y también reservado, permitirá las mismas opción que las reglas 2 y 3.
  6. Si el libro está en deposito, permitirá reservarlo, y en el proceso de reservarlo, le cambiará el estado a la operación de deposito a prestado, envíandole un mail al profesor indicándole que su deposito ha finalizado y que debe devolver el libro cuanto antes.

Tras la realización de cualquiera de estás operaciones, hay que mostrar un mensaje al usuario informándole de la operación realizada.

Datos de un libro disponible

IOperacionBO

Las operaciones que tenemos que implementar en el proyecto web son las siguientes:

package org.especialistajee.jbib.bo.operacion;

import org.especialistajee.jbib.BibliotecaException;

/**
 * Reglas de negocio sobre operaciones de libros.
 * 
 * $Author: amedrano $ $Date: 2011/01/16 16:13:48 $ $Revision: 1.22 $
 */
public interface IOperacionBo {

  /**
   * Realiza la reserva de un libro por un usuario
   * 
   * @param login
   *            Usuario que realiza la reserva
   * @param isbn
   *            Libro a reservar
   * @return Devuelve el identificador de la operacion
   * @throws DAOException
   */
  String realizaReserva(String login, String isbn) throws OperacionException,
      OperacionCupoCompletoException;

  /**
   * Anula la reserva de un libro
   * 
   * @param login
   *            Login del usuario que anula la reserva el libro
   * @param isbn
   *            ISBN del libro a reservar
   * @return Resultado de la operacion
   * @throws BibliotecaException
   */
  void anulaReserva(String idOperacion, String login)
      throws OperacionException;

  /**
   * Realiza la reserva de un libro por un usuario, pero a partir de un libro
   * que previamente estaba en deposito
   * 
   * @param idOperacion
   *            Operacion con el deposito del libro
   * @param login
   *            Usuario que realiza la reserva
   * @return Devuelve el identificador de la operacion
   * @throws DAOException
   */
  String realizaReservaDesdeDeposito(String idOperacion, String login)
      throws OperacionException, OperacionCupoCompletoException;
}

Resto de Operaciones

En cuanto al resto de operaciones, vamos a detallar en una tabla las tareas que conlleva cada operación:

Anular Reserva
Eliminar la operación, siempre y cuando el usuario que anula la reserva es el propietario de la misma.
Realizar Reserva desde Deposito
Tras crear la reserva para el nuevo usuario, cambia el estado de la operación de deposito a prestamo, y envia un mail al profesor informando de la caducidad de su deposito.

Para poder enviar emails, crearemos una clase de utilidades en el proyecto común:

package org.especialistajee.jbib;

// imports

public class BibliotecaUtils {

  public static void envioMail(String emailDestino, String asunto,
      String texto) {

    Properties props = new Properties();
    props.setProperty("mail.smtp.host", "mail.alu.ua.es");
    props.setProperty("mail.smtp.starttls.enable", "true");
        props.setProperty("mail.smtp.auth", "false");

    try {
      // Preparamos la sesion
      Session session = Session.getDefaultInstance(props);

      // Construimos el mensaje
      MimeMessage message = new MimeMessage(session);
      message.setFrom(new InternetAddress("admin@especialistajee.org"));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(
          emailDestino));
      message.setSubject(asunto);
      message.setText(texto);

      // Lo enviamos.
      Transport t = session.getTransport("smtp");
      t.connect();
      t.sendMessage(message, message.getAllRecipients());

      // Cierre.
      t.close();
    } catch (MessagingException e) {
      e.printStackTrace();
      throw new BibliotecaException("Error al enviar email", e);
    }
  }
}

Para que el proyecto comun pueda enviar correos, añadiremos las siguientes dependencias al archivo pom.xml:

<dependency>
  <groupId>javax.activation</groupId>
  <artifactId>activation</artifactId>
  <version>1.1</version>
</dependency>
<dependency>
  <groupId>javax.mail</groupId>
  <artifactId>mail</artifactId>
  <version>1.4</version>
</dependency>
Queda pendiente...
Un parte importante es la anulación automática de las reservas. Para ello necesitamos la ejecución periódica de proceso que compruebe que reservas han caducado, y consecuentemente, anule dichas reservas. Veremos como podemos hacer esto en el módulo de EJB.

Guia de Estilo

Toda la aplicación debe hacer un uso consistente de los taglibs de Struts y/o JSTL, tanto a nivel de formularios con sus correspondientes ActionForms como referencias a enlaces, tratamiento de errores, etc...

Todas las clases Java deben contener una cabecera Javadoc indicando el propósito de la clase, y los atributos javadoc de author y revision con los valores del CVS. Por ejemplo, en la interfaz Tokens.java tenemos:

/**
 * Interfaz con las constantes utilizadas dentro de los actions
 * 
 * @author $Author: amedrano $
 * @version $Revision: 1.22 $
 */

Siempre que sea posible, se minimizará el acoplamiento entre las diferentes capas y objetos, pasando como parámetros el menor número de información posible.

Además, el proyecto deberá incluir únicamente el código fuente de la funcionalidad desarrollada. Todo los desarrollos previos (Servlets, AccionPrueba, etc...) debe ser eliminados del proyecto.

No debe haber ningún mensaje de error o advertencia en la pestaña Problems de Eclipse (excepto el de aviso del uso de librerías en el proyecto común). Para ello, se debe revisar que todo el código es correcto, así como objetos declarados y no utilizados, imports de sobra, etc...

Entrega

La entrega se realizará por parejas. Si alguien quiere implementar la aplicación de forma individual, no se valorará el trabajo extra. El objetivo del proyecto es mejorar la calidad, trabajar en equipo (modelado ágil y programación en parejas), y aprender. Recordar que 2 cerebros son mejor que 1 :)

Al finalizar el proyecto web, deben quedar implementadas las webs del bibliotecario y del usuario, en ambas con el ciclo completo de Action -> Capa de Negocio -> DAO con JDBC.

Proyecto de Negocio

A continuación se muestra un imagen del contenido a entregar dentro del proyecto común.

Contenido Proyecto Común

Proyecto Web

Para este proyecto, mostramos 2 imágenes, una con el contenido del código Java y la otra con el contenido web.

Contenido Java Proyecto Web Contenido Web Proyecto Web

Se plantea como optativos aquellos ejercicios planteados en rojo a lo largo del presente documento.