Proyecto de Integración
 

Integración con GlassFish y EJBs

En esta sesión de integración integraremos el stack JSF (formado por los proyectos jbib-comun, jbib-persist-jpa, jbib-negocio-jpa y jbib-web-jsf) en el servidor de aplicaciones GlassFish y añadiremos un módulo EJB.

Configuración del dominio GlassFish

Vamos a crear un dominio nuevo llamado jbib-domain. Créalo sin password en el puerto 9000 con el comando:

./asadmin create-domain --portbase 9000 jbib-domain

Una vez creado añadimos un nuevo servidor a NetBeans utilizando este nuevo dominio. Llámalo por ejemplo Server jbib:

Nuevo servidor

Lánzalo y abre una consola de administración en http://localhost:9048:

Consola de administración

Creación del proyecto Maven EAR

Vamos a crear un EAR en el que incluiremos el proyecto web como un fichero WAR y los demás proyectos como librerías JAR. Al igual que hemos venido haciendo hasta ahora lo crearemos como un proyecto Maven. De esta forma vamos a tener un proyecto portable a distintos IDEs y vamos a poder automatizar su prueba y despliegue con comandos de Maven.

Proyectos existentes

Comenzamos creando un nuevo directorio proy-int-glassfish y copiando ahí los proyectos con los que vamos a trabajar:

  • jbib-comun
  • jbib-persist-jpa
  • jbib-negocio-jpa
  • jbib-web-jsf

Mantenemos los directorios cvs para que sigan conectados con el repositorio CVS.

GlassFish utiliza el proveedor de persistencia JPA TopLink. Para que los proyectos funcionen correctamente en GlassFish tenemos que cambiar este proveedor de persistencia a las librerías de Hibernate incluidas en el proyecto jbib-persist-jpa. Para ello tenemos que asegurarnos que el fichero de configuración de JPA persistence.xml se encuentra en la ruta correcta:

jbib-persist-jpa/src/main/resources/META-INF/persistence.xml

y que contiene como provider la clase org.hibernate.ejb.HibernatePersistence:

<persistence-unit name="proyint" transaction-type="RESOURCE_LOCAL">
   <provider>org.hibernate.ejb.HibernatePersistence</provider>
   <class>org.especialistajee.jbib.entity.AlumnoEntity</class>
   ...
   <properties>
      ...
   </properties>
</persistence-unit>

Nuevo proyecto EAR: jbib-enterprise

Vamos a crear el proyecto EAR jbib-enterprise. Comenzamos creando un proyecto Maven básico con el arquetipo maven-archetype-quickstart y con los siguientes parámetros:

  • groupId: org.especialistajee.proyint
  • artifactId: jbib-enterprise
  • version: 1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart
-DgroupId=org.especialistajee.proyint -DartifactId=jbib-enterprise -Dversion=1.0

Borramos el directorio src y sustituimos el fichero POM por el siguiente.

Fichero pom.xml:

<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-enterprise</artifactId>
  <packaging>ear</packaging>
  <version>1.0</version>
  <name>jbib-enterprise</name>
  <dependencies>
    <dependency>
      <groupId>org.especialistajee.proyint</groupId>
      <artifactId>jbib-comun</artifactId>
      <version>1.0</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.especialistajee.proyint</groupId>
      <artifactId>jbib-persist-jpa</artifactId>
      <version>1.0</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.especialistajee.proyint</groupId>
      <artifactId>jbib-negocio-jpa</artifactId>
      <version>1.0</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.especialistajee.proyint</groupId>
      <artifactId>jbib-web-jsf</artifactId>
      <version>1.0</version>
      <type>war</type>
    </dependency>  
  </dependencies>
  <build>
    <finalName>jbib-enterprise</finalName>
    <defaultGoal>package</defaultGoal>
   <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ear-plugin</artifactId>
        <version>2.4.2</version>
        <configuration>
          <modules>
            <jarModule>
               <groupId>org.especialistajee.proyint</groupId>
               <artifactId>jbib-comun</artifactId>
            </jarModule>
            <jarModule>
               <groupId>org.especialistajee.proyint</groupId>
               <artifactId>jbib-comun</artifactId>
            </jarModule>
            <jarModule>
               <groupId>org.especialistajee.proyint</groupId>
               <artifactId>jbib-persist-jpa</artifactId>
            </jarModule>
            <jarModule>
               <groupId>org.especialistajee.proyint</groupId>
               <artifactId>jbib-negocio-jpa</artifactId>
            </jarModule>
            <webModule>
               <groupId>org.especialistajee.proyint</groupId>
               <artifactId>jbib-web-jsf</artifactId>
            </webModule>
          </modules>
          <defaultLibBundleDir>lib</defaultLibBundleDir>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

El POM define el tipo de empaquetamiento como EAR, declara las dependencias con los otros proyectos Maven y utiliza el plugin maven-ear-plugin para empaquetar todos los componentes en un EAR.

Los módulos incluidos en el EAR se definen en la configuración del plugin, con los elementos jarModule y webModule. El elemento defaultLibBundleDir es muy importante porque empaqueta todas las librerías en el directorio lib del EAR. Si no se hace esto todas las librerías se colocan en la raíz del EAR y no funciona correctamente en GlassFish.

Para crear el fichero EAR basta con llamar al goal de Maven package, que a su vez llama al goal ear:ear del plugin y empaqueta todo el ear en el directorio target/jbib-enterprise.ear.

mvn clean package
Aviso
Asegúrate de que todos los otros proyectos están instalados correctamente en el repositorio Maven local, haciendo un mvn clean install en cada uno de ellos.

Nos aseguramos de que el EAR tiene la estructura correcta:

Desplegando el EAR en GlassFish

Utilizamos la consola de administración de GlassFish para desplegar el EAR:

Probamos que funciona correctamente accediendo a la URL http://localhost:9080/jbib-web-jsf/.

Importando los proyectos en NetBeans

Cambiamos la configuración de NetBeans para que utilice nuestra configuración externa de Maven en /opt/apache-maven-2.2.1 con la opción Tools > Options > Miscellaneous > Maven:

Importamos los proyectos en NetBeans con la opción Open Proyect... NetBeans detecta que se tratan de proyectos Maven y los importa correctamente:

Desplegando el EAR desde NetBeans

Asociamos el proyecto EAR con el servidor glassfish Server jbib modificando la opción Run en sus propiedades.

Probamos a modificar algo de código de alguno de los módulos (por ejemplo, hacer que el getNumPaginas() del LibroDomain siempre devuelva 200 y reconstruir el EAR. Primero debemos hacer Clean and Build en el proyecto que hemos modificado y después en el EAR.

Desplegamos en el servidor con la opción Run y probamos para comprobar el cambio.

Aviso
Hay algún bug entre NetBeans y GlassFish que hace que no funcione el despliegue cuando la aplicación está ya desplegada. Prueba a hacer un undeploy antes de desplegar la nueva versión.

Configuración de la seguridad

Uno de los problemas principales de la aplicación JSF tal y como está es que no se impide el acceso directo a URLs de la aplicación.

Una forma de impedirlo es utilizando filtros de servlets. Debes definir un filtro en la aplicación web que se aplique en las URLs no autorizadas y que compruebe si el usuario está autentificado. Una forma de registrar un usuario autentificado sería que la acción de login de JSF guardara en la sesión del servlet el rol del usuario autentificado.

(Más información: autorización mediante filtros)

Capa de negocio con EJB

Creación de la fuente de datos XA

Vamos a comenzar a preparar la integración de la capa EJB. En concreto, crearemos el EJB OperacionBoEjb que nos permitirá añadir transaccionalidad extendida y seguridad a las distintas operaciones de negocio. También nos permitirá definir algún método remoto al que podremos acceder desde aplicaciones clientes.

Vamos a comenzar por configurar la fuente de datos jdbc/jbib en el dominio jbib-domain. Copia el driver JDBC de MySQL mysql-connector-java-5.0.5.jaren el directorio lib del dominio. Ahora ya podemos lanzar GlassFish y conectarnos a su consola de administración. Es recomendable lanzarlo desde NetBeans, para poder visualizar sus mensajes en el panel de Output.

Crea en GlassFish una fuente de datos de tipo XA conectada a la base de datos biblioteca. Primero creamos el conjunto de conexiones BibliotecaPool:

Completamos los parámetros que nos muestra por defecto, añadiendo los siguientes

  • user: root
  • password: especialista
  • ServerName: localhost
  • url: jdbc:mysql://:3306/biblioteca
  • URL: jdbc:mysql://:3306/biblioteca

Terminamos creando la nueva fuente de datos JDBC jdbc/jbib basada en el conjunto de conexiones que acabamos de crear.

En NetBeans, editamos el fichero persistence.xml del proyecto jbib-persist-jpa para añadir una nueva unidad de persistencia que se conecte a esta nueva fuente de datos utilizando JTA. La llamamos jbib-jta:

<persistence-unit name="jbib-jta" transaction-type="JTA">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <jta-data-source>jdbc/jbib</jta-data-source>
  <class>org.especialistajee.jbib.entity.AlumnoEntity</class>
  <class>org.especialistajee.jbib.entity.BibliotecarioEntity</class>
  <class>org.especialistajee.jbib.entity.LibroEntity</class>
  <class>org.especialistajee.jbib.entity.MultaEntity</class>
  <class>org.especialistajee.jbib.entity.OperacionEntity</class>
  <class>org.especialistajee.jbib.entity.ProfesorEntity</class>
  <class>org.especialistajee.jbib.entity.UsuarioEntity</class>
  <properties>
       <property name="hibernate.hbm2ddl.auto" value="validate" />
       <property name="hibernate.show_sql" value="false" />
  </properties>
</persistence-unit>

Creación del proyecto jbib-negocio-ejb

Creamos en el mismo directorio proy-int-glassfish un nuevo proyecto Maven EJB utilizando el asistente de NetBeans. Escogemos la opción New Project ... > Maven > Maven EJB Module e introducimos los siguientes valores:

  • Project Name: jbib-negocio-ejb
  • Project Location: /home/especialista/proy-int-glassfish
  • Group Id: org.especialistajee.proyint
  • Version: 1.0
  • Package: org.especialistajee.jbib.bean

Editamos el POM para modificar el nombre (lo dejamos como jbib-negocio-ejb), la versión de JUnit (ponemos la 4.8.1) y añadir las dependencias con los proyectos jbib-comun y jbib-persist-jpa.

Modifica el POM de proyecto EAR para incluir también este nuevo proyecto. Añade las dependencias:

<dependencies>
...
<dependency>
   <groupId>org.especialistajee.proyint</groupId>
   <artifactId>jbib-negocio-ejb</artifactId>
   <version>1.0</version>
   <type>ejb</type>
</dependency>
...
</dependencies>
...

Y en la parte del POM de la configuración del plugin maven-ear-plugin añade el módulo con el tipo ejbModule:

<ejbModule>
   <groupId>org.especialistajee.proyint</groupId>
   <artifactId>jbib-negocio-ejb</artifactId>
</ejbModule>

Creación del EJB OperacionBoEjb

Creamos el EJB OperacionBoEjb que implemente la interfaz IOperacionBo de forma local y proporcione además la posibilidad de acceder de forma remota a alguna de estas operaciones.

Codificamos el EJB en el paquete org.especialistajee.jbib.bean. Debemos crear las siguientes clases e interfaces:

  • Interfaz local OperacionBoLocal
  • Interfaz remota OperacionBoRemote
  • Clase de implementación OperacionBo

En la interfaz local extiende la interfaz IOperacionBo definida en el proyecto común. De esta forma obligamos al bean a implementar todos los métodos de la interfaz. En la interfaz remota define únicamente el método:

String realizaReservaRemota(String idUsuario, String idLibro) 
                    throws OperacionException, OperacionCupoCompletoException;

En la implementación del EJB obtenemos el entity manager por inyección de dependencias, utilizando la unidad de persistencia definida anteriormente. También definimos el tipo de gestión de transacción definido por el contenedor.

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class OperacionBoEjb implements OperacionBoRemote, OperacionBoLocal {

    @PersistenceContext(unitName="jbib-jta")
    EntityManager em;
    
    @Resource
    SessionContext context;

    ...

En la implementación del bean completamos automáticamente todos los métodos de la interfaz. Verás que aparecen todos los métodos definidos en IOperacionBo con una implementación vacía. Debemos implementar todos los métodos de la interfaz, copiando el código ya existente en los objetos de negocio del paquete común. Comenzamos, por ejemplo, por el método anulaReserva.

El código es exactamente el mismo que el ya definido en la implementación del objeto de negocio, elminando las llamadas de gestión de la transacción y del entity manager. Y añadiendo el atributo de transacción a REQUIRED y la llamada a setRollbackOnly() del contexto de sesión para marcar la transacción actual como inválida. Ahora no gestionamos nosotros las transacciones ni el entity manager; lo hace el servidor de aplicaciones. Añadimos el atributo de transacción a REQUIRED para asegurarnos de que ese es el comportamiento del EJB. El uso de setRollbackOnly nos asegura que la transacción se va a invalidar incluso en el caso en que la excepción que se lanza fuera capturada.

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void anulaReserva(String idOperacion, String login) throws OperacionException {
        if (idOperacion == null || login == null) {
            throw new IllegalArgumentException(
                    "Se esperaba un idOperacion y un login");
        }
        OperacionEao operacionEao = new OperacionEao();
        operacionEao.setEntityManager(em);
        int idOperacionInt = Integer.parseInt(idOperacion);
        OperacionEntity reserva = operacionEao.find(idOperacionInt);
        if (reserva == null) {
            throw new OperacionException("No existe la operación " + idOperacion);
        }
        if (reserva.getUsuario().getLogin().equals(login)) {
            operacionEao.delete(idOperacionInt);
        } else {
            throw new OperacionException("Este usuario no puede anular la reserva");
        }
    }

Implementamos la operación realizaReserva y realizaReservaRemota que llama a la anterior.

Prueba del EJB con un cliente remoto

Crea en el paquete de test una clase main que realice un acceso remoto al EJB y que realice una reserva:

   ...
   System.out.print("Introduce el id del libro: ");
   BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   String idLibro = in.readLine();
   System.out.print("Introduce el id del usuario: ");
   in = new BufferedReader(new InputStreamReader(System.in));
   String  idUsuario = in.readLine();
   System.out.println("Voy a realizar la reserva");
   operacion.realizaReservaRemta(idUsuario, idLibro);
   System.out.println("Reserva realizada"); ...

Comprueba en la interfaz web que el libro aparece en estado reservado.

Creación del resto de EJBs

Implementar los EJBs LibroBoEjb y UsuarioBoEjb sólo con acceso local.

Factoria de negocio

Nota
Para simplificar, se ha cambiado ligeramente la definición de la factoría con respecto a las sesiones anteriores. En lugar de una clase abstracta en el proyecto común definimos allí la interfaz IFactoriaBos. Y la clase concreta que implementa la interfaz la llamamos FactoriaBos

Crea en el proyecto jbib-negocio-ejb una factoría con el mismo nombre que el proyecto jbib-negocio-jpa: org.especialistajee.jbib.bo.FactoriaBos. Así podremos sustituir el proyecto jbib-negocio-jpa por jbib-negocio-ejb en el EAR y la aplicación web seguirá funcionando correctamente usando EJBs.

Nota
Esta solución no es la más correcta, porque impide utilizar EJBs y objetos de negocio JPA simultáneamente. En el proyecto enterprise propondremos una solución alternativa.
public class FactoriaBos implements IFactoriaBos {
   
   protected static FactoriaBos me;
   protected static OperacionBoLocal operacionBo;
   protected static LibroBoLocal libroBo;
   protected static UsuarioBoLocal usuarioBo;
  
   protected FactoriaBos() {
      try {
         InitialContext ic = new InitialContext();
         operacionBo = (OperacionBoLocal) ic.lookup("java:global/jbib-enterprise/jbib-negocio-ejb-1.0/"+
                "OperacionBoEjb!org.especialistajee.jbib.bean.OperacionBoLocal");
         libroBo = (LibroBoLocal) ic.lookup("java:global/jbib-enterprise/jbib-negocio-ejb-1.0/"+
                "LibroBoEjb!org.especialistajee.jbib.bean.LibroBoLocal");
         usuarioBo = (UsuarioBoLocal)
      ic.lookup("java:global/jbib-enterprise/jbib-negocio-ejb-1.0/"+
                "UsuarioBoEjb!org.especialistajee.jbib.bean.UsuarioBoLocal");
      } catch (NamingException ex) {
         throw new RuntimeException();
      }
   }

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

   public IOperacionBo getOperacionBo() {
      return operacionBo;
   }

   public ILibroBo getLibroBo() {
      return libroBo;
   }

   public IUsuarioBo getUsuarioBo() {
      return usuarioBo;
   }


}

Esta factoría devuelve un acceso local a cada objeto de negocio implementado como un EJB. Para ello busca en el contexto JNDI local el nombre JNDI local de cada uno de ellos. Ese nombre JNDI lo podemos encontrar en la salida estándar del serivdor de aplicaciones.

Aplicación EAR

Lo único que falta para poder empaquetar la nueva aplicación es modificar el POM en la aplicación EAR para eliminar el módulo jbib-negocio-jpa que sustituimos por jbib-negocio-ejb. Para ello borramos las líneas en las que se hace referencia a este módulo, tanto en las dependencias como en la parte del plugin maven-ear-plugin. Borramos también el módulo porque no lo vamos a necesitar.

Una vez hecho esto empaquetamos todos los módulos y la aplicación (desde el terminal haciendo un mvn clean pacakge o desde Netbeans con Clean and Build) y la desplegamos en GlassFish utilizando la consola de administración.

Resumen

Proyecto a entregar

Resumen de las tareas a realizar en la sesión:

  • Crear el dominio jbib-domain y la fuente de datos.
  • Proyecto EAR jbib-enterprise con los módulos: jbib-comun, jbib-persist-jpa, jbib-negocio-ejb y jbib-web-jsf.
  • Configuración de la seguridad en la aplicación web mediante un filtro servlet.
  • Creación del módulo jbib-negocio-ejb con los EJB: OperacionBoEjb, LibroBoEjb y UsuarioBoEjb y con la factoría FactoriaBos.
  • Programa Java en NetBeans (no hace falta que sea un proyecto Maven) que prueba el acceso remoto a la función de reservar un libro.
  • Creación de las referencias a las interfaces locales de los EJB en el módulo web.