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.

Primer contacto con Maven

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 a partir de un proyecto Java. Los proyectos Java se organizan en directorios con una determinada estructura. Por ejemplo:

src
bin
lib
resources
  • src: ficheros fuente Java
  • bin: ficheros .class compilados
  • lib: librerías JAR
  • resources: ficheros de recursos

Esta estructura tiene el inconveniente de que no es estándar. Cada equipo de desarrollo y cada entorno de programación tiene una estructura distinta.

En el proceso de desarrollo, cuando queremos construir la aplicación para probarla deberemos compilar las clases en el directorio adecuado, estableciendo la variable CLASSPATH para que incluya las librerías necesarias para compilar, copiar todos los recursos al directorio donde se haya construido la aplicación, realizar algún procesamiento adicional a los ficheros de código en caso de que sea necesario, empaquetar la aplicación y cualquier otra tarea que pueda ser necesaria. Este proceso de construcción y empaquetado nos permitirá generar algo (un artefacto lo llaman en Maven) que podremos entregar al cliente o desplegar en un servidor web o servidor de aplicaciones.

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.

De Ant a Maven

La primera solución ampliamente usada por la comunidad Java para automatizar la construcción de proyectos fue Ant. Fue también la fundación Apache quien desarrolló la herramienta, lanzándola con gran éxito en el verano de 2000. Una gran cantidad de equipos de desarrolladores y empresas la han adoptado desde entonces.

En el directorio raíz del proyecto se define el fichero build.xml en el que se especifican las tareas a realizar para conseguir objetivos (targets) definidos por el equipo de desarrollo (compilación, test, empaquetamiento, etc.). El siguiente código es un ejemplo de un fichero build.xml. Vemos cómo se definen los target compile, run y clean y cómo se especifica las tareas a realizar en cada uno de ellos.

<project name="MiProyecto" default="run" basedir=".">
  <description>
    Ejemplo de fichero build.xml
  </description>

  <!-- Propiedades -->
  <property name="src.home" value="src"/>
  <property name="bin.home" value="bin"/>

  <!-- Declaración de tareas -->
  <taskdef name="mitarea" classname="org.especialistajee.tareas.MiTarea"/>

  <!-- Objetivos -->
  <target name="compile">
    <mkdir dir="${bin.home}"/>
    <javac srcdir="${src.home}"
      destdir="${bin.home}">
    </javac>
  </target>

  <target name="run" depends="compile">
    <java classname="org.especialistajee.prueba.Principal"
          classpath="${bin.home}"/>
  </target>

  <target name="clean">
    <delete dir="${bin.home}"/>
  </target>

</project>

El problema principal de Ant es que estos targets no son estándar, sino que el grupo de desarrollo puede definir las tareas que desee, y definir completamente sus detalles. Esta libertad obliga a tomar decisiones de diseño que no están prefijadas por la herramienta. Por ejemplo, ¿dónde colocar el código fuente? ¿dónde los tests de las unidades? ¿cómo gestionar las dependencias? ¿cómo construir la aplicación a entregar al cliente? ¿qué nombre dar a los targets? La flexibilidad de Ant es también su punto débil. Es necesario especificar en el script de ant todas estas cuestiones o copiarlo de proyectos anteriores.

Y lo peor es que las soluciones que encontremos no serán estándar. Cuando vayamos a otra empresa o proyecto, tendremos que realizar otra vez todas estas configuraciones.

A diferencia de Ant, Maven es un estándar y define un modelo de proyecto y un modelo de ciclo de vida. El fichero pom.xml describe las características del proyecto, asignando valores a determinadas variables como el nombre de proyecto, el tipo de artefacto a producir, el número de versión o las librerías JAR de las que depende. Estas variables están definidas en el propio estándar de Maven. Maven (y sus plugins) se encarga de interpretar el fichero y realizar los comandos necesarios para conseguir los objetivos. A diferencia de Ant el desarrollador no especifica cómo debe hacer Maven las tareas, sólo cuál debe ser el resutado.

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-2.2.1.

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_21
export MAVEN_HOME=/opt/apache-maven-2.2.1
export PATH=${PATH}:${MAVEN_HOME}/bin

Lanzando Maven

Para empezar a conocer Maven lo mejor es verlo en funcionamiento. Tomemos como ejemplo el proyecto jbib-comun que implementaremos en la primera sesión del proyecto de integración. El directorio en el que está el proyecto se llama jbib-comun. Si entramos en él veremos el fichero pom.xml. Es el fichero fundamental de Maven, en el que se describe el proyecto. Lo analizaremos más adelante. La estructura de directorios del proyecto, una vez construido, es la siguiente:

Vemos un directorio main en el que se encuentra el código de proyecto y los ficheros de recursos, y un directorio test con los tests.

Vemos también un directorio target donde Maven compila el proyecto y genera los .class resultantes y la librería JAR en la que están empaquetadas todas estas clases. La construcción de este directorio es automática a partir del directorio de fuentes ejecutando el comando mvn compile:

especialista@especialista:~/proy-int/jbib-comun$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building jbib-comun
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources {execution: default-resources}]
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Compiling 19 source files to /home/especialista/proy-int/jbib-comun/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Ejecutando mvn test Maven lanza los tests del proyecto. En los mensajes de salida vemos que se asegura de que todo está compilado y después lanza los tests.

especialista@especialista:~/proy-int/jbib-comun$ mvn test
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building jbib-comun
[INFO]    task-segment: [test]
[INFO] ------------------------------------------------------------------------
...
[INFO] Nothing to compile - all classes are up to date
...
-------------------------------------------------------
 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

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Si ejecutamos ahora mvn package veremos que Maven vuelve a comprobar que el proyecto está compilado, lanza los tests y por último construye el JAR con el proyecto:

especialista@especialista:~/proy-int/jbib-comun$ mvn package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building jbib-comun
[INFO]    task-segment: [package]
[INFO] ------------------------------------------------------------------------
...
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
...
Results :
Tests run: 19, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: /home/especialista/proy-int/jbib-comun/target/jbib-comun-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

El fichero jbib-comun-1.0.jar es una librería con las clases básicas del proyecto de integración. Está listo para ser utilizado por otros módulos. Para ello, hay que instalarlo en el repositorio de Maven con la instrucción mvn install. El repositorio local de Maven se encuentra en el directorio ${HOME}.m2/repository.

especialista@especialista:~/proy-int/jbib-comun$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building jbib-comun
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
...
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
...
Results :
Tests run: 19, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] [install:install {execution: default-install}]
[INFO] Installing /home/especialista/proy-int/jbib-comun/target/jbib-comun-1.0.jar
 to /home/especialista/.m2/repository/org/especialistajee/
proyint/jbib-comun/1.0/jbib-comun-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Vemos que hay distintos comandos que podemos ejecutar sobre el proyecto. Estos comandos se llaman objetivos o goals en Maven. Cada uno de ellos corresponde a una fase del ciclo de vida del desarrollo de un proyecto y unos objetivos incluyen a otros. Por ejemplo, antes de hacer un install del proyecto, Maven ejecuta todos los objetivos previos: compile, test y package.

Conceptos básicos

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-comun que construiremos más adelante. Veamos su fichero pom.xml. Al comienzo nos encontramos 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-comun</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>jbib-comun</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.proying:jbib-comun:jar:1.0-SNAPSHOT

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>

Aunque en nuestro caso no lo hacemos, 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".

Si un número de proyecto tiene el literal "SNAPSHOT", Maven entenderá que se trata de un proyecto en desarrollo activo y al empaquetarlo y subirlo al repositorio local sustituirá este token por la hora UTC. Por ejemplo, si el número de versión del proyecto es "1.0-SNAPSHOT" y lo construimos el día 9 de octubre de 2010 a las 22:34, Maven construirá el número de versión "1.0-20101009-2234". De esta forma Maven indica que se trata de la instantánea de esa hora en concreto del componente que estamos desarrollando. Cuando otros proyectos que dependen de este se vayan a construir, Maven obtendrá del repositorio local la última instantánea disponible.

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-comun$ 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

Trabajando con Maven

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: 1.0-SNAPSHOT
  • 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=1.0-SNAPSHOT -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 = 1.0-SNAPSHOT
[INFO] Using property: package = es.prensasoft.backend.recomend
Confirm properties configuration:
groupId: es.prensasoft
artifactId: recomend-backend
version: 1.0-SNAPSHOT
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>1.0-SNAPSHOT</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=1.0-SNAPSHOT

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, profesores y alumnos del centro educativo.
  • Los bibliotecarios se encargarán de la gestión de préstamos, reservas y libros. La aplicación les permitirá registrar los préstamos y devoluciones que se realizan en el mostrador de la biblioteca, así como consultar el estado de un determinado libro o usuario.
  • Los bibliotecarios podrán dar de alta libros, modificar su información y darlos de baja.
  • Los bibliotecarios podrán tomar prestados libros con la modalidad de Depósito cuando necesite retirarlos para hacer algún tipo de gestión (cambiar etiquetas, cambiar encuadernación, etc.).
  • Los profesores y alumnos van a poder realizar las siguientes acciones:
    • Pedir un préstamo al bibliotecario
    • Solicitar la reserva de un libro prestado a otro usuario o disponible en la sala
    • Devolver un libro
  • Para pedir 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 en el mostrador. 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 prestados o disponibles en la sala (por ejemplo, cuando están consultando desde casa el catálogo). Un libro reservado sólo podrá prestarse al usuario que ha realizado el préstamo. Un libro reservado no puede tener otras reservas. La reserva de un libro en la sala estará activa un número de días determinado, dependiendo del tipo de usuario que ha hecho la reserva. Si el usuario prestatario es un profesor que tiene el libro en depósito, se le enviará un aviso para solicitar su devolución. En este caso el depósito terminará y comenzará un préstamo.
  • Cuando se realiza una devolución de un libro, si el libro está reservado, la aplicación enviará un aviso al usuario que ha hecho la reserva, el libro pasará a la sala y el usuario podrá pasar a solicitar el préstamo.
  • Se desea mantener un histórico con las reservas y préstamos realizados, tanto por los profesores como por los alumnos.
  • 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:
    • Nombre y apellidos
    • Login y password
    • Tipo de usuario / Rol: Bibliotecario, profesor, alumno
    • Estado de un usuario: activo o moroso
    • Correo electrónico
    • Datos referentes a su dirección, como son calle, número, piso, ciudad y código postal.
  • 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

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.

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 la 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.

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.

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, los estados viene definidos por las operaciones que se pueden realizar. Para poder saber el estado actual de un libro, tendremos que consultar en la base de datos si hay alguna operación existente (activa) que lo relacione.

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

Antes de comenzar a implementar el proyecto, vamos a crear la estructura del proyecto en Eclipse.

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. Lo primero que hacemos es crear el directorio proy-int:

$ cd /home/especialista
$ mkdir proy-int

El primer módulo lo llamaremos jbib-comun y contendrá las clases que representan el modelo de objetos de dominio, así como las reglas de negocio, excepciones, configuración del log, etc.. Todas las clases Java del proyecto estarán en el paquete org.especialistajee.jbib.

Creamos el proyecto Maven dentro de proy-int con los siguientes comandos:

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

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 = org.especialistajee.proyint
[INFO] Using property: artifactId = jbib-comun
[INFO] Using property: version = 1.0-SNAPSHOT
[INFO] Using property: package = org.especialistajee.jbib
Confirm properties configuration:
groupId: org.especialistajee.proyint
artifactId: jbib-comun
version: 1.0-SNAPSHOT
package: org.especialistajee.jbib
Y: 

También podríamos haber ejecutado el comando sin especificar los parámetros y Maven los hubiera pedido de forma interactiva, 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 (jbib-comun). 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 org/especialistajee/jbib).

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>org.especialistajee.proyint</groupId>
  <artifactId>jbib-comun</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jbib-comun</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>

Debemos ahora modificar el fichero POM para:

  • Indicar que la codificación de los ficheros fuentes del proyecto es UTF-8. Para ello hay que establecer la propiedad project.build.sourceEncoding a UTF-8 justo antes de la especificación de dependencias:
    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
    ...
  • Modificar la versión de la librería junit, para que sea la 4.8.1
  • Incluir las librerías commons-logging (gruoupId: commons-logging y versión: 1.1.1) y log4j (groupId: log4j y versión: 1.2.14)
  • Añadir lo siguiente después de la definición de dependencias, para configurar la versión del compilador Java con la que vamos a trabajar en Eclipse:
    <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>

Lo último que falta es crear el directorio de recursos jbib-comun/src/main/resources que no se crea en el arquetipo:

$ cd jbib-comun/src/main
$ mkdir resources

Importamos en Eclipse

Vamos a importar el proyecto recién creado en Eclipse. Lo primero abrir el espacio de trabajo /home/especialista/proy-int, seleccionar la opción de Eclipse Import... > Maven > Existing Maven Projects y escoger el proyecto que acabamos de crear:

Eclipse importará el proyecto al espacio de trabajo actual y actualizará automáticamente sus dependencias. A partir de este momento ya podemos trabajar en Eclipse.

Actualizar fichero POM en Eclipse
Si una vez importado el proyecto modificamos en Eclipse el fichero pom.xml Eclipse no actualiza automáticamente la configuración. Para hacerlo hay que seleccionar en el menú contextual del proyecto la opción Maven > Update Project Configuration.

Una vez subido al repositorio CVS, la estructura del proyecto es la siguiente:

Estructura de Proyecto
  • src: carpeta fuente que contiene las clases Java comunes tanto para una aplicación web como para una aplicación Swing.
    Destacar el prefijo de todos los paquetes: org.especialistajee.jbib
  • test: carpeta fuente con clases de Java basadas en JUnit para realizar las pruebas de la aplicación.
  • resources: carpeta fuente con los ficheros de recursos que al desplegar la aplicación deben estar en el classpath

Implementación del Modelo de Clases

Objetos de dominio

Los objetos de dominio representan las entidades que van a relacionarse en la aplicación. 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.

/**
* $Author: domingo $
* $Date: 2010/10/15 11:52:04 $
* $Revision: 1.10 $
*/

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 tablas en JDBC y entidades en 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. Tienen 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();
}
CVS Keywords
Es muy útil incrustar las palabra clave de CVS $Author: domingo $, $Date: 2010/10/15 11:52:04 $Revision: 1.10 $. CVS las rellena cuando realizamos un commit del proyecto. De este modo, siempre sabremos la última persona que ha subido una revisión al CVS y la fecha y el número de ésta revisión.

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:

/**
* $Author: domingo $
* $Date: 2010/10/15 11:52:04 $
* $Revision: 1.10 $
*/

package org.especialistajee.jbib.model;

import java.util.Date;
import java.util.List;

/**
 * La clase Libro representa el modelo de un libro de la biblioteca
 * Destacar que la aplicacion no permite dos ejemplares del mismo libro
 */
public class LibroDomain extends DomainObject {

   private static final long serialVersionUID = 1L;
   private String isbn;
   private String titulo;
   private String autor;
   private Integer numPaginas;
   private Date fechaAlta;
   private List<OperacionDomain> historicas;
   private OperacionDomain activa;
   
   public LibroDomain() {}

   public LibroDomain(String isbn) {
      super();
      this.isbn = isbn;
   }
   
   @Override
   public boolean equals(Object obj) {
      if (obj instanceof LibroDomain) {
         LibroDomain libro = (LibroDomain) obj;
         return getIsbn().equals(libro.getIsbn());
      }
      return false;
   }
   
   @Override
   public int hashCode() {
      return getIsbn().hashCode();
   }
   
   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 List<OperacionDomain> getHistoricas() {
      return historicas;
   }

   public void setHistoricas(List<OperacionDomain> operaciones) {
      this.historicas = operaciones;
   }

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

   public OperacionDomain getActiva() {
      return activa;
   }
}

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 la relación de herencia en la clase UsuarioDomain. Definimos esta clase como abstracta y las clases hijas BibliotecarioDomain, ProfesorDomain y AlumnoDomain.

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:

/**
* $Author: domingo $
* $Date: 2010/10/15 11:52:04 $
* $Revision: 1.10 $
*/

package org.especialistajee.jbib.model;

public enum EstadoUsuario {
   ACTIVO, MOROSO
}

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

/**
* $Author: domingo $
* $Date: 2010/10/15 11:52:04 $
* $Revision: 1.10 $
*/

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
 * 
 * @author $Author: domingo $
 * @version $Revision: 1.10 $
 */
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.

Ficheros de recursos

Fichero commons-logging.properties:

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

Fichero 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

Tests

Vamos a definir dos tipos de tests, unos relacionados con la creación y la comparación de objetos 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:

/**
 * $Author: domingo $
 * $Date: 2010/10/15 11:52:04 $
 * $Revision: 1.10 $
 */

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);
   }
}

Tests de la clase UsuarioDomain:

/**
 * $Author: domingo $
 * $Date: 2010/10/15 11:52:04 $
 * $Revision: 1.10 $
 */

package org.especialistajee.jbib.model;

import static org.junit.Assert.*;

import org.junit.Test;

public class UsuarioDomainTest {

   @Test
   public void testEquals() {
      UsuarioDomain bibliotecario1 = new BibliotecarioDomain(
            "Ana", "Murillo", "Pérez");
      UsuarioDomain bibliotecario2 = new BibliotecarioDomain(
            "Ana", "Murillo", "Pérez");
      bibliotecario1.setEmail("ana.murillo@gmail.com");
      bibliotecario1.setEstado(EstadoUsuario.MOROSO);
      bibliotecario1.setLogin("anamurillo");
      bibliotecario1.setPassword("123456");
      assertTrue(bibliotecario1.equals(bibliotecario2));
   }

   @Test
   public void testDistintoTipoNotEquals() {
      UsuarioDomain bibliotecario = new BibliotecarioDomain(
            "Ana", "Murillo", "Pérez");
      UsuarioDomain profesor = new ProfesorDomain("Ana",
            "Murillo", "Pérez");
      assertFalse(profesor.equals(bibliotecario));
   }

   @Test
   public void testTipoUsuarioBibliotecario() {
      UsuarioDomain usuario = new BibliotecarioDomain(
            "Ana", "Murillo", "Pérez");
      assertEquals(TipoUsuario.BIBLIOTECARIO, usuario
            .getTipo());
   }

   @Test
   public void testTipoUsuarioAlumno() {
      UsuarioDomain usuario = new AlumnoDomain("Ana",
            "Murillo", "Pérez");
      assertEquals(TipoUsuario.ALUMNO, usuario.getTipo());
   }

   @Test
   public void testTipoUsuarioProfesor() {
      UsuarioDomain usuario = new ProfesorDomain("Ana",
            "Murillo", "Pérez");
      assertEquals(TipoUsuario.PROFESOR, usuario.getTipo());
   }
}

Entorno de Desarrollo

A lo largo del proyecto, vamos a basarnos en la plataforma Eclipse Galileo + MySQL 5.0 (ya instalados en la máquina virtual). Más adelante, entrarán en juego el contenedor web (Apache Tomcat) y el servidor de aplicaciones (Glassfish).

Dentro de las prácticas comunes a lo largo del proyecto tenemos:

  • una estructura de proyecto limpia y estándar basada en Maven
  • uso de logs para todo tipo de mensajes de debug y error
  • uso de convenciones de código Java

A Entregar

En esta sesión vamos a preparar la base para el resto de sesiones. Por ellos, debemos crear un proyecto Eclipse, al que llamaremos proy-int-comun, el cual contenga:

  1. proyecto Maven con la estructura y nomenclatura definida
  2. 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.
  3. 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.

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

Estructura de Proyecto I Estructura de Proyecto II

El plazo final de entrega será el jueves próximo. Se realizará una sesión on-line de dudas el miércoles de 19:00 a 21:00.