Proyecto de Integración
 

Capa de negocio con JPA

Introducción

Una vez mapeadas las entidades y creados los EAO con los que recupararlas y actualizarlas crearemos en esta sesión las clases de negocio (business object) que contengan los métodos de negocio que permitan trabajar con la biblioteca:

  • Operaciones: listar libros disponibles, listar libros reservados, reservar libros, ...
  • CRUD de libros: creación, recuperación, actualización y borrado

Estas clases de negocio son muy importantes, porque definen las funcionalidades que exportamos a otras capas, como la capa web. Los objetos que devuelven son objetos de dominio desconectados de la base de datos. Reciben como parámetro datos elementales relacionados con la operación (un número de operación, o un login de usuario por ejemplo). Las clases de negocio encapsulan toda la funcionalidad y de nuestra aplicación y proporcionan un front-end de la capa de persistencia.

Todos los métodos de las clases de negocio son transaccionales. Si el método termina correctamente estaremos seguros de que todas las actualizaciones se han realizado en la base de datos. Si algo falla en el método, se devolverá una excepción y se hará un rollback del estado de la base de datos, de forma que todo volverá al estado previo a la realización de la llamada al método.

La capa de negocio se implementará en un nuevo proyecto llamado jbib-negocio (que dependerá del anterior jbib-modelo) y trabajará sobre una nueva base de datos, llamada biblioteca. Este esquema de base de datos será también el que utilizaríamos en producción. En la sesión de hoy vamos a crearlo y a poblarlo con datos que para probar la capa de negocio.

Debido a la gran cantidad de código a desarrollar y al poco tiempo que tenemos para hacerlo, vamos a proporcionar la mayor parte de las clases de la aplicación. Es importante que no sólo copies el código, sino que también lo leas y reflexiones sobre su funcionamiento y estructura. Dejaremos como ejercicio la implementación de algunas clases y métodos. También propondremos posibles amplicaciones y nuevas funcionalidades.

Proyecto jbib-negocio

Empezamos creando el nuevo proyecto jbib-negocio que va a contener todas las clases e interfaces relacionadas con la lógica de negocio.

En el espacio de trabajo del proyecto de integración crea un nuevo proyecto jbib-negocio. Escoge la opción de Eclipse New > Project... > Maven > Maven Module, marcando la opción de Create simple project e introduciendo:

  • Module Name: jbib-negocio
  • Parent Project: proyint-jbib
  • Name: jbib-negocio

En el editor del POM, añade la dependencia con el proyecto jbib-modelo.

Base de datos biblioteca

Creación de la base de datos de producción

Vamos a crear el esquema de base de datos biblioteca a partir del esquema biblioteca_test con las tablas creadas por JPA en el proyecto jbib-modelo. Esta base de datos será la base de datos de producción y nuestra copia local contendrá los datos que comprobarán la lógica de negocio.

La base de datos tiene que tener las tablas creadas por JPA a partir de la defíción de las clases de dominio. Podemos hacerlo mediante un backup del esquema de base de datos biblioteca_test con el programa MySQL Administrator. Usa la opción backup y selecciona la base de datos y sus tablas. En Advanced Options selecciona la opción Add DROP Statemets para que se incluyan las sentencias DROP que eliminan las tablas y sus datos antes de crearlas.

Escribe como nombre de fichero biblioteca.sql. Edítalo, reemplaza todas las referencias a biblioteca_test por biblioteca y elimina todos los INSERT de los datos. Así tendremos un fichero de instrucciones SQL que crea todas las tablas de la base de datos con la que vamos a ejecutar la aplicación. Debe ser tener esta forma:

CREATE DATABASE IF NOT EXISTS biblioteca;
USE biblioteca;
DROP TABLE IF EXISTS `biblioteca`.`libro`;
CREATE TABLE  `biblioteca`.`libro` (
  `idLibro` bigint(20) NOT NULL AUTO_INCREMENT,
  `autor` varchar(255) DEFAULT NULL,
  `fechaAlta` datetime DEFAULT NULL,
  `isbn` varchar(255) DEFAULT NULL,
  `numPaginas` int(11) DEFAULT NULL,
  `titulo` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`idLibro`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

DROP TABLE IF EXISTS `biblioteca`.`prereserva`;
CREATE TABLE  `biblioteca`.`prereserva` (
  `idOperacion` bigint(20) NOT NULL AUTO_INCREMENT,
  `fechaInicio` datetime NOT NULL,
  `libro` bigint(20) DEFAULT NULL,
  `usuario_idUsuario` bigint(20) NOT NULL,
  PRIMARY KEY (`idOperacion`),
  KEY `FK23D5ACB584157C2B` (`usuario_idUsuario`),
  KEY `FK23D5ACB57C13B4DF` (`libro`),
  CONSTRAINT `FK23D5ACB57C13B4DF` FOREIGN KEY (`libro`) REFERENCES `libro` (`idLibro`),
  CONSTRAINT `FK23D5ACB584157C2B` FOREIGN KEY (`usuario_idUsuario`) 
                 REFERENCES `usuario` (`idUsuario`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

DROP TABLE IF EXISTS `biblioteca`.`multa`;
CREATE TABLE  `biblioteca`.`multa` ...

DROP TABLE IF EXISTS `biblioteca`.`operacion`;
CREATE TABLE  `biblioteca`.`operacion` ...

DROP TABLE IF EXISTS `biblioteca`.`usuario`;
CREATE TABLE  `biblioteca`.`usuario` ...

Ejecuta este fichero SQL para crear el nuevo esquema en la base de datos local. Puedes hacerlo de dos formas:

  • Con la aplicación MySQL QueryBrowser, usando la opción File > Open Script... y después seleccionando Execute
  • Desde línea de comandos:
    mysql -u root -p < biblioteca.sql
    Nos pedira la contraseña: especialista

Para poblar estas tablas con datos de prueba descarga este fichero con el nombre de datos.sql y ejecútalo de cualquiera de las formas anteriores. Comprueba con MySQL QueryBrowser que los datos se han cargado correctamente.

Automatización de la creación del esquema en el POM

Cada vez que se quieran lanzar los tests que van a probar la capa de negocio es necesario hacer el proceso anterior:

  1. Crear el esquema de base de datos con las tablas vacías
  2. Poblar de datos las tablas

Vamos a modificar el POM del proyecto para automatizar el proceso y haciendo que Maven ejecute este proceso siempre que vaya ha ejecutar los tests del proyecto.

Para ello debes copiar los ficheros biblioteca.sql y datos.sql en la carpeta src/test/resources/sql del proyecto jbib-negocio

Y copiar el siguiente código en el fichero POM. En él se carga el plug-in sql-maven-plugin, se cofiguran las conexiones y se definen los dos ficheros SQL que queremos ejecutar:

<project>
<dependencies>...</dependencies>

<build>
  <plugins>
    <plugin>    
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>sql-maven-plugin</artifactId>
      <version>1.4</version>
 
      <dependencies>
        <!-- specify the dependent JDBC driver here -->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.0.8</version>
        </dependency>
      </dependencies>
 
      <!-- common configuration shared by all executions -->
      <configuration>
        <driver>com.mysql.jdbc.Driver</driver>
        <url>jdbc:mysql://localhost:3306/</url>
        <username>root</username>
        <password>especialista</password>
      </configuration>
 
      <executions>
        <execution>
          <id>create-db</id>
          <phase>process-test-resources</phase>
          <goals>
            <goal>execute</goal>
          </goals>
          <configuration>
            <srcFiles>
              <srcFile>src/test/sql/biblioteca.sql</srcFile>
            </srcFiles>
          </configuration>
        </execution>
 
        <execution>
          <id>create-data</id>
          <phase>process-test-resources</phase>
          <goals>
            <goal>execute</goal>
          </goals>
          <configuration>
            <srcFiles>
              <srcFile>src/test/sql/datos.sql</srcFile>
            </srcFiles>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
</project>

Prueba a lanzar los scripts haciendo que Maven ejecute los tests del proyecto. Por ahora no hay ninguno, pero se ejecutarán los scripts anteriores. Comprueba que el esquema se ha creado correctamente y que se ha poblado con los datos de prueba.

Unidad de persistencia y singleton PersistenceManager

La unidad de persistencia definida en el fichero persistence.xml (en el proyecto jbib-modelo) contiene únicamente la conexión a la base de datos de test. Tenemos que crear una nueva unidad de persistencia que se conecte al nuevo esquema biblioteca.

Crea una nueva unidad de persistencia igual que la ya existente, pero con el nuevo nombre proyint y usando la base de datos biblioteca. Cambia también el valor de la propiedad hibernate.hbm2ddl.auto a validate:

<property name="hibernate.hbm2ddl.auto" value="validate" />

Creamos ahora una clase singleton en jbib-negocio que encapsule la conexión a esta unidad de persistencia y la creación de entity managers. Será una clase de utilidad que utilizaremos en la implementación de los objetos de negocio. Creamos por tanto la clase en el nuevo proyecto jbib-negocio, en concreto en el paquete org.especialistajee.jbib.eao.

En el método de creación del singleton obtenemos la factoría de entity managers y la guardamos en una variable. Definimos en el singleton un método createEntityManager que devuelve un nuevo entity manager obtenido a partir de esta factoría. Definimos también el método setEntityManagerFactory para dar flexibilidad a la clase y poder inyectar en ella una nueva factoría de entity managers. De esta forma podríamos cambiar en tiempo de ejecución la unidad de persistencia con la que trabaja la aplicación.

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;
   private EntityManagerFactory emf = null;

   private PersistenceManager() {
      if (emf == null) {
         emf = Persistence
               .createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
         this.setEntityManagerFactory(emf);
      }
   }

   public static PersistenceManager getInstance() {
      if (me == null) {
         me = new PersistenceManager();
      }
      return me;
   }
   
   public void setEntityManagerFactory(EntityManagerFactory myEmf) {
      emf = myEmf;
   }

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

Por último, crea el siguiente test que comprueba que el singleton funciona correctamente:

package org.especialistajee.jbib.persistence;

import static org.junit.Assert.*;

import javax.persistence.EntityManager;
import org.junit.Test;

public class PersistenceManagerTest {
   @Test
   public void obtenerEntityManagerTest() {
      EntityManager em = PersistenceManager.getInstance().createEntityManager();
      em.getTransaction().begin();
      assertTrue(em.isOpen());
      em.getTransaction().commit();
      em.close();
      assertFalse(em.isOpen());
   }
}

Capa de lógica de negocio

Vamos ahora a definir las clases que implementan la lógica de negocio. Tal y como hemos comentado, estas clases de negocio son muy importantes, porque definen las funcionalidades que exportamos a otras capas, como la capa web. Los objetos que devuelven son objetos de dominio desconectados de la base de datos. Reciben como parámetro datos elementales relacionados con la operación (un número de operación, o un login de usuario por ejemplo). Las clases de negocio encapsulan toda la funcionalidad y de nuestra aplicación y proporcionan un front-end de la capa de persistencia.

Todos los métodos de las clases de negocio son transaccionales. Si el método termina correctamente estaremos seguros de que todas las actualizaciones se han realizado en la base de datos. Si algo falla en el método, se devolverá una excepción y se hará un rollback del estado de la base de datos, de forma que todo volverá al estado previo a la realización de la llamada al método.

Utilizaremos el patrón factoría para definir las clases. Especificaremos las clases con interfaces y una factoría devolverá la implementación concreta. Esto nos permitirá en el futuro sustituir la implementación de la capa de negocio por otra que, por ejemplo, utilice EJBs.

En cuanto a notación, llamaremos a las clases de negocio con el nombre de las entidades con las que trabajan y el sufijo Bo (de Business Object, objeto de negocio):

  • OperacionBo
  • LibroBo
  • UsuarioBo

También suele utilizarse el sufijo Service para estas clases. Por ejemplo OperacionService (no confundir con las interfaces de servicios web que explicaremos en futuras sesiones del proyecto).

Interfaces de las clases de negocio

A continuación listamos las interfaces de las clases de negocio. Definen las funcionalidades que se ofrece a otras capas de la aplicación y que debemos implementar con las clases de negocio. Estas funcionalidades son las mínimas definidas. Si quieres añadir algún metodo adicional puedes hacerlo.

Definimos todas las clases en el paquete org.especialistajee.jbib.bo.

Clase IOperacionBo:

package org.especialistajee.jbib.bo;

import java.util.List;
import org.especialistajee.jbib.model.LibroDomain;
import org.especialistajee.jbib.model.OperacionDomain;
import org.especialistajee.jbib.model.PrereservaDomain;

public interface IOperacionBo {
   OperacionDomain realizaReserva(Long idUsuario, String isbn)
      throws OperacionException, OperacionCupoCompletoException;
   PrereservaDomain realizaPrereserva(Long idUsuario, String isbn)
      throws OperacionException;
   List<LibroDomain> listaLibrosDisponiblesEnSala()
      throws OperacionException;
}

Clase ILibroBo:

package org.especialistajee.jbib.bo;

import java.util.List;

import org.especialistajee.jbib.model.LibroDomain;

public interface ILibroBo {
   LibroDomain recuperaLibro(String isbn) throws LibroException;
   void anyadeLibro(LibroDomain libro) throws LibroException;
   void eliminaLibro(String isbn) throws LibroException;
   List<LibroDomain> listaLibros() throws LibroException;
   List<LibroDomain> listaLibros(int firstResult, int maxResults) throws LibroException;
   void actualizaLibro(LibroDomain libro) throws LibroException;
   public List<LibroDomain> listaLibrosByTitulo() throws LibroException;
   public List<LibroDomain> listaLibrosByTitulo(int firstResult, int maxResults)
      throws LibroException;
   public List<LibroDomain> buscaLibros(String keyword) throws LibroException;
}

Clase IUsuarioBo:

package org.especialistajee.jbib.bo;

import org.especialistajee.jbib.bo.UsuarioException;
import org.especialistajee.jbib.model.UsuarioDomain;

public interface IUsuarioBo {
   UsuarioDomain recuperaUsuario(String login) throws UsuarioException;
}

Factoría de clases de negocio

La factoría de las clases de negocio permite obtener las implementaciones concretas LibroBo, OperacionBo y UsuarioBo (todavía sin implementar):

package org.especialistajee.jbib.bo;
public class FactoriaBos {

   public static FactoriaBos me;

   protected FactoriaBos() { }

   public static FactoriaBos getInstance() {
      if (me == null) {
         me = new FactoriaBos();
      }
      return me;
   }

   public ILibroBo getLibroBo() {
      return new LibroBo();
   }

   public IOperacionBo getOperacionBo() {
      return new OperacionBo();
   }

   public IUsuarioBo getUsuarioBo() {
      return new UsuarioBo();
   }
}

Consultas

Para implementar las funcionalidades de la capa de negocio, debemos ampliar los EAO del proyecto jbib-modelo con los siguientes métodos. También debemos añadir las consultas JPA asociadas en las clases entidad:

OperacionEao:

  • long countOperacionesUsuarioEstado(Long usuarioId, EstadoOperacion estado): cuenta las operaciones de un usuario y un estado determinado

LibroEao:

  • LibroDomain findByIsbn(String isbn): Devuelve el libroDomain correspondiente al isbn que se le pasa como parámetro
  • List<LibroDomain> findAllLibrosByTitulo(): Devuelve todos los libros ordenados por título
  • List<LibroDomain> findAllLibrosByTitulo(int primero, int numLibros): Devuelve un lista paginada de numLibros libros, comenzando por el primero
  • List<LibroDomain> findLibrosDisponiblesEnSala(): Devuelve todos los libros disponibles en sala
  • List<LibroDomain> buscaLibros(String palabra): Devuelve todos los libros que contienen la palabra en el título o autor

UsuarioEao:

  • UsuarioDomain findByLogin(String login): Devuelve el usuarioDomain correspondiente al login que se le pasa como parámetro

Implementando los BO

Terminamos construyendo en el paquete org.especialistajee.jbib.bo las clases de negocio que implementan las interfaces definidas. Damos como ejemplo la clase OperacionBo.

package org.especialistajee.jbib.bo;
// Imports

public class OperacionBo implements IOperacionBo {

   /**
    * Prerrequisitos: el login es un profesor o alumno activo que no tiene
    * cubierto el cupo de reservas y el libro está disponible para una reserva
    * en sala. Se lanza una excepción si no es así.
    * 
    * @param idUsuario
    *           - id del usuario
    * @param isbn
    *           - isbn del libro a reservar
    * @return un objeto OperacionDomain con los datos de la operación realizada
    * 
    */
   public OperacionDomain realizaReserva(Long idUsuario, String isbn)
         throws OperacionException {

      if (idUsuario == null || isbn == null) {
         throw new IllegalArgumentException(
               "Se esperaba un login y un isbn");
      }

      EntityManager em = null;

      try {

         em = PersistenceManager.getInstance().createEntityManager();
         em.getTransaction().begin();
         UsuarioEao usuarioEao = new UsuarioEao(em);
         OperacionEao operacionEao = new OperacionEao(em);
         LibroEao libroEao = new LibroEao(em);

         UsuarioDomain usuario = usuarioEao.find(idUsuario);

         // Comprobamos que el usuario no sea moroso, y que sea alumno o
         // profesor
         if (usuario == null) {
            throw new OperacionException(
                  "El usuario especificado no existe");
         }
         if ((usuario.getTipo() != TipoUsuario.PROFESOR && usuario
               .getTipo() != TipoUsuario.ALUMNO)
               || usuario.getEstado() == EstadoUsuario.MOROSO) {
            throw new OperacionException(
                  "Este usuario no puede hacer una reserva");
         }

         // Comprobamos que el usuario no tiene lleno el cupo de reservas
         int numOp = (int) operacionEao
               .countOperacionesUsuarioEstado(idUsuario,
                     EstadoOperacion.ACTIVA);
         BibliotecaBR.getInstance().compruebaCupoOperaciones(
               usuario.getTipo(), numOp);

         // Comprobamos que el libro existe
         LibroDomain libro = libroEao.findByIsbn(isbn);
         if (libro == null) {
            throw new OperacionException(
                  "El libro solicitado no existe");
         }

         // Comprobamos que el libro está disponible para reservar en sala

         if (libro.getEstado().equals(EstadoLibro.DISPONIBLE)) {

            Calendar cal = GregorianCalendar.getInstance();
            Date ahora = cal.getTime();

            // Calculamos el numero de dias que tiene validez la reserva

            int dias = 0;
            dias = BibliotecaBR.getInstance().calculaNumDiasReserva(
                  usuario.getTipo());
            cal.add(Calendar.DATE, dias);
            Date ffin = cal.getTime();

            OperacionDomain reserva = new ActivaDomain(libro,
                  usuario, TipoOperacion.RESERVA, ahora);
            reserva.setFechaFin(ffin);
            operacionEao.create(reserva);
            em.getTransaction().commit();
            return reserva;
         } else
            throw new OperacionException("Libro no disponible");
      } catch (Exception e) {
         em.getTransaction().rollback();
         throw new OperacionException("Error en la reserva", e);
      } finally {
         em.close();
      }
   }

   /**
    * Prerrequisitos: el login es un profesor o alumno activo que no tiene
    * cubierto el cupo de reservas y el libro está disponible para una
    * prereserva Se lanza una excepción si no es así.
    * 
    * @param idUsuario
    *           - id del usuario
    * @param isbn
    *           - isbn del libro a prereservar
    * @return un objeto PrereservaDomain con los datos de la operación realizada
    * 
    */
   public PrereservaDomain realizaPrereserva(Long idUsuario,
         String isbn) throws OperacionException {

      if (idUsuario == null || isbn == null) {
         throw new IllegalArgumentException(
               "Se esperaba un login y un isbn");
      }

      EntityManager em = null;

      try {

         em = PersistenceManager.getInstance().createEntityManager();
         em.getTransaction().begin();

         UsuarioEao usuarioEao = new UsuarioEao(em);
         OperacionEao operacionEao = new OperacionEao(em);
         PrereservaEao prereservaEao = new PrereservaEao(em);
         LibroEao libroEao = new LibroEao(em);

         UsuarioDomain usuario = usuarioEao.find(idUsuario);

         // Comprobamos que el usuario no sea moroso, y que sea alumno o
         // profesor
         if (usuario == null) {
            throw new OperacionException(
                  "El usuario especificado no existe");
         }
         if ((usuario.getTipo() != TipoUsuario.PROFESOR && usuario
               .getTipo() != TipoUsuario.ALUMNO)
               || usuario.getEstado() == EstadoUsuario.MOROSO) {
            throw new BibliotecaException(
                  "Este usuario no puede hacer una reserva");
         }

         // Comprobamos que el usuario no tiene lleno el cupo de reservas
         int numOp = (int) operacionEao
               .countOperacionesUsuarioEstado(idUsuario,
                     EstadoOperacion.ACTIVA);
         BibliotecaBR.getInstance().compruebaCupoOperaciones(
               usuario.getTipo(), numOp);

         // Comprobamos que el libro existe
         LibroDomain libro = libroEao.findByIsbn(isbn);
         if (libro == null) {
            throw new OperacionException(
                  "El libro solicitado no existe");
         }

         // Comprobamos que el libro está prestado

         if (libro.getEstado().equals(EstadoLibro.PRESTADO)) {
            Calendar cal = GregorianCalendar.getInstance();
            Date ahora = cal.getTime();
            PrereservaDomain prereserva = new PrereservaDomain(libro,
                  usuario, ahora);
            prereservaEao.create(prereserva);
            em.getTransaction().commit();
            return prereserva;
         } else
            throw new OperacionException("Libro no disponible");
      } catch (Exception e) {
         em.getTransaction().rollback();
         throw new OperacionException("Error en la reserva", e);
      } finally {
         em.close();
      }
   }

   public List<LibroDomain> listaLibrosDisponiblesEnSala()
         throws OperacionException {
      EntityManager em = null;
      try {
         em = PersistenceManager.getInstance().createEntityManager();
         em.getTransaction().begin();
         LibroEao libroEao = new LibroEao(em);
         List<LibroDomain> libros = libroEao
               .listaLibrosDisponiblesEnSala();
         em.getTransaction().commit();
         return libros;
      } catch (Exception e) {
         em.getTransaction().rollback();
         throw new OperacionException("Error en el listado ", e);
      } finally {
         em.close();
      }
   }
}

Algunos comentarios sobre los métodos en la capa de negocio:

  • Gestionamos correctamente la transacción y el entity manager: cerramos siempre el entity manager poniéndolo en el finally y hacemos un rollback de la transacción si hay algún error
  • En todos los métodos comprobamos que se cumplen las precondiciones y lanzamos una excepción en el caso en que no sea así
  • Los argumentos que se toman como parámetros son datos sencillos (el identificador de un usuario y el isbn de un libro, por ejemplo) y se devuelven objetos de dominio desconectados del contexto de persistencia
  • Hay que tener cuidado en cargar en memoria todos los objetos que vayan a necesitar los que llamen al método. Los atributos lazy del objeto devuelto no van a estar disponibles, porque el objeto está desconectado del entity manager.

Ejercicios

Además de construir todo el proyecto jbib-negocio con las clases anteriores, debes realizar lo siguiente:

  • Implementar la clase LibroBo con los métodos definidos en la interfaz
  • Implementar la clase UsuarioBo con los métodos definidos en la interfaz
  • Implementar las clases con las excepciones
  • Añadir el método listaLibrosReservados() en la interfaz IOperacionBo y en la clase OperacionBo. El método debe devolver una lista con los libros que están reservados.

Pruebas

A continuación listamos una clase de test sobre los objetos de negocio definidos y una aplicación de prueba. Ambos en el directorio src/test/java. Puedes ampliar los tests para incluir pruebas adicionales.

Clase org.especialistajee.jbib.bo.OperacionBoTest

package org.especialistajee.jbib.bo;

import static org.junit.Assert.*;

import java.util.List;

import org.especialistajee.jbib.model.LibroDomain;
import org.especialistajee.jbib.model.TipoOperacion;
import org.especialistajee.jbib.model.UsuarioDomain;
import org.junit.Test;

public class BoTest {
   @Test
   public void recuperaUsuarioTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IUsuarioBo usuarioBo = fBos.getUsuarioBo();
      UsuarioDomain usuario = usuarioBo.recuperaUsuario("antonio");
      assertTrue(usuario.getId().equals(2L));
   }

   @Test
   public void reservaLibroTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IUsuarioBo usuarioBo = fBos.getUsuarioBo();
      IOperacionBo operacionBo = fBos.getOperacionBo();
      ILibroBo libroBo = fBos.getLibroBo();
      UsuarioDomain usuario = usuarioBo.recuperaUsuario("patricia");
      operacionBo.realizaReserva(usuario.getId(), "097451408X");
      LibroDomain libro = libroBo.recuperaLibro("097451408X");
      assertTrue(libro.getActiva().getTipo()
            .equals(TipoOperacion.RESERVA)
            && libro.getActiva().getUsuario().equals(usuario));
   }

   @Test
   public void listaLibrosDisponiblesEnSalaTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IOperacionBo operacionBo = fBos.getOperacionBo();
      ILibroBo libroBo = fBos.getLibroBo();
      LibroDomain libro = libroBo.recuperaLibro("1932394885");
      List<LibroDomain> libros = operacionBo.listaLibrosDisponiblesEnSala();
      assertTrue(libros.size() == 7 && libros.contains(libro));
   }

   @Test
   public void listaLibrosLimitTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      ILibroBo libroBo = fBos.getLibroBo();
      List<LibroDomain> libros = libroBo.listaLibros(2, 5);
      assertTrue(libros.size() == 5);
   }

   @Test
   public void listaLibrosByTituloTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      ILibroBo libroBo = fBos.getLibroBo();
      List<LibroDomain> lista = libroBo.listaLibrosByTitulo();
      assertTrue(lista.size() == 12);
   }

   @Test
   public void buscaLibrosTest() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      ILibroBo libroBo = fBos.getLibroBo();
      List<LibroDomain> lista = libroBo.buscaLibros("Agile");
      assertTrue(lista.size() == 3);
   }
}

Clase main org.especialistajee.jbib.bo.PruebaBo en el directorio src/test/java

package org.especialistajee.jbib.bo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.especialistajee.jbib.BibliotecaException;
import org.especialistajee.jbib.model.LibroDomain;
import org.especialistajee.jbib.model.OperacionDomain;

public class PruebaBo {

   public static void main(String[] args) {
      BufferedReader br = new BufferedReader(
            new InputStreamReader(System.in));
      PruebaBo prueba = new PruebaBo();
      int opc = -1;

      while (opc != 0) {
         System.out
               .println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
         System.out
               .print("\t1.- Obtener listado de libros disponibles.\n"
                     + "\t2.- Obtener listado de libros reservados.\n"
                     + "\t3.- Realizar reserva.\n"
                     + "\t0.- Salir.\n"
                     + "\t\tElija una opcion:");
         try {
            opc = Integer.parseInt(br.readLine());
            switch (opc) {
            case (1):
               prueba.listarLibrosDisponibles();
               br.readLine();
               break;
            case (2):
               prueba.listarLibrosReservados();
               br.readLine();
               break;
            case (3):
               prueba.realizaReserva(br);
               br.readLine();
               break;
            }
         } catch (Exception e) {
            e.printStackTrace();
            try {
               br.readLine();
            } catch (IOException e1) {
            }
         }
      }
   }

   public void listarLibrosDisponibles() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IOperacionBo op = fBos.getOperacionBo();
      try {
         List<LibroDomain> lista = op.listaLibrosDisponiblesEnSala();
         for (LibroDomain libro : lista) {
            System.out.print("[" + libro.getIsbn() + "] ");
            System.out.print(libro.getTitulo());
            System.out.println();
         }

      } catch (BibliotecaException e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }

   public void listarLibrosReservados() {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IOperacionBo op = fBos.getOperacionBo();
      try {
         List<LibroDomain> lista = op.listaLibrosReservados();
         for (LibroDomain libro : lista) {
            System.out.print("[" + libro.getIsbn() + "] ");
            System.out.print(libro.getTitulo());
            System.out.println(" -> " + libro.getActiva().getUsuario().getLogin());
            System.out.println();
         }

      } catch (BibliotecaException e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }

   public void realizaReserva(BufferedReader br) {
      FactoriaBos fBos = FactoriaBos.getInstance();
      IOperacionBo op = fBos.getOperacionBo();
      IUsuarioBo usuarioBo = fBos.getUsuarioBo();
      try {
         System.out.println("ISBN: ");
         String isbn = br.readLine();
         System.out.println("Login usuario: ");
         String login = br.readLine();
         Long idUsuario = usuarioBo.recuperaUsuario(login).getId();
         OperacionDomain result = op.realizaReserva(idUsuario, isbn);
         System.out
               .println("Reserva realizada con identificador "
                     + result.getId());
      } catch (Exception e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }
}

Entrega

Para la entrega se deberá etiquetar el estado del proyecto con el tag entrega-proyint-jpa-negocio. Recordamos que la dirección del proyecto padre proyint-jbib en el servidor SVN debe ser:

svn+ssh://server.jtech.ua.es/home/svn/<login>/proyint/trunk/proyint-jbib

Como ayuda, proporcionamos la siguiente figura con un ejemplo de estructura de paquetes y clases resultantes de esta sesión.