Proyecto de Integración
 

Caso de estudio

Introducción a Maven

Maven es una herramienta Java de gestión del proceso de desarrollo de proyectos software, que simplifica la complejidad de sus distintas partes: compilación, prueba, empaquetamiento y despliegue. Es una herramienta muy popular en proyectos open source ya que facilita la construcción, descarga y distribución tanto de las librerías (ficheros .jar) como de las dependencias del proyecto.

Maven se origina de hecho en la comunidad open source, en concreto en la Apache Software Foundation en la que se desarrolló para poder gestionar y minimizar la complejidad de la construcción del proyecto Jakarta Turbine en 2002. El diseñador principal de Maven fue Jason van Zyl, ahora en la empresa Sonatype. En 2003 el proyecto fue aceptado como proyecto de nivel principal de Apache. En octubre de 2005 se lanzó la versión más utilizada en la actualidad de Maven: Maven 2. Desde entonces ha sido adoptado como la herramienta de desarrollo de software de muchas empresas y se ha integrado con muchos otros proyectos y entornos.

Maven es una herramienta de línea de comando, similar a las herramientas habituales en Java como javac, jar o a proyectos como Ant. Aunque es posible utilizar Maven en IDEs como Eclipse o Glassfish, las interfaces de usuario incluyen sus distintos comandos en menús de opciones para que puedan seleccionarse de forma gráfica. Es muy útil conocer la utilización de Maven en línea de comandos porque es la base de cualquier adaptación gráfica.

Una de las características principales de Maven es su enfoque declarativo, frente al enfoque orientado a tareas de herramientas tradicionales como Make o Ant. En Maven, el proceso de compilación de un proyecto se basa en una descripción de su estructura y de su contenido. Maven mantiene el concepto de modelo de un proyecto y obliga a definir un identificador único para cada proyecto que desarrollemos, así como declarar sus características (URL, versión, librerías que usa, tipo y nombre del artefacto generado, etc.). Todas estas características deben estar especificadas en el fichero POM (Project Object Model, fichero pom.xml en el directorio raíz del proyecto). De esta forma es posible publicar el proyecto en un repositorio y ponerlo a disposición de la comunidad para que otros a su vez puedan usarlo como librería.

Instalando Maven

Maven ya viene preinstalado en la máquina virtual del especialista. La instalación en Linux es muy sencilla.

En primer lugar debemos descargar la última versión de la página web oficial: http://maven.apache.org/download.html y descomprimirla en algún directorio del sistema. En el caso de la MV, lo hemos instalado en /opt/apache-maven-3.0.3.

Maven es una aplicación Java, y utiliza la variable JAVA_HOME para encontrar el path del JDK. También es necesario añadir el directorio bin de Maven al PATH del sistema. Se pueden definir en el fichero de configuración .bashrc de un usuario o en el fichero del sistema /etc/bashrc para todos los usuarios. En nuestro caso hemos modificado el único usuario de la MV especialista. El código que hemos añadido ha sido este:

export JAVA_HOME=/opt/jdk1.6.0_27
export MAVEN_HOME=/opt/apache-maven-3.0.3
export PATH=${PATH}:${MAVEN_HOME}/bin

El proceso de build de un proyecto

Los que hemos programado en C recordamos los ficheros Makefile en los que se especificaban las dependencias entre los distintos elementos de un proyecto y la secuencia de compilación necesaria para generar una librería o un ejecutable. En Java, el desarrollo de aplicaciones medianamente complejas es más complicado que en C. Estamos obligados a gestionar un gran número de recursos: código fuente, ficheros de configuración, librerías externas, librerías desarrolladas en la empresa, etc. Para gestionar este desarrollo es necesario algo de más nivel que las herramientas que proporciona Java (javac, jar, rmic, java, etc.)

¿En qué consiste el proceso de compilación y empaquetado en Java?. Básicamente en construir lo que Maven llama un artefacto (terminología de Maven que significa fichero) a partir de un proyecto Java definido con una estructura propia de Maven (apartado siguiente). Los posibles artefactos en los que podemos empaquetar un programa Java son:

  • Fichero JAR: librería de clases o aplicación standalone. Contiene clases Java compiladas (.class) organizadas en paquetes, ficheros de recursos y (opcionalmente) otros ficheros JAR con librerías usadas por las clases. En las aplicaciones enterprise, los EJB también se empaquetan en ficheros JAR que se despliegan en servidores de aplicaciones.
  • Fichero WAR: aplicación web lista para desplegarse en un servidor web. Contiene un conjunto de clases Java, librerías, ficheros de configuración y ficheros de distintos formatos que maneja el servidor web (HTML, JPG, etc.)
  • Fichero EAR: aplicación enterprise que se despliega en un servidor de aplicaciones. Contiene librerías, componentes EJB y distintas aplicaciones web (ficheros WAR).

Además, el ciclo de desarrollo de un proyecto es más complejo que esta construcción, ya que es necesario realizar un conjunto de tareas adicionales como gestionar las dependencias con librerías externas, integrar el código en repositorios de control de versiones (CVS, subversion o Git), lanzar tests o desplegar la aplicación en algún servidor de aplicaciones.

Podría pensarse que los entornos de desarrollo (Eclipse o Netbeans) pueden dar una buena solución a la complejidad del proceso de construcción, pero no es así. Son imprescindibles para el desarrollo, pero no ayudan demasiado en la construcción del proyecto. La configuración de las dependencias se realiza mediante asistentes gráficos que no generan ficheros de texto comprensibles que podamos utilizar para comunicarnos con otros compañeros o equipos de desarrolladores y que pueden dar lugar a errores. El hecho de que sean entornos gráficos hacen complicado también usarlos en procesos de automatización y de integración continua.

Estructura de un proyecto Maven

La estructura de directorios de un proyecto Maven genérico es la que aparece en la siguiente figura.

Si observamos el primer nivel, encontramos en la raíz del proyecto el fichero pom.xml, el directorio src y el directorio target. El fichero pom.xml es el fichero principal de Maven en el que se describen todas las características del proyecto: nombre, versión, tipo de artefacto generado, librerías de las que depende, ect. Lo estudiaremos más adelante. El directorio src contiene todo el código fuente original del proyecto. Y el directorio target contiene el resultado de la construcción del proyecto con Maven.

Entrando en más profundidad, nos encontramos los siguientes directorios:

  • src/main/java: el código fuente de las clases Java del proyecto
  • src/main/resources: ficheros de recursos que necesita la aplicación
  • src/main/filters: filtros de recursos, en forma de ficheros de propiedades, que pueden usarse para definir variables que se utilizan en tiempo de ejecución
  • src/main/config: ficheros de configuración
  • src/main/webapp: el directorio de aplicación web de un proyecto WAR
  • src/test/java: código fuente de las pruebas de unidad de las clases que tiene la misma estructura que la estructura del código fuente en el directorio main/java
  • src/tests/resources: recursos utilizados para los tests que no serán desplegados
  • src/tests/filters: filtros de recursos utilizados para los tests
  • src/site: ficheros usados para generar el sitio web Maven del proyecto

Los ficheros de recursos son ficheros leídos desde la aplicación. Tal y como hemos visto en el módulo JHD, estos recursos deben estar contenidos en la raíz del JAR para poder acceder a ellos utilizando el método getResourceAsStream de la clase Class:

InputStream in = 
   getClass().getResourceAsStream("/datos.txt");

Maven se encarga de colocar los recursos en la raíz del JAR al empaquetar el proyecto.

POM: Project Object Model

El elemento más importante de un proyecto Maven, a parte de su estructura, es su fichero POM en el que se define completamente el proyecto. Este fichero define elementos XML preestablecidos que deben ser definidos por el grupo de desarrollo del proyecto. Viendo algunos de ellos podemos entender también más características de Maven.

Vamos a utilizar como ejemplo el primer proyecto de integración jbib-modelo que construiremos más adelante. Veamos su fichero pom.xml. Al comienzo nos encontramojbib-modelos con la cabecera XML y la definición del proyecto:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.especialistajee.proyint</groupId>
<artifactId>jbib-modelo</artifactId>
<packaging>jar</packaging>
<version>2011</version>
<name>jbib-modelo</name>
<url>http://web.ua.es/especialistajee</url>

La primera definición project xmlns es común para todos los ficheros pom.xml. En ella se declara el tipo de esquema XML y la dirección donde se encuentra el fichero de esquema XML. Se utiliza para que los editores de XML puedan validar correctamente el fichero. Esta sintaxis depende de la versión de Maven que se esté utilizando.

Después aparece la identificación del proyecto, en la que hay que definir el grupo que desarrolla el proyecto (groupId), el nombre del artefacto que genera el proyecto (artifactId), el tipo de empaquetamiento (packaging) y su versión (version). Estos campos representan las denominadas coordenadas del proyecto (hablaremos de ello más adelante). En nuestro caso son:

org.especialistajee.proyint:jbib-modelo:jar:2011

Por último, hay que definir el nombre lógico del proyecto (name) y una URL asociada al mismo url.

A continuación definimos algunas propiedades del proyecto, que se utilizarán en los distintos procesos de Maven. En nuestro caso sólo la codificación de caracteres que estamos utilizando en el código fuente de nuestro proyecto:

<properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

A continuación viene la definición de las dependencias del proyecto: librerías de las que dependen el proyecto. En nuestro caso:

  • JUnit: junit:junit:4.8.1:jar
  • Log4j: log4j:log4j:1.2.14:jar
  • Commons Logging: commons-logging:commons-logging:1.1.1:jar
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
  </dependencies>

Por último, definimos algunas características de los procesos de Maven que construyen el proyecto, definiendo parámetros para los pluging de Maven que se encargan de ejecutarlos.

En nuestro caso, definimos el nivel de compilación de Java, necesario para que no haya conflicto al importar el proyecto en Eclipse. Es interesante hacer notar que el plugin se identifica de una forma similar al proyecto, utilizando el identificador del grupo, el de proyecto y su número de versión.

<build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
            </configuration>
         </plugin>
      </plugins>
   </build>
</project>

Es posible definir herencia entre los ficheros POM utilizando el identificador parent. Es útil para definir elementos comunes y evitar repetirlos en todos los POM. Por ejemplo, podríamos definir un POM en el que se declaren todas las librerías que se usan habitualmente por nuestros proyectos e incluir este POM en todos los proyectos utilizando herencia.

<project>
   <parent>
      <groupId></groupId>
      <artifactId></artifactId>
      <version></version>
   </parent>
  ...
</project>

Maven define un super POM que por defecto es el padre de todos los POM. Allí se definen elementos comunes como la localización de los repositorios o la estructura de directorios por defecto de Maven. Se puede encontrar este super POM en el fichero llamado pom-4.0.0.xml en el JAR maven-2.2.1-uber.jar en el directorio lib de Maven.

Maven resuelve todas las relaciones de herencia entre POMs y genera internamente un POM efectivo (effective POM) en el que combinan todos los POMs que afectan a un determinado proyecto. Este POM efectivo es el que se utiliza para realizar la construcción del proyecto. Es posible consultar este POM efectivo con el comando:

mvn help:effective-pom

También puede consultarse en la pestaña correspondiente del editor POM del plugin de Ecipse.

Repositorios

Los proyectos software están relacionados. Los proyectos necesitan de clases y librerías definidas en otros proyectos. Esos proyectos pueden ser otros desarrollados por nosotros en la empresa o librerías open source bajadas de Internet.

La tarea de mantener las dependencias de un proyecto es complicada, tanto para las dependencias entre nuestros proyectos como las dependencias con otros proyectos open source disponibles en Internet. Por ejemplo, si queremos utilizar un framework como Spring, tendremos que descargarnos no sólo los JAR desarrollados en el proyecto, sino también un buen número de otras librerías open source que usa. Cada librería es un fichero JAR. ¿Qué pasa si alguna de esas librerías ya las estamos usando y las tenemos ya descargadas? O, peor aún, ¿Qué pasa si estamos usando otras versiones de esas librerías en nuestros proyectos? ¿Podremos detectar los posibles conflictos?. Maven obliga a declarar explícitamente estas dependencias en el fichero POM del proyecto y se encarga de gestionar estos y otros problemas:

  • Descarga las librerías necesarias para construir el proyecto y los ficheros POM asociados a esas librerías
  • Resuelve dependencias transitivas, librerías que dependen de librerías de las que dependen nuestro proyecto
  • Resuelve conflictos entre librerías

Un elemento fundamental para gestionar las dependencias es poder identificar y nombrar un proyecto. En Maven el nombre de un proyecto se define mediante los siguientes elementos (que en Maven se denominan coordenadas):

  • groupId: El grupo, compañía, equipo, organización, etc. Se utiliza una convención similar a la de los paquetes Java, comenzando por el nombre de dominio invertido de la organización que crea el proyecto. Por ejemplo, los groupId de la Apache Software Foundation comienzan con org.apache.
  • artifactId: Identificador único que representa de forma única el proyecto dentro del groupId
  • version: Número de versión del proyecto, por ejemplo 1.3.5 o 1.3.6-beta-01
  • packaging: Tipo de empaquetamiento del proyecto. Por defecto es jar. Un tipo jar genera una librería JAR, un tipo war se refiere a una aplicación web.

En Maven un proyecto genera un artefacto. El artefacto puede ser un fichero JAR, WAR o EAR. El tipo de artefacto viene indicado en el tipo de empaquetamiento del proyecto.

El nombre final del fichero resultante de la construcción del proyecto es por defecto:

<artifactId>-<version>.<packaging>

Por ejemplo, Apache ha desarrollado el proyecto commons-email que proporciona una serie de utilidades para la gestión de correos electrónicos en Java. Sus coordenadas son:

org.apache.commons:commons-email:1.1:jar

El artefacto (fichero JAR) generado por el proyecto tiene como nombre email-1.1.jar

Cuando ejecutamos Maven por primera vez veremos que descarga un número de ficheros del repositorio remoto de Maven. Estos ficheros corresponden a plugins y librerías que necesita para construir el proyecto con el que estamos trabajando. Maven los descarga de un repositorio global a un repositorio local donde están disponibles para su uso. Sólo es necesario hacer esto la primera vez que se necesita la librería o el plugin. Las siguientes ocasiones ya está disponible en el repositorio local.

La direcciones en las que se encuentran los repositorios son las siguientes:

  • Repositorio central: El repositorio central de Maven se encuentra en http://repo1.maven.org/maven2. Se puede acceder a la dirección con un navegador y explorar su estructura.
  • Repositorio local: El repositorio local se encuentra en el directorio ${HOME}/.m2/repository.

La estructura de directorios de los repositorios (tanto el central como el local) está directamente relacionada con las coordenadas de los proyectos. Los proyectos tienen la siguiente ruta, relativa a la raíz del repositorio:

/<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packaging>

Por ejemplo, el artefacto commons-email-1.1.jar, con coordenadas org.apache.commons:commons-email:1.1:jar está disponible en la ruta:

/org/apache/commons/commons-email/1.1/commons-email-1.1.jar

Versiones

El estándar de Maven para los números de versiones es muy importante, porque permite definir reglas para gestionar correctamente las dependencias en caso de conflicto. El número de versión de un proyecto se define por un número principal, un número menor y un número incremental. También es posible definir un calificador, para indicar una versión alfa o beta. Los números se separan por puntos y el calificador por un guión. Por ejemplo, el número 1.3.5-alpha-03 define un número de versión principal 1, la versión menor 3, la versión incremental de 5 y el calificador de "alpha-03".

Maven compara las versiones de una dependencia utilizando este orden. Por ejemplo, la versión 1.3.4 representa un build más reciente que la 1.0.9. Los clasificadores se comparan utilizando comparación de cadenas. Hay que tener cuidado, porque "alpha10" es anterior a "alpha2"; habría que llamar al segundo "alpha02".

Maven permite definir rangos de versiones en las dependencias, utilizando los operadores de rango exclusivos "(", ")" o inclusivos "[", "]". Así, por ejemplo, si queremos indicar que nuestro proyecto necesita una versión de JUnit mayor o igual de 3.8, pero menor que 4.0, lo podemos indicar con el siguiente rango:

<version>[3.8,4.0)</version>

Si una dependencia transitiva necesita la versión 3.8.1, esa es la escoge Maven sin crear ningún conflicto.

Es posible también indicar rangos de mayor que o menor que dejando sin escribir ningún número de versión antes o después de la coma. Por ejemplo, "[4.0,)" representa cualquier número mayor o igual que 4.0, "(,2.0)" representa cualquier versión menor que la 2.0 y "[1.2]" significa sólo la versión 1.2 y ninguna otra.

Cuando dos proyectos necesitan dos versiones distintas de la misma librería, Maven intenta resolver el conflicto, descargándose la que satisface todos los rangos. Si no utilizamos los operadores de rango estamos indicando que preferimos esa versión, pero que podríamos utilizar alguna otra. Por ejemplo, es distinto especificar "3.1" y "[3.1]". En el primer caso preferimos la versión 3.1, pero si otro proyecto necesitara la 3.2 Maven se descargaría esa. En el segundo caso exigimos que la versión descargada sea la 3.1. Si otro proyecto especifica otra versión obligatoria, por ejemplo "3.2", entonces el proyecto no se compilará.

Gestión de dependencias

Hemos visto que una de las características principales de Maven es la posibilidad de definir las dependencias de un proyecto. En la sección dependencies del fichero POM se declaran las librerías necesarias para compilar, testear y ejecutar nuestra aplicación. Maven obtiene estas dependencias del repositorio central o de algún repositorio local configurado por nuestra empresa y las guarda en el directorio .$HOME/.m2/repository. Si utilizamos la misma librería en un varios proyectos, sólo se descargará una vez, lo que nos ahorrará espacio de disco y tiempo. Y lo que es más importante, el proyecto será mucho más ligero y portable, porque no llevará incluidas las librerías que necesita para su construcción.

Ya hemos visto en apartados anteriores cómo se declaran las dependencias en el fichero POM. Cada dependencia se define de forma unívoca utilizando sus coordenadas. El mecanismo de declaración de las dependencias es el mismo para las dependencias de librerías externas como para las definidas dentro de la organización.

Para definir una dependencia hay que identificar también el número de versión que se quiere utilizar, utilizando la nomenclatura del apartado anterior. Por ejemplo, la siguiente dependencia especifica una versión 3.0 o posterior de hibernate.

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate</artifactId>
   <version>[3.0,)</version>
 </dependency>

Un concepto fundamental en Maven es el de dependencia transitiva. En los repositorios no solo se depositan los artefactos generados por los proyectos, sino también el fichero POM del proyecto. Y en ese fichero se definen las dependencias propias del proyecto. Por ejemplo, junto con el artefacto hibernate-3.0.jar se encuentra el fichero POM hibernate-3.0.pom.xml en el que se definen sus propias dependencias, librerías necesarias para Hibernate-3.0. Estas librerías son dependencias transitivas de nuestro proyecto. Si nuestro proyecto necesita Hibernate, e Hibernate necesita estas otra librería B, nuestro proyecto también necesita (de forma transitiva) la librería B. A su vez esa librería B tendrá también otras dependencias, y así sucesivamente ...

Maven se encarga de resolver todas las dependencias transitivas y de descargar al respositorio local todos los artefactos necesarios para que nuestro proyecto se construya correctamente.

Otro elemento importante es el ámbito (scope) en el que se define la dependencia. El ámbito por defecto es compile y define librerías necesarias para la compilación del proyecto. También es posible especificar otros ámbitos. Por ejemplo test, indicando que la librería es necesaria para realizar pruebas del proyecto:

<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.1</version>
   <type>jar</type>
   <scope>test</scope>
</dependency>

Otros ámbitos posibles son provided y runtime. Una dependencia se define provided cuando es necesaria para compilar la aplicación, pero que no se incluirá en el WAR y no será desplegada. Por ejemplo las APIs de servlets:

<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.4</version>
   <scope>provided</scope>
</dependency>

Las dependencias runtime son dependencias que no se necesitan para la compilación, sólo para la ejecución. Por ejemplo los drivers de JDBC para conectarse a la base de datos:

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>3.1.13</version>
   <scope>runtime</scope>
</dependency>

Una herramienta muy útil es el informe de dependencia. Este informe se genera cuando se ejecuta el objetivo site. Maven construye un sitio web con información sobre el proyecto y coloca el informe en el fichero target/dependencies.html:

$ mvn site

El informe muestra una lista de dependencias directas y transitivas y su ámbito.

El ciclo de vida de Maven

El concepto de ciclo de vida es central para Maven. El ciclo de vida de un proyecto Maven es una secuencia de fases que hay que seguir de forma ordenada para construir el artefacto final.

Las fases principales del ciclo de vida por defecto son:

  • validate: valida que el proyecto es correcto y que está disponible toda la información necesaria
  • process-resources: procesar el código fuente, por ejemplo para filtrar algunos valores
  • compile: compila el código fuente del proyecto
  • test: lanza los tests del código fuente compilado del proyecto utilizando el framework de testing disponible. Estos tests no deben necesitar que el proyecto haya sido empaquetado o desplegado
  • package: empaqueta el código compilado del proyecto en un formato distribuible, como un JAR
  • integration-test: procesa y despliega el paquete en un entorno en donde se pueden realizar tests de integración
  • verify: lanza pruebas que verifican que el paquete es válido y satisface ciertos criterios de calidad
  • install: instala el paquete en el repositorio local, para poder ser usado como librería en otros proyectos locales
  • deploy: realizado en un entorno de integración o de lanzamiento, copia el paquete final en el repositorio remoto para ser compartido con otros desarrolladores y otros proyectos.

Todas estas fases se lanzan especificándolas como parámetro en el comando mvn. Si ejecutamos una fase, Maven se asegura que el proyecto pasa por todas las fases anteriores. Por ejemplo:

$ mvn install

Esta llamada realiza la compilación, los tests, el empaquetado los tests de integración y la instalación del paquete resultante en el repositorio local de Maven.

Nota
Para un listado completo de todas las opciones se puede consultar la página de Apache Maven Introduction to the Build Lifecycle

Ejecutando tests

Los tests de unidad son una parte importante de cualquier metodología moderna de desarrollo, y juegan un papel fundamental en el ciclo de vida de desarrollo de Maven. Por defecto, Maven obliga a pasar los tests antes de empaquetar el proyecto. Maven permite utilizar los frameworks de prueba JUnit y TestNG. Las clases de prueba deben colocarse en el directorio src/test.

Para ejecutar los tests se lanza el comando mvn test:

especialista@especialista:~/proy-int/jbib-modelo$ mvn test
[INFO] Scanning for projects...
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.especialistajee.jbib.model.UsuarioTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.082 sec
Running org.especialistajee.jbib.model.OperacionTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.141 sec
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec
Running org.especialistajee.jbib.model.AvisoTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.012 sec

Results :

Tests run: 19, Failures: 0, Errors: 0, Skipped: 0

Maven compilará los tests si es necesario. Por defecto, los tests deben colocarse en el directorio src/test siguiendo una estructura idéntica a la estructura de clases del proyecto. Maven ejecutará todas las clases que comiencen o terminen con Test o que terminen con TestCase.

Los resultados detallados de los tests se producen en texto y en XML y se dejan en el directorio target/surefire-reports. Es posible también generar los resultados en HTML utilizando el comando:

$ mvn surefire-report:report

El informe HTML se generará en el fichero target/site/surefire-report.html.

Plugins y goals

Todo el trabajo que realiza Maven es realizado por módulos independientes que son también descargados del repositorio global. Estos módulos reciben el nombre de plugins. Cada plugin tiene un conjunto de goals que podemos lanzar desde línea de comando. La sintaxis de una ejecución de un goal de un plugin es:

$ mvn <plugin>:goal -Dparam1=valor1 -Dparam2=valor2 ...

Las fases del ciclo de vida vistas en el apartado anterior también se procesan mediante el mecanismo de plugins. Por ejemplo, la llamada a mvn test realmente genera una llamada al objetivo test del plugin surefire:

$ mvn surefire:test

Otro ejemplo es el plugin Jar que define el objetivo jar para realizar la fase package:

$ mvn jar:jar

Existen múltiples plugins de Maven que pueden utilizarse para automatizar distintas tareas complementarias necesarias para la construcción del proyecto. Un ejemplo es el plugin SQL de Codehaus que permite lanzar comandos SQL utilizando su objetivo execute:

$ mvn sql:execute

La configuración del plugin se define en el fichero POM. La siguiente configuración lanza en la fase de construcción process-test-resource dos ficheros de comandos SQL; el primero inicializa la base de datos y el segundo la llena con datos sobre los que se van a realizar las pruebas.

<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/main/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>

Vemos que en el apartado dependency se definen las dependencias del plugin. En este caso el JAR mysql-connector-java-5.0.8 necesario para la conexión a la base de datos. En el apartado executions es donde se definen los ficheros SQL que se ejecutan y la fase del ciclo de vida en la que se lanza.

Cuando se llame a la fase process-test-resoures de Maven (o a cualquiera posterior) se ejecutaran los ficheros SQL:

$ mvn process-test-resources
[INFO] Scanning for projects...
...
[INFO] [sql:execute {execution: create-db}]
[INFO] Executing file: /tmp/biblioteca.2081627011sql
[INFO] 24 of 24 SQL statements executed successfully
[INFO] [sql:execute {execution: create-data}]
[INFO] Executing file: /tmp/datos.601247424sql
[INFO] 27 of 27 SQL statements executed successfully
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Wed Oct 06 06:16:02 CEST 2010
[INFO] Final Memory: 8M/19M
[INFO] ------------------------------------------------------------------------

La siguiente figura muestra un resumen de los conceptos vistos hasta ahora de fases de build y plugins:

Diagrama de Actividad para la entrada al sistema

Creando proyectos de cero: arquetipos

Es tedioso crear todo un proyecto Maven desde cero. Implica crear toda una estructura de directorios y algunos ficheros de configuración y clases Java vacías. Maven puede hacerlo automáticamente con el plugin Archetype, que permite construir un proyecto vacío a partir de plantillas estándar. El proyecto construido contiene la estructura estándar de directorios así como algunos ficheros de ejemplo que ilustran las convenciones y buenas prácticas de Maven. El modelo de arquetipo por defecto creará un proyecto que construye una librería JAR. Es posible utilizar otras plantillas, como proyectos web, proyectos JPA, etc.

Por ejemplo, supongamos que somos una empresa de desarrollo de software llamada PrensaSoft, especializada en dar soporte a grupos editoriales y que estamos desarrollando un módulo de recomendaciones de noticias que llamamos RecomendBackend. Todas las clases del módulo queremos que se creen en el paquete es.prensasoft.backend.recomend. Definimos entonces el proyecto Maven con las siguientes coordenadas:

  • groupId: es.prensasoft
  • artifactId: recomend-backend
  • version: 2011
  • package: es.prensasoft.backend.recomend

Para construir el esqueleto inicial del proyecto llamamos entonces al objetivo archetype:generate de la siguiente forma:

$ mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart 
-DgroupId=es.prensasoft -DartifactId=recomend-backend 
-Dversion=2011 -Dpackage=es.prensasoft.backend.recomend

Maven muestra la siguiente información y nos pide confirmar. Pulsamos INTRO y Maven crea toda la estructura de directorios del proyecto.

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:generate] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Preparing archetype:generate
[INFO] No goals needed for project - skipping
[INFO] [archetype:generate {execution: default-cli}]
[INFO] Generating project in Interactive mode
[INFO] Using property: groupId = es.prensasoft
[INFO] Using property: artifactId = recomend-backend
[INFO] Using property: version = 2011
[INFO] Using property: package = es.prensasoft.backend.recomend
Confirm properties configuration:
groupId: es.prensasoft
artifactId: recomend-backend
version: 2011
package: es.prensasoft.backend.recomend
Y: 

Otra posible forma de crear el proyecto es ejecutar el comando sin especificar los parámetros. En este caso Maven los va pidiendo de forma interactiva y sugiriendo opciones.

Por último, una tercera forma de crear un proyecto Maven es utilizando el plugin de Eclipse. Incluye un asistente que permite introducir todas las opciones utilizando ventanas, menús y diálogos.

El proyecto se creará en el subdirectorio con el mismo nombre que el del artefacto (recomend-backend). El groupId y el artifactId se utilizarán para colocar la librería JAR generada en el repositorio de Maven. El nombre de paquete es el paquete raíz en el que se definen todas las clases del proyecto. Se crearán los directorios correspondentes bajo src/main/java (en nuestro caso es/prensasoft/backend/recomend).

También se creará un fichero POM mínimo. En nuestro caso será este:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>es.prensasoft</groupId>
  <artifactId>recomend-backend</artifactId>
  <packaging>jar</packaging>
  <version>2011</version>
  <name>recomend-backend</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Existen muchos arquetipos posibles que pueden utilizarse para crear distintos tipos de proyectos. Por ejemplo, para crear una aplicación web debemos utilizar el arquetipo maven-archetype-webapp que creará un proyecto WAR vacío.

Existen también un gran número de arquetipos disponbiles de otros tipos de aplicaciones y stacks, como Struts, Spring, JSF, Hibernate, etc. Todos ellos se pueden encontrar en el página web de Codehaus.

Un proyecto muy interesante es AppFuse que proporciona un conjunto de arquetipos con aplicaciones ejemplo listas para ser ejecutadas. Veámoslo rápidamente. Podemos construir una aplicación web basada en Hibernate, Spring y JSF con el arquetipo appfuse-basic-jsf:

$mvn archetype:generate -DarchetypeGroupId=org.appfuse.archetypes 
-DarchetypeArtifactId=appfuse-basic-jsf-archetype 
-DarchetypeVersion=2.1.0-M1 -DgroupId=org.especialistajee 
-DartifactId=prueba-jsf -Dversion=2011

Esto creará una aplicación web ejecutable y con un fichero POM completo. Se intentará conectar la base de datos local MySQL utilizando el usuario "root" y sin password. Podemos ejecutarla utilizando el plugin de Jetty (un servidor web, similar a Tomcat). Descargará todas las librerías necesarias (cuidado, serán muchas), compilará el proyecto, pasará los tests y desplegará la aplicación en el servidor jetty:

$ cd prueba-jsf
$ mvn jetty:run
...
 mvn jetty:run-war
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building AppFuse JSF Application
[INFO]    task-segment: [jetty:run-war]
[INFO] ----------------------------------------------------------------------------
...
2007-10-10 21:30:48.410::INFO:  Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server

Podemos consultar la aplicación conectándonos a http://localhost:8080 y entrando con nombre de usuario y contraseña "admin".

Usando Maven en Eclipse

El plugin de Eclipse m2eclipse permite la utilización de Maven desde el entorno de programación. Ha sido desarrollado por la compañía Sonatype y se distribuye de forma gratuita. El plugin permite importar proyectos Maven en Eclipse, editar y explorar el fichero POM del proyecto, explorar repositorios de Maven o lanzar builds de Maven desde el propio entorno.

El documento Sonatype m2eclipse es un excelente manual de uso del plugin. Os remitimos a él para conocer en profundidad todas sus características. Aquí sólo haremos un rápido repaso.

El plugin permite importar un proyecto Maven en Eclipse y trabajar con él manteniendo su estructura de directorios. El plugin configura automáticamente los ficheros de configuración de Eclipse (los ficheros .project y .classpath) para que utilicen esa estructura de directorios y para que se actualicen las dependencias entre los proyectos y las librerías JAR.

Una vez importado el proyecto el desarrollador puede seguir trabajando en él como lo haría con cualquier proyecto Eclipse. Puede lanzar añadir código, ejecutarlo, lanzar los tests o compartirlo por CVS. En el caso en que fuera un proyecto web, podrá también desplegarlo en el servidor instalado en Eclipse.

El plugin m2eclipse proporciona un editor especializado para trabajar con los ficheros POM. En la parte inferior de la imagen se pueden observar pestañas para acceder a los distintas elementos configurados en el POM: general, dependencias, plugins, informes, jerarquía de dependencias, grafo de dependencias, POM efectivo y código XML.

Por ejemplo, la pestaña de dependencias del POM muestra el siguiente aspecto.

Desde esa pestaña es posible editar las dependencias del POM y añadir nuevas. Una ventana de diálogo permite explorar todos los artefactos del repositorio de Maven.

Es posible activar una vista especial que permite hojear los repositorios. Se activa en la opción Window > Show View > Other... y buscando Maven y seleccionando Maven Repositories.

Es posible también lanzar builds predeterminados de Maven y configurar esas ejecuciones.

Para saber más

Las siguientes referencias son de mucho interés para aprender más sobre Maven o como referencia para consultar dudas:

  • Maven in 5 minutes: Introducción rápida a Maven en la que se muestra cómo construir, empaquetar y ejecutar una sencilla aplicación.
  • Maven by Example: Libro de introducción a Maven realizado por Sonatype, la compañía creada por Jason Van Zyl el desarrollador principal de Maven. Disponible también en PDF en esta dirección.
  • Maven, the complete reference: Otro libro muy recomendable de Sonatype. Disponible también en PDF en esta dirección.
  • Java Power Tools: Excelente libro sobre 30 herramientas que ayudan en el desarrollo de software Java. El capítulo 1 está dedicado a Ant y el 2 a Maven.
  • Maven: Página principal del proyecto Maven

Caso de estudio

Introducción

A partir de un supuesto básico de la gestión de una biblioteca, vamos a crear un caso de estudio completo que evolucionará conforme estudiemos las diferentes tecnologías de la plataforma Java Enterprise.

El objetivo de esta sesión es introducir el caso de estudio que vamos a desarrollar, obtener una visión global del proyecto, fijando los casos de uso y requisitos principales y definiendo el esqueleto inicial del problema.

Ingeniería de Requisitos

El Instituto de Educación Secundaria "jUA" nos ha encargado que desarrollemos una aplicación para la gestión de los préstamos realizados en la biblioteca del centro, lo que implica tanto una gestión de los libros como de los alumnos y profesores que realizan estos prestamos.

Tras una serie de entrevistas y reuniones con diferente personal del centro, hemos llegado a recopilar las siguientes funcionalidades a informatizar.

  • La aplicación será utilizada por bibliotecarios y usuarios de la biblioteca.
  • Un usuario administrador puede realizar acciones relacionadas con la configuración de la aplicación: dar de alta nuevos usuarios, modificar ciertos parámetros, etc.
  • Tanto bibliotecarios como usuarios podrán realizar con la aplicación acciones sobre los libros: consultar su disponibilidad, reservarlos, prestarlos, etc. Las acciones estarán limitadas al rol.
  • Un bibliotecario se encargará de la gestión de préstamos y libros. La aplicación le permitirá registrar los préstamos y devoluciones que realiza en el mostrador de la biblioteca, así como consultar el estado de un determinado libro o usuario. Podrá también dar de alta libros, modificar su información y darlos de baja.
  • El sistema guardará todas las operaciones realizadas por los usuarios. Diferenciará entre operaciones activas (préstamos o reservas) y operaciones históricas (préstamos que ya se han devuelto o reservas que se han cancelado o convertido en préstamos).
  • Los profesores y alumnos van a poder realizar las siguientes acciones con la aplicación:
    • Reservar un libro que está disponible en sala
    • Prereservar un libro prestado a otro usuario
    • Consultar el estado de los libros: un libro puede estar prestado (y no pre-reservado), reservado en sala, pre-reservado y disponible.
  • Además podrán hacer las siguientes acciones físicas:
    • Pedir un préstamo al bibliotecario
    • Devolver un libro
  • Para realizar un préstamo tanto los profesores como los alumnos deberán personarse en la biblioteca, coger el libro y solicitar su préstamo al bibliotecario. El bibliotecario utilizará la aplicación para asignar el préstamo del libro al usuario. El número de libros que puede tener en préstamo un usuario dependerá de si es profesor o alumno.
  • Los usuarios profesores pueden solicitar préstamos en depósito. En esta modalidad, el profesor puede quedarse el libro hasta que otro usuario (profesor o alumno) solicite una reserva.
  • Los usuarios pueden reservar libros con la aplicación:
    • Si el libro está disponible en sala, no podrá ser prestado a ningún otro usuario durante un número determinado de días.
    • Si el libro está prestado, se pondrá en estado de 'prereserva': cuando el usuario prestatario devuelva el libro, el bibliotecario lo dejará en la sala para consulta, pero quedará reservado durante un número de días (dependiendo del tipo de usuario que ha hecho la reserva). En el momento de la devolución la aplicación enviará un aviso al usuario que ha hecho la reserva y el libro pasará a estado 'reservado'. Si el usuario prestatario es un profesor que tiene el libro EN depósito, se le enviará un aviso para solicitar su devolución.
  • El estado por defecto de un usuario es activo. Cuando el usuario se retrasa en la devolución de un préstamo pasa a estado moroso. En ese estado no puede pedir prestado ni reservar ningún otro libro. Cuando devuelve el libro se le crea una multa y pasa a estado multado.
  • De la multa nos interesa saber la fecha de inicio y de finalización de la misma. La finalización de la multa dependerá del tipo de usuario. Nos han comunicado que quieren mantener un histórico de las multas que ha tenido un usuario. Cuando pasa la fecha de finalización, el estado del usuario vuelve a activo.

Estas funcionalidades las vamos a ir implementando a lo largo del curso, conforme vaya avanzando el proyecto de integración.

Requisitos de Información (IRQ)

Los requisitos de información resumen la información persistente que nos interesa almacenar relacionada con el sistema.

  • Respecto a un usuario, nos interesa almacenar:
    • Login y password
    • Nombre y apellidos
    • Tipo de usuario / Rol: Bibliotecario, profesor, alumno
    • Estado de un usuario: activo, moroso o multado
    • Correo electrónico
    • Datos referentes a su dirección, como son calle, número, piso, ciudad y código postal.
    • Si el usuario es alumno, necesitaremos guardar quién es su tutor
    • Si el usuario es profesor, necesitaremos su departamento
  • Cuando un usuario se retrase en la devolución de un libro, se le creará una multa, de la cual nos interesa saber la fecha de inicio y de finalización de la misma. Nos han comunicado que quieren mantener un histórico de las multas que ha tenido un usuario.
  • Respecto a un libro, nos interesa almacenar:
    • Título y autor
    • ISBN
    • Número de páginas
    • Fecha de alta del libro
  • Respecto a los préstamos, tras muchas entrevistas deducimos que tanto las reservas, como los prestamos tienen características comunes, como son:
    • Tipo: préstamo normal, préstamo depósito o reserva
    • Fecha de inicio y finalización
    • Usuario de la operación
    • Libro de la operación
  • Por último, las pre-reservas se van a almacenar sólo mientras no se hayan convertido en reservas. En cada reserva guardaremos:
    • Usuario que realiza la pre-reserva
    • Libro
    • Fecha de inicio

Casos de Uso

En un principio, el número de casos de uso es muy limitado. A lo largo del proyecto nos vamos a centrar en el desarrollo de los siguientes casos de uso:

Diagrama de Casos de Uso Principal

Destacar que la validación de usuarios para todos los actores se considera una precondición que deben cumplir todos los casos de uso, y por lo tanto no se muestra en el diagrama. Entendemos también que la realización de una reserva por parte de un usuario

A continuación vamos a mostrar en mayor detalle los casos de uso separados por el tipo de usuario, de modo que quede más claro cuales son las operaciones que debe soportar la aplicación.

Un poco de UML...
Recordar que las relaciones entre casos de uso con estereotipo ‹include› representan que el caso de uso incluido se realiza siempre que se realiza el caso base. En cambio, el estereotipo ‹extend› representa que el caso de uso extendido puede realizarse cuando se realiza el caso de uso padre (o puede que no).
Bibliotecario

El tipo de usuario bibliotecario es el más complejo de nuestra aplicación. Como se puede observar, es el que va a poder realizar un mayor número de casos de uso:

Diagrama de Casos de Uso detallado para el Bibliotecario

Claramente en el centro podemos observar como el caso "Consultar Libros" es el eje de la aplicación. A partir del listado de libros, ya sean disponibles, reservados o prestados, este tipo de usuario podrá gestionar los libros (alta, baja o modificación) y realizar las operaciones de reserva, préstamo o devolución (en nombre de un determinado usuario registrado). Finalmente, también tiene la posibilidad de consultar un histórico de operaciones, tanto de reservas como de prestamos realizados.

Alumno o Profesor

En cuanto a un usuario cuyo tipo sea alumno o profesor, y por tanto, sea un usuario registrado en el sistema, las operaciones que puede realizar son las siguientes:

Diagrama de Casos de Uso detallado para el Usuario Registrado

En cierta medida, este tipo de usuario va a poder realizar las mismas consultas que un bibliotecario, pero sólo desde su punto de vista (sus reservas, sus prestamos), sin poder visualizar que usuarios tienen reservado/prestado un determinado libro.

Además, solamente podrá reservar libros. Para formalizar el prestamo, deberá acudir a la biblioteca, donde el bibliotecario realizará el préstamo en su nombre.

Más adelante, conforme cambien los requisitos del cliente (que siempre cambian), puede que el sistema permita renovar los prestamos a los usuarios registrados, que éstos puedan modificar su información de usuario, que los bibliotecarios obtengan informes sobre libros más prestados y/o reservados, etc...

Requisitos de Restricción (CRQ)

Respecto a las restricciones que se aplicarán tanto a los casos de uso como a los requisitos de información, y que concretan las reglas de negocio, hemos averiguado:

  • Diferencias a la hora de realizar una operación tanto por parte de un profesor como de un alumno:

    Número Máximo de LibrosDías de ReservaDías de Prestamo
    Alumno357
    Profesor101030


    Según esta información, un alumno solo puede reservar un máximo de 3 libros, teniendo 5 días para recoger el libro. Si a los 5 días de realizar la reserva no ha acudido a la biblioteca a formalizar el préstamo, se anulará la reserva, de modo que el libro quedará disponible. En cuanto a los prestamos, deberá devolver el libro como mucho una semana después.

  • En el momento que un usuario tenga una demora en la devolución de un préstamo, se considerará al usuario moroso y se le impondrá una penalización del doble de días de desfase durante los cuales no podrá ni reservar ni realizar préstamos de libros.

El equipo directivo del "jUA" nos ha confirmado que para la primera versión de la aplicación se van a cumplir las siguientes premisas:

  • No se permite más de una existencia de un libro. Es decir, sólo hay un ejemplar de cada libro, y la aplicación no permite libros repetidos.
  • Un libro sólo tiene un autor.

Análisis y Diseño OO

A partir de esta captura de requisitos inicial, vamos a plantear los elementos que van a formar parte de la aplicación, comenzado por el modelo de clases.

Modelo de Clases Conceptual

A partir de los requisitos y tras unas sesiones de modelado, hemos llegado al siguiente modelo de clases conceptual representado mediante el siguiente diagrama UML:

Diagrama de Clases

El elemento central de nuestro sistema va a ser una clase Operacion, el cual, dependiendo de un atributo enumerado, nos indicará si el objeto es un préstamo, un depósito o una reserva. Además, cada operación se compone de un Libro y de un Usuario. La clase tendrá dos subclases, Activa y Historica, para reflejar la distinta cardinalidad de la relación con Libro. La relación entre Libro y Activa es uno-a-uno (un libro sólo puede estar una operación activa), mientras que entre Libro y Historica es uno-a-muchos.

Alternativa
Otra forma de modelar este problema sería mediante una relación de herencia entre una clase abstracta Operacion, del cual tuviésemos las clases extendidas de Reserva y Prestamo, pero realmente para la lógica de nuestra aplicación, no nos interesa esta separación.

La decisión de desnormalizar esta jerarquía se debe a que realmente estas dos entidades no tienen ningún atributo diferenciador, ni se prevé que vayamos a necesitar almacenar algún dato específico de alguna entidad que lo haga diferenciarse una de la otra.

Definimos también una clase Usuario con tres subclases: Bibliotecario, Profesor y Alumno.

Todas las entidades tienen un campo identificador que permitirá indentificarlas de forma única. No usaremos claves primarias naturales (por ejemplo, el isbn de un libro o el login de un usuario) porque hacen que el modelo sea demasiado rígido.

Modelo de Datos Conceptual

Siguiendo las mismas pautas que en el modelado de clases, llegamos a un modelo conceptual de datos muy similar al anterior. Podemos observar que ambos modelos representan el mismo problema y son casi semejantes.

Diagrama Conceptual de Datos

Por donde empezamos
¿ Datos o clases ? Podemos empezar modelando los datos o las clases, y ambos modelos serán casi semejantes. Normalmente, la elección viene dada por la destreza del analista, si se siente más seguro comenzando por los datos, o con el modelo conceptual de clases. Otra opción es el modelado en paralelo, de modo que al finalizar ambos modelos, podamos compararlos y validar si hemos comprobado todas las restricciones.

Diagramas de Estado

A continuación podemos visualizar un par de diagramas que representan los diferentes estados que puede tomar tanto los libros (por medio de las operaciones) como los usuarios (mediante las acciones que conllevan algunas operaciones).

Estados de un libro

En el caso de un libro, su estado determina las operaciones que se pueden realizar con él. Para saber el estado actual de un libro, tendremos que consultar en la base de datos las relaciones que tiene con operaciones y con pre-reservas:

  • Si no tiene ninguna operación activa ni está pre-reservado: disponible
  • Si tiene una pre-reserva: pre-reservado
  • Si tiene una operación activa de tipo reserva: reservado en sala
  • Si tiene una operación activa de tipo préstamo o depósito: prestado

Las operaciones que hacen que un libro cambie de un estado a otro son las siguientes:

Diagrama de Estados de un Libro

Estados de un usuario

Del mismo modo, los estados de un usuario dependen de si el usuario realiza sus operaciones dentro de las reglas de negocio permitidas. Si un usuario devuelve un libro con fecha caducada, se le multará y pasará a ser moroso. Destacar que si un usuario posee un libro con fecha de devolución caducada, hasta que el usuario no devuelve el libro, no se le considera moroso, ya que no podemos calcular la cuantía de la multa.

Diagrama de Estados de un Usuario

Esta lógica se puede tratar de diferentes modos, pero por ahora, como veremos a continuación, cuando un usuario entra al sistema, comprobaremos tanto si es moroso como si tiene libros pendientes de devolución cuya fecha ya ha caducado.

Validación de Usuarios

Dados los diferentes tipos de usuarios y la compleja lógica de entrada a la aplicación, hemos dedicado una sesión de modelado a estudiar cual debe ser el comportamiento de la aplicación cuando un usuario se valida en el sistema.

El siguiente diagrama de actividad representa toda la posible casuística:

Diagrama de Actividad para la entrada al sistema

Reserva/Prestamo de un Libro

Tanto las operaciones de reserva como de préstamo de un libro conllevan una serie de comprobaciones que hemos definido en el siguiente diagrama:

Diagrama de Actividad para la reserva de un libro

Implementación

El objetivo de la sesión de hoy es crear el proyecto jbib-modelo, módulo inicial en el que vamos a incluir todas las clases de dominio que implementan las entidades de la aplicación, y las clases auxiliares (tipos enumerados y clases auxiliares). Vamos a construirlo utilizando Maven. A lo largo de las siguientes sesiones del proyecto iremos construyendo nuevos módulos necesarios para la aplicación.

Al ser un proyecto formado por múltiples módulos, utilizaremos una estructura Maven de multiproyecto, en el que un directorio padre con un POM define las características comunes y los módulos y contiene a los distintos módulos en subdirectorios. Cada subdirectorio define un módulo y contiene su propio fichero POM que describe sus características.

Construcción del proyecto Maven

Vamos a crear el módulo inicial del proyecto de integración. Todos los módulos los crearemos en un espacio de trabajo eclipse llamado proy-int dentro de /home/especialista. Para ello, seleccionamos un espacio de trabajo con ese nombre. Si no existe el directorio, Eclipse lo crea.

Espacio de trabajo proyint

Vamos a utilizar el asistente de Eclipse para crear proyectos Maven. Pulsamos con el botón derecho en el panel de proyectos la opción New > Project... > Maven > Maven Project:

Nuevo proyecto Maven

Vamos a crear el proyecto padre de todos los módulos que iremos desarrollando más adelante. Seleccionamos la opción skip archetype selection para no basar el proyecto Maven en ningún arquetipo. Eclipse únicamente generará el proyecto con la estructura de directorios Maven adecuada y el POM mínimo con su descripción.

Skip archetype selection

Introduce los datos del proyecto necesarios para crear el POM inicial de Maven, escribiendo tu login en el nombre del proyecto:

  • GroupId: org.especialistajee
  • ArtifactId: login-proyint-jbib
  • Version: 2011
  • Packaging: POM
  • Name: login-proyint-jbib

Datos proyecto padre

Eclipse creará un proyecto nuevo en el que existirá un directorio de fuentes (src) y el fichero POM que descibre este proyecto padre. En el icono del proyecto puedes ver una pequeña "M" que indica que se trata de un proyecto Maven y que el plugin Maven de Eclipse lo ha reconocido como tal.

Proyecto padre

Podemos abrir el fichero POM para comprobar que el XML se ha creado correctamente:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.especialistajee</groupId>
  <artifactId>pperez-proyint-jbib</artifactId>
  <version>2011</version>
  <packaging>pom</packaging>
  <name>pperez-proyint-jbib</name>
  <description>Proyecto padre de los módulos del proyecto de 
	integración Biblioteca Java</description>
</project>

Procedemos a continuación a crear el módulo con el que vamos a trabajar en esta sesión: jbib-modelo (precedido de tu login). Lo hacemos también utilizando el asistente de Eclipse. Seleccionamos con el botón derecho la opción New > Project... > Maven > Maven Module:

Creando el módulo jbib-modelo

Marcamos también la opción de saltar la selección del arquetipo y escribimos el nombre del módulo y del proyecto padre:

Nombre módulo y proyecto padre

Introducimos a continuación los datos del módulo para crear su POM

  • GroupId: org.especialistajee
  • ArtifactId: login-jbib-modelo
  • Version: 2011
  • Packaging: jar
  • Name: login-jbib-modelo

Es importante el tipo de empaquetamiento. El módulo se va a empaquetar como un JAR conteniendo todas las clases del dominio y las clases de utilidad. Hay que definir esta opción. Ya veremos más adelante como Maven automatiza el proceso de compilación, prueba y empaquetamiento del JAR.

Datos del módulo jbib-modelo

Podemos comprobar ahora que el proyecto se ha creado correctamente y que su estructura corresponde con la de un proyecto Maven:

Estructura proyecto jbib-modelo

Vemos que se ha creado un directorio de fuentes src/main, y otro directorio de tests src/tests. También vemos un directorio target donde Maven colocará los ficheros .class compilados y el JAR resultante del empaquetado del proyecto.

Si revisas los ficheros POM veremos que en el del proyecto padre se ha añadido el nombre del módulo recién creado. El POM del módulo contiene los datos introducidos:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>pperez-proyint-jbib</artifactId>
    <groupId>org.especialistajee</groupId>
    <version>2011</version>
  </parent>
  <groupId>org.especialistajee</groupId>
  <artifactId>pperez-jbib-modelo</artifactId>
  <version>2011</version>
  <name>pperez-jbib-modelo</name>
  <description>Paquete JAR que implemeta el modelo de dominio 
	Biblioteca Java</description>
</project>

Por último, para comprobar que la estructura de directorios es correcta, podemos abrir el explorador de archivos y asegurarnos de que se ha creado un directorio padre con el proyecto padre y un subdirectorio hijo con el módulo jbib-modelo:

Estructura proyecto en el sistema de ficheros

Ficheros POM

Hay que añadir algunos elemenots a los ficheros POM para configurar correctamente los proyectos

En primer lugar, podemos ver que el plug-in de Maven interpreta incorrectamente la versión de la máquina virtual Java del proyecto. En el proyecto aparece la J2SE-1.5, en lugar de la que está configurada por defecto en Eclipse, la 1.6. Para solucionar el aviso, indicamos en el POM del proyecto padre explícitamente que el proyecto va a trabajar con la versión 1.6 del compilador Java.

También indicamos que la codificación de los ficheros de código fuente que componen el proyecto es UTF-8 (la versión de Eclipse de la máquina virtual está configurado de esa forma, se puede comprobar en Windows > Preferences > General > Workspace > Text file encoding).

Para ello añadimos el siguiente código al fichero POM del proyecto padre (login-proyint-jbib/pom.xml). Para que el plugin de Maven actualice los proyectos, hay que pulsar con el botón derecho la opción Maven > Update project configuration. Hazlo en el proyecto hijo jbib-modelo y verás que se actualiza la versión de Java y desaparece el aviso.

<project> 
  ...

  <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>


  <build>
   <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
             <source>1.6</source>
             <target>1.6</target>
         </configuration>
      </plugin>
   </plugins>
</build>

</project>

Por último, añadimos en el proyecto padre las dependencias de algunas librerías que vamos a usar en todos los módulos del proyecto:

  • Librería de pruebas junit: junit-4.8.1
  • Librerías de gestión de logs commons-logging y log4j: commons-logging-1.1.1 y log4j-1.2.12
   ...
   </properties>

   <dependencies>
      <dependency>
         <groupId>log4j</groupId>
         <artifactId>log4j</artifactId>
         <version>1.2.12</version>
         <type>jar</type>
         <scope>compile</scope>
      </dependency>

      <dependency>
         <groupId>commons-logging</groupId>
         <artifactId>commons-logging</artifactId>
         <version>1.1.1</version>
         <type>jar</type>
         <scope>compile</scope>
      </dependency>

      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.8.1</version>
         <type>jar</type>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
   ...

El proyecto se actualizará automáticamente, y podremos ver las librerías importadas bajo las Maven Dependencies:

Estructura proyecto en el sistema de ficheros

Nota
También es posible añadir dependencias utilizando el asistente de Eclipse, que permite ademas buscar por nombre y versión.

Las librerías descargadas se guardan en el directorio /home/especialista/.m2 (repositorio local de Maven). Puedes explorar el directorio con el navegador de archivos para comprobar que los ficheros JAR se encuentran allí. En el repositorio exiten más librerías porque las hemos descargado al montar la máquina virtual para que sea más rápida la construcción de los proyectos.

Primera iteración

Primera clase entidad

Las clases de dominio representan las entidades que van a relacionarse en la aplicación. En las siguientes sesiones, cuando veamos JPA, veremos cómo se podrán definir la capa de persistencia de la aplicación directamente a partir de estas clases. Serán también objetos que podremos utilizar como Transfer Objects (TOs) dentro del sistema, realizando funciones de meros contenedores y viajando por las capas de la aplicación.

Para asegurarnos que todos nuestros objetos de dominio tienen una estructura común, definimos una clase abstracta, que será la clase padre de todas las clases de dominio. La hacemos serializable para asegurar que los objetos pueden transmitirse entre capas físicas de la aplicación (por ejemplo, entre la capa de presentación y la capa de lógica de negocio) y definimos los métodos equals() y hashCode() para obligar a que las clases hijas implementen la redefinan la igualdad. Estos métodos son muy útiles en el caso en el que los objetos se guarden y queramos buscarlos en colecciones.

package org.especialistajee.jbib.model;

import java.io.Serializable;

/**
 * Padre de todos los objetos de dominio del modelo. Los objetos del dominio
 * están relacionados entre si y se hacen persistentes utilizando entidades JPA.
 * Pulsando F4 en Eclipse podemos ver la jerarquía. En la clase definimos los
 * métodos abstractos que deben implementar todas las clases del dominio. Los
 * objetos de dominio participan entre ellos en relaciones uno-a-uno,
	 * uno-a-muchos y muchos-a-uno y estan relacionados mediante referencias. Tendrán
 * una clave única que permite identificarlos en la BD. Una Direccion no es un
 * objeto de dominio.
 */

public abstract class DomainObject implements Serializable {
	private static final long serialVersionUID = 1L;

	public abstract boolean equals(Object object);
	public abstract int hashCode();
}

Todas las entidades las vamos a definir dentro del paquete org.especialistajee.jbib.model, y utilizaremos el sufijo Domain a modo de nomenclatura para indicar que un objeto de la aplicación es una entidad.

Cada entidad se compone de sus atributos, relaciones y de todos los getter/setter que encapsulan al objeto. Así pues, por ejemplo, la representación del Domain Object LibroDomain sería:

package org.especialistajee.jbib.model;

import java.util.Date;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LibroDomain extends DomainObject {
	private static final long serialVersionUID = 1L;

	private Long id;
	private String isbn;
	private String titulo;
	private String autor;
	private Integer numPaginas;
	private Date fechaAlta;
	private Set<HistoricaDomain> historicas = new HashSet<HistoricaDomain>();
	private ActivaDomain activa;
	private PrereservaDomain prereserva;

	static Log logger = LogFactory.getLog(LibroDomain.class);

	// Definimos un hashCode basado en un campo que no cambia y que existe en el
	// momento de la creación con new, ya que el id autogenerado por la base de 
	// datos no existe en ese momento
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + isbn.hashCode();
		return result;
	}

    // Definimos equals dos distintos niveles: a nivel de referencia, y a nivel 
    // de identificador de base de datos. Dos objetos que tienen la misma referencia 
    // son simpre equals. Dos objetos con distintas referencias también pueden ser 
    // equals si contienen el mismo identificador (generado por la base de datos).
    @Override
    public boolean equals(Object obj) {
       if (id == null)
           return this == obj;
       else {
           if (obj == null)
             return false;
           if (getClass() != obj.getClass())
             return false;
           LibroDomain other = (LibroDomain) obj;
           return id.equals(other.getId());
       }
    }

	public LibroDomain() {
	}

	public LibroDomain(String isbn) {
		this.isbn = isbn;
	}

	public Long getId() {
		return this.id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getAutor() {
		return autor;
	}

	public void setAutor(String autor) {
		this.autor = autor;
	}

	public String getIsbn() {
		return isbn;
	}

	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}

	public Integer getNumPaginas() {
		return numPaginas;
	}

	public void setNumPaginas(Integer numPaginas) {
		this.numPaginas = numPaginas;
	}

	public String getTitulo() {
		return titulo;
	}

	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}

	public Date getFechaAlta() {
		return fechaAlta;
	}

	public void setFechaAlta(Date fechaAlta) {
		this.fechaAlta = fechaAlta;
	}

	public void setHistoricas(Set<HistoricaDomain> operacionesHistoricas) {
		this.historicas = operacionesHistoricas;
	}

	public void addOperacionHistorica(HistoricaDomain historica) {
		if (historica == null)
			throw new IllegalArgumentException("Null operacion");
		historicas.add(historica);
		// se actualiza la parte inversa de la relación
		historica.setLibro(this);
	}

	public Set<HistoricaDomain> getHistoricas() {
		return historicas;
	}

	public void setActiva(ActivaDomain activa) {
		this.activa = activa;
	}

	public ActivaDomain getActiva() {
		return activa;
	}

	public void setPrereserva(PrereservaDomain prereserva) {
		this.prereserva = prereserva;
	}

	public PrereservaDomain getPrereserva() {
		return prereserva;
	}
}

Es interesante hacer notar la forma en que están implementados los métodos hashCode y equals. Son métodos muy importantes porque son los que se utilizan para buscar en las colecciones. Dos importante ideas relacionadas con estos métodos (ver el capítulo 3 de Effective Java de Joshua Bloch):

  • Si dos objetos son equals deben tener el mismo hashCode, pero no tiene por qué ser al revés. En nuestro caso, el método hashCode se basa en el campo isbn. Si dos libros son equals puede ser porque: a) tengan la misma referencia o b) tengan el mismo identificador. En ambos casos van a tener el mismo isbn. Puede ser que al revés no. Supongamos dos libros que tienen el mismo isbn pero distinto identificador (dos ejemplares de un mismo libro). Tendrían el mismo hashCode pero no serían equals. Esto es posible, ya que el hashCode define la posición en una tabla hash, pero en una posición puede haber más de un elemento.
  • El método hashCode debe devolver un valor estable, que no cambie en el tiempo que un objeto está en una colección. En nuestro caso también es correcto, ya que basamos el hashCode en un campo que no se modifica, el isbn del libro (una clave natural).

Eliminando errores

Definimos las clases vacías necesarias para eliminar los errores de la clase Libro:

  • Clase abstracta OperacionDomain y sus clases hijas ActivaDomain y HistoricasDomain.
  • Clase PrereservaDomain

Ficheros de recursos

Añadimos los ficheros de recursos necesarios para el funcionamiento de los logs.

Fichero src/main/resources/commons-logging.properties:

org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger

Fichero src/main/resources/log4j.properties:

# Coloca el nivel root del logger en DEBUG (muestra mensajes de DEBUG hacia arriba)
# Añade appender A1
log4j.rootLogger=DEBUG, A1

# A1 se redirige a la consola
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Threshold=INFO

# A1 utiliza PatternLayout
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d{dd/MM/yyyy HH:mm:ss} - %p - %m %n

Primer test

Definimos el primer test con la clase org.especialistajee.jbib.model.LibroDomainTest en el directorio src/test/java, con el que probamos el funcionamiento correcto de la igualdad:

  • Si no se ha definido el identificador del libro (de tipo Long), equals se comporta como la igualdad de referencia.
  • En cuanto se definen el identificador del libro, equals compara estos identificadores.
package org.especialistajee.jbib.model;

import static org.junit.Assert.*;

import org.junit.Test;

public class LibroDomainTest {
    @Test
    public void testEquals() {
        LibroDomain lib1 = new LibroDomain("123456789");
        LibroDomain lib2 = new LibroDomain("123456789");
        LibroDomain lib3 = lib1;
        
        assertFalse(lib1.equals(lib2));
        assertTrue(lib1.equals(lib3));
        lib1.setId(1L);
        lib2.setId(1L);
        assertTrue(lib1.equals(lib2));
        lib2.setId(2L);
        assertFalse(lib1.equals(lib2));
    }
}

Ejectuamos el test con el botón derecho sobre la clase o el paquete: Run As > JUnit Test y debe aparecer en verde:

Datos proyecto padre

Construcción con Maven

Abrimos un terminal y ejecutamos los comandos Maven para construir el JAR que contiene el proyecto:

$ cd /home/especialista/proyint/pperez-proyint-jbib
$ mvn clean
$ mvn install

Todos los comandos Maven los ejecutamos en el directorio del proyecto principal, que contiene el subproyecto jbib-modelo. Cuando añadamos más subproyectos los empaquetaremos de la misma forma.

El comando Maven clean borra todos los ficheros .class que puedan haber sido creados anteriormente. El comando install realiza secuencialmente las siguientes acciones:

  • Compila todos los módulos del proyecto principal
  • Ejecuta todos los tests
  • Si los tests son correctos, empaqueta el subproyecto pperez-jbib-modelo en el fichero JAR pperez-jbib-modelo-2011.jar
  • Copia el fichero JAR en el repositorio local de Maven (directorio .m2) para dejarlo como una librería disponible para otros proyectos.

La última parte de la salida del comando debe ser esta:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.especialistajee.jbib.model.LibroDomainTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.144 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ pperez-jbib-modelo ---
[INFO] Building jar: /home/especialista/proyint/pperez-proyint-jbib/pperez-jbib-
modelo/target/pperez-jbib-modelo-2011.jar
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ pperez-jbib-modelo ---
[INFO] Installing /home/especialista/proyint/pperez-proyint-jbib/pperez-jbib-modelo/
target/pperez-jbib-modelo-2011.jar to /home/especialista/.m2/repository/
org/especialistajee/pperez-jbib-modelo/2011/pperez-jbib-modelo-2011.jar
[INFO] Installing /home/especialista/proyint/pperez-proyint-jbib/pperez-jbib-modelo/
pom.xml to /home/especialista/.m2/repository/org/especialistajee/pperez-jbib-modelo/2011/
pperez-jbib-modelo-2011.pom
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] pperez-proyint-jbib ............................... SUCCESS [0.369s]
[INFO] pperez-jbib-modelo ................................ SUCCESS [1.342s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.882s
[INFO] Finished at: Sat Oct 08 08:07:18 CEST 2011
[INFO] Final Memory: 4M/15M
[INFO] ------------------------------------------------------------------------

Todos los ficheros .class y JAR se guardan en el directorio target del proyecto. Puedes explorar el sistema de archivos para comprobarlo.

Esto lo podemos hacer también desde el asistente de Eclipse situándonos en el proyecto principal y pulsando con el botón derecho la opción Run As > Maven instal. Puede ser que la salida de la consola sea ligeramente distinta, porque Eclipse usa una versión de Maven instalada en el propio entorno.

Para usar la misma versión que cuando la lanzamos desde línea de comandos, podemos seleccionar la opción Window > Preferences > Maven > Installations y añadir la ruta en la que se encuentra Maven en el sistema operativo: /opt/apache-maven-3.0.3.

Repositorio SVN

Terminamos esta primera iteración subiendo el proyecto al repositorio SVN.

Aviso
Los directorios target no deben subirse al repositorio SVN, ya que sólo debemos colocar allí los ficheros fuentes. Podemos configurar Eclipse de forma que haga que Subversion los ignore. Debemos entrar en la opción Window > Preferences > Team > Ignored Resources y añadir el patrón target. De esta forma el cliente de Subversion no subirá ni el directorio ni sus contenidos.

Pulsamos con el botón derecho sobre el proyecto padre la opción Team > Share project y creamos la conexión SVN con la URL svn+ssh://server.jtech.ua.es/home/svn/pperez/proyint. Después escojemos la opción Advanced Mode para espeficicar la localización del proyecto. La localización que debe aparecer en la parte inferior del asistente es:

svn+ssh://server.jtech.ua.es/home/svn/pperez/proyint/trunk/pperez-proyint-jbib

Subiendo a SVN el proyecto padre, se suben también todos los subproyectos.

Enumeraciones

En cuanto a las enumeraciones, Java (desde su versión 5.0) permite su creación mediante la clase java.lang.Enum. En nuestro caso, por ejemplo, la enumeración de EstadoUsuario quedaría del siguiente modo:

package org.especialistajee.jbib.model;

public enum EstadoUsuario {
   ACTIVO, MOROSO, MULTADO
}

Definimos al menos las enumeraciones:

  • EstadoLibro
  • EstadoMulta
  • EstadoUsuario
  • TipoOperacion

Implementar y completar las clases de entidad

Para implementar nuestras clases, tendremos que codificar todas las clases y relaciones expuestas en el Modelo de Clases Conceptual.

Hay que tener especial cuidad con las relaciones de herencia. Tenemos dos: la clase UsuarioDomain y las clases hijas BibliotecarioDomain, ProfesorDomain y AlumnoDomain. También la clase OperacionDomain y sus clases hijas ActivaDomain e HistoricaDomain. En ambos casos la clase padre es abstracta.

Algunas características comunes a todas las clases de entidad:

  • En todas se define un identificador de tipo Long que será generado por la base de datos y que constítuirá la clave primaria de la entidad. El método equals se define en base a este idenficador de la misma forma que hemos hecho con la clase Libro.
  • En todas las clases el método hashCode se basa en un campo que debe siempre estar definido en el objeto. En todas las clases debemos definir un constructor público que contenga un número mínimo de campos, entre los que estará el campo usado para el número hash. Por ejemplo el login en la clase UsuarioDomain o el usuario en la clase MultaDomain.
  • Las relaciones X-a-muchos las definimos del tipo Set. De esta forma nos aseguramos que no existen objetos duplicados en las relaciones. La identidad en un conjunto se define con el método equals.

Definimos también la clase Direccion que será una clase no entidad y se utilizará en la entidad UsuarioDomain.

Nota
No olvides implementar un método getEstado en la clase Libro. El estado del libro se puede calcular a partir de sus relaciones.

Gestión de las Excepciones

Todas las aplicaciones empresariales definen una política de gestión de las excepciones de aplicación.

En nuestro caso, conforme crezca el proyecto, iremos creando nuevas excepciones. Como punto de partida, y como buena norma de programación, vamos a definir una excepción genérica de tipo unchecked (BibliotecaException), que será la excepción padre de todas las excepciones de aplicación de la biblioteca. El hecho de que la excepción se unchecked remarca el carácter de que estamos definiendo excepciones relacionadas con el mal uso del API. En general, un método debe realizar su funcionalidad y terminar correctamente cuando todo ha funcionado bien. Se lanzará una excepción si algo falla. Por ejemplo, cuando definamos un método prestar(libro,usuario) lanzaremos excepciones cuando no se cumplan las condiciones que hacen que el libro pueda ser prestado al usuario. Al lanzar excepciones no chequeadas permitimos que el programador chequee las condiciones antes de llamar al método y no tenga que obligatoriamente capturar una excepción que sabemos que no se va a producir.

Definimos las excepciones en el paquete global org.especialistajee.jbib

package org.especialistajee.jbib;

public class BibliotecaException extends RuntimeException {
   private static final long serialVersionUID = 1L;
   
   public BibliotecaException() {
      super();
   }
   
   public BibliotecaException(String message) {
      super(message);
   }
   
   public BibliotecaException(String message, Throwable cause) {
      super(message, cause);
   }
   
}

Podemos observar como, al sobrecargar el constructor con los parámetros {String, Throwable}, nuestra excepción permitirá su uso como Nested Exception.

Implementación de las Reglas de Negocio

Es común agrupar las reglas de negocio de una aplicación en una o más clases (dependiendo de los diferentes subsistemas de la aplicación), para evitar que estén dispersas por la aplicación y acopladas a un gran número de clases.

En nuestro caso, vamos a crear un Singleton, al que llamaremos BibliotecaBR (BR = Business Rules). En principio, los valores estarán escritos directamente sobre la clase, pero en un futuro podríamos querer leer los valores de las reglas de negocio de un fichero de configuración).

El esqueleto de nuestras reglas de negocio será el siguiente:

package org.especialistajee.jbib;

// faltan los imports

/**
 * Reglas de Negocio de la Biblioteca
 * BR = Business Rules
 * 
 * Lo implementamos como un singleton por si algun dia queremos leer
 * las constantes desde un fichero de configuración, lo podemos
 * hacer desde el constructor del singleton
 */
public class BibliotecaBR {

   private static BibliotecaBR me = new BibliotecaBR();

   private BibliotecaBR() {
   }

   public static BibliotecaBR getInstance() {
      return me;
   }
   
   /**
    * Calcula el número de dias de plazo que tienen un usuario para
    * realizar una reserva (Socio = 5 , Profesor = 10)
    * @param tipo tipo del usuario
    * @return número de dias de reserva
    * @throws BibliotecaException el tipo del usuario no es válido
    */
   public int calculaNumDiasReserva(TipoUsuario tipo)
         throws BibliotecaException {
      // TODO Completar
   }

   /**
    * Calcula el número de dias de plazo que tienen un usuario para
    * realizar un prestamo (Socio = 7 , Profesor = 30)
    * @param tipo tipo del usuario
    * @return número de dias del prestamo
    * @throws BibliotecaException el tipo del usuario no es válido
    */
   public int calculaNumDiasPrestamo(TipoUsuario tipo)
         throws BibliotecaException {
      // TODO Completar
   }
   
   /**
    * Valida que el número de operaciones realizadas por
    * un determinado tipo de usuario se inferior al cupo definido
    * @param tipo tipo del usuario
    * @param numOp número de operación que ya tiene realizadas
    * @throws BibliotecaException el cupo de operacion esta lleno, 
    *  o el tipo del usuario no es el esperado
    */
   public void compruebaCupoOperaciones(TipoUsuario tipo, int numOp)
         throws BibliotecaException {
      // TODO Completar
   }
}

Podemos observar que vamos a tener un método por cada regla de negocio, y que estos métodos lanzarán excepciones de aplicación en el caso de un comportamiento anómalo.

Tests

Vamos a definir dos tipos de tests, unos relacionados con las clases del modelo y otros con las reglas de negocio.

Los tests se deben colocar dentro de la carpeta test, en el mismo paquete que la clase a probar.

Tests de reglas de negocio:

package org.especialistajee.jbib;

import static org.junit.Assert.*;

import org.especialistajee.jbib.model.TipoUsuario;
import org.junit.Test;

/**
 * Pruebas jUnit sobre las reglas de negocio de la biblioteca
 */
public class BibliotecaBRTest {

   @Test
   public void testCalculaNumDiasReservaProfesor() {
      int diasProfesor = BibliotecaBR.getInstance()
            .calculaNumDiasReserva(TipoUsuario.PROFESOR);
      assertEquals(diasProfesor, 10);
   }

   @Test
   public void testCalculaNumDiasReservaSocio() {
      int diasAlumno = BibliotecaBR.getInstance()
            .calculaNumDiasReserva(TipoUsuario.ALUMNO);
      assertEquals(diasAlumno, 5);
   }

   @Test
   public void testCalculaNumDiasPrestamoProfesor() {
      int diasProfesor = BibliotecaBR.getInstance()
            .calculaNumDiasPrestamo(TipoUsuario.PROFESOR);
      assertEquals(diasProfesor, 30);
   }

   @Test
   public void testCalculaNumDiasPrestamoSocio() {
      int diasAlumno = BibliotecaBR.getInstance()
            .calculaNumDiasPrestamo(TipoUsuario.ALUMNO);
      assertEquals(diasAlumno, 7);
   }

   @Test
   public void testCompruebaCupoOperacionesProfesorCorrecto() {
      try {
         BibliotecaBR.getInstance()
               .compruebaCupoOperaciones(
                     TipoUsuario.PROFESOR, 4);
         BibliotecaBR.getInstance()
               .compruebaCupoOperaciones(
                     TipoUsuario.PROFESOR, 0);
      } catch (BibliotecaException e) {
         fail("No debería fallar - el cupo de operaciones del PROFESOR es correcto");
      }
   }

   @Test(expected = BibliotecaException.class)
   public void testCompruebaCupoOperacionesProfesorIncorrecto()
         throws BibliotecaException {
      BibliotecaBR.getInstance().compruebaCupoOperaciones(
            TipoUsuario.PROFESOR, 11);
   }

   @Test
   public void testCompruebaCupoOperacionesAlumnoCorrecto() {
      try {
         BibliotecaBR.getInstance()
               .compruebaCupoOperaciones(
                     TipoUsuario.ALUMNO, 2);
         BibliotecaBR.getInstance()
               .compruebaCupoOperaciones(
                     TipoUsuario.ALUMNO, 0);
      } catch (BibliotecaException e) {
         fail("No debería fallar - el cupo de operaciones del ALUMNO es correcto");
      }
   }

   @Test(expected = BibliotecaException.class)
   public void testCompruebaCupoOperacionesAlumnoIncorrecto()
         throws BibliotecaException {
      BibliotecaBR.getInstance().compruebaCupoOperaciones(
            TipoUsuario.ALUMNO, 4);
   }
}

En los tests de las clases del modelo debemos de comprobar:

  • Relaciones de igualdad.
  • Actualizaciones de las relaciones entre entidades. Por ejemplo, podemos crear una operación, un libro y un usuario, asociar la operación al libro y al usuario y comprobar que los métodos get devuelven correctamente las relaciones.

Resumen

En esta sesión vamos a preparar la base para el resto de sesiones. Por ellos, debemos crear un proyecto padre Maven, al que llamaremos login-proyint-jbib, que contendrá al módulo login-jbib-modelo con el modelo de dominio de la aplicación:

  1. modelo de objetos dentro del paquete org.especialistajee.jbib.model, implementando cada entidad con los atributos representados en el diagrama UML, sus relaciones, y teniendo en cuenta la relación de herencia con la clase DomainObject y los constructores necesarios y los métodos de acceso.
  2. clase BibliotecaBR con las reglas de negocio, implementada como un singleton, la cual debe pasar las pruebas JUnit aportadas. Para implementar esta clase, es necesaria la clase BibliotecaException.
  3. tests de las clases de dominio y de las reglas de negocio que realicen algunas comprobaciones de los métodos equals y de las actualizaciones de las relaciones.

Las siguientes imágenes muestran todos los archivos que debe contener el proyecto Eclipse:

Estructura de Proyecto I Estructura de Proyecto II

Debemos utilizar la etiqueta entrega-proyint-modelo El plazo final de entrega será el jueves 20 de octubre. Se realizará una sesión on-line de dudas el lunes 17 de 19:00 a 21:00.