EJB3 : Cliente standalone


Introducción

En el artículo Integración de EJB3 y JSF vimos cómo podíamos consumir componentes EJB3 desde un módulo web que residía en el mismo servidor que el módulo EJB. En este artículo voy a intentar explicar cómo acceder a los componentes EJB3 desde un cliente Java standalone que se ejecuta en una JVM distinta del servidor.
El servidor de aplicaciones que he utilizado para desplegar el módulo EJB ha sido la versión 4.0.4.GA de JBoss, y el IDE utilizado para el desarrollo del proyecto ha sido la versión 5.5 Beta de NetBeans.
El primer paso que vamos a hacer es crear el módulo EJB. Vamos al menú New Project y seleccionamos EJB Module que se encuentra dentro de la categoría Enterprise. Configuramos el asistente del siguiente modo :

Creando la Entidad

A continuación tenemos que agregar una entidad al proyecto, la entidad Cliente, a la vez que configuramos el EntityManager para indicarle cómo acceder a nuestra base de datos. En JBoss tenemos configurado por defecto un DataSource al que podemos acceder a través de JNDI. Este DataSource hace referencia una base de datos HSQL, y para acceder a ella a través de JNDI se hace a través del nombre java:/DefaultDS.
El asistente para la creación de la entidad Cliente quedaría como sigue :
Y el asistente para la configuración del EntityManager nos quedaría así :
Una vez finalizados los asistentes modificaremos la entidad para agregarle los métodos que ésta necesite, tras lo cual la entidad nos quedará del siguiente modo :
package org.jlm.domain;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Cliente implements Serializable {

    private Integer id;
    private String nombre;
    private String apellidos;


    /** Creates a new instance of Cliente */
    public Cliente() {
    }


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "CUSTOMER_ID")
    public Integer getId() {
        return id;
    }

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

    @Column(name = "CUSTOMER_NAME" )
    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    @Column(name = "CUSTOMER_SURNAME")
    public String getApellidos() {
        return apellidos;
    }

    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }

    public int hashCode() {
        int hash = 0;
        hash += (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Cliente)) {
            return false;
        }
        Cliente other = (Cliente)object;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false;
        return true;
    }

    public String toString() {
        return "org.jlm.domain.Cliente[id=" + id + "]";
    }

}
Y el fichero persistence.xml nos quedará de este otro modo :



org.hibernate.ejb.HibernatePersistence
java:/DefaultDS





Creando el Stateless Session Bean (SLSB)

El siguiente paso que tenemos que hacer es crear un SLSB que se encargue de hacer llamadas al EntityManager para comunicarse con la base de datos. Este SLSB tan sólo va a disponer de dos métodos, uno para grabar clientes, y otro para mostrarlos.
El asistente para la creación del SLSB tendremos que configurarlo como sigue :
Es importante tener en cuenta que la interfaz que tiene que implementar el SLSB tiene que ser Remota, ya que los métodos van a ser invocados desde una JVM distinta a la del servidor. Este asistente nos va a generar dos ficheros. Por una parte nos va a generar la interfaz remota, a la cual le agregaremos dos métodos :
@Remote
public interface ServicioClientesRemote {
    public void grabarCliente(Cliente cliente);
    public List obtenerClientes();
}
Y por otra parte, el asistente nos generará el bean propiamente dicho que lo implementaremos del siguiente modo :
@Stateless
public class ServicioClientesBean implements ServicioClientesRemote {

    @PersistenceContext(unitName="GestionClientesPU")
    private EntityManager em;

    public void grabarCliente(Cliente cliente) {
        em.persist(cliente);
    }

    public List obtenerClientes() {
        Query query = em.createQuery( "SELECT c FROM Cliente AS c" );
        return query.getResultList();
    }
Hemos configurado el EntityManager para que utilice la unidad de persistencia que hemos configurado previamente. En nuestro caso no sería necesario indicarle la propiedad unitName, ya que en el proyecto tan sólo tenemos configurada una unidad de persistencia. Si tuviéramos más de una unidad de persistencia definidas en el fichero persistence.xml entonces sí sería necesario establecer la propiedad unitName.

Definiendo la aplicación cliente

Ahora tenemos que crear un nuevo proyecto que haga uso del módulo EJB que hemos definido previamente. Vamos al menú y creamos un nuevo proyecto basado en el asistente Java Application que está en la categoría General :
A continuación tendremos que decirle al cliente dónde está el módulo EJB que hemos creado anteriormente para que sea capaz de utilizar la entidad Cliente, y la interfaz remota que están dentro del módulo EJB. Para ello vamos a hacer clic con el botón derecho sobre el proyecto ProgramaCliente :
Seleccionamos Properties, Libraries y agregamos el proyecto en el que habíamos creado el EJB3 :
A continuación implementamos la clase de la siguiente forma :
public class Main {

    public static void main(String[] args) {

        try {

            Context jndiContext = getInitialContext();
            ServicioClientesRemote servicioClientes = (ServicioClientesRemote)jndiContext.lookup("ServicioClientesBean/remote");

            //Grabamos el Cliente
            Cliente cliente = new Cliente();
            cliente.setNombre("Jose Luis");
            cliente.setApellidos("Monteagudo Montañes");
            servicioClientes.grabarCliente(cliente);

            //Mostramos los Clientes
            List clientes = servicioClientes.obtenerClientes();
            for(Cliente c: clientes) {
                System.out.println(c);
            }

        }
        catch (javax.naming.NamingException ne) {
            ne.printStackTrace();
        }

    }

    private static Context getInitialContext() throws javax.naming.NamingException {
        return new javax.naming.InitialContext();
    }

}
El primer paso es obtener el contexto para acceder al servicio JNDI del servidor. Como vemos, no hemos asignado ninguna propiedad a la hora de obtener una conexión a JNDI. Cuando obtenemos un contexto de este modo, el JDK va a buscar el fichero jndi.properties en classpath. En este fichero vamos a definir las propiedades necesarias para poder tener acceso a JNDI, y al tenerlo definido fuera del código fuente no vamos a tener que recompilar si el módulo EJB está desplegado en un servidor u otro. Más adelante crearemos este fichero.
Una vez tenemos el contexto vamos a poder realizar búsquedas en JNDI, y así obtendremos la interfaz remota de nuestro SLSB, la cuál podremos utilizar del mismo modo que utilizamos cualquier interfaz convencional.

Escribiendo jndi.properties

Este fichero es un fichero de propiedades en el que definimos cómo el cliente puede acceder al servicio JNDI de un servidor. Lo vamos a agregar a partir un asistente al cual accedemos a través de Properties File que está dentro de la categoría Other. Guardaremos el fichero en el directorio src y definiremos las siguientes propiedades :
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=127.0.0.1:1099
Lo único que tenemos que tener en cuenta es que este fichero esté en classpath, de esta forma será encontrado cuando creamos un nuevo InitialContext.

Desplegando la aplicación

Aunque la aplicación la podemos desplegar a través del IDE, yo la voy a desplegar manualmente para ver lo sencillo que resulta esta operación en el servidor JBoss. Lo primero que tenemos que hacer es obtener el JAR correspondiente al módulo EJB. Para ello vamos a hacer un build del proyecto EJB y si todo ha funcionado correctamente en el directorio dist de nuestro proyecto encontraremos el fichero GestionClientes.jar. Para desplegarlo en un servidor JBoss lo único que tenemos que hacer es copiar dicho fichero en JBOSS_HOME\server\default\deploy y comprobamos el log del servidor para comprobar que el despliegue se ha producido sin problemas.
A continuación tenemos que hacer el build de la aplicación cliente. Antes de realizarlo y para que todo funcione correctamente y nos compile sin problemas vamos a tener que añadir unas librerías al proyecto ProgramaCliente, librerías que se encuentran en el directorio JBOSS_HOME/client. Las librerías necesarias son las siguientes :
ejb3-persistence.jar
GestionClientes.jar
jbossall-client.jar
jboss-aop-jdk50-client.jar
jboss-aspect-jdk50-client.jar
jboss-ejb3-client.jar
Una vez agregadas estas librerías a nuestro proyecto ya podremos hacer el build de ProgramaCliente. Una vez hecho el build encontraremos en el directorio dist los fichero que necesitamos para ejecutar el programa. Podemos copiar este directorio a cualquier ordenador y ( configurando correctamente la propiedad java.naming.provider.url del fichero jndi.properties ) podremos ejecutar nuestra aplicación cliente de la siguiente forma :
java -jar ProgramaCliente.jar
Si queremos consultar la información en la base de datos podemos hacerlo del siguiente modo. Primero accedemos a la url http://localhost:8080/. Desde aquí enlazamos a la JMX Console. En la sección jboss accedemos a database=localDB,service=Hypersonic. Hacemos clic sobre el botón Invoke que hay bajo el comando void startDatabaseManager() y se nos abrirá el HSQL Database Manager. Desde aquí podemos ejecutar la SQL "SELECT * FROM CLIENTE" para ver los clientes que se han agregado a la base de datos.