Prácticas de Desarrollo de Aplicaciones Distribuidas

Anuncio
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Prácticas de Desarrollo de Aplicaciones Distribuidas
Enterprise JavaBeans
Objetivos.
Introducción. .........................................................................................................................2
Análisis y Diseño. .................................................................................................................2
Instalación del servidor de aplicaciones JRun. .....................................................................7
Configuración de JRun. ........................................................................................................8
Instalación de una Fuente de Datos. ...................................................................................11
Estructura del Servidor y Proyecto de Programación. ........................................................12
Modelos de Persistencia EJB. .............................................................................................14
Perspectiva distribuida de un objeto de negocio.................................................................15
Gestor del Objeto de Negocio (Home). ..............................................................................15
Persistencia BMP............................................................................................................17
Pruebas. ..........................................................................................................................21
Persistencia CMP 1.x......................................................................................................36
Persistencia CMP 2.x......................................................................................................39
Interfaces Locales...........................................................................................................40
Objetos de Negocio del Caso de Uso. ............................................................................42
Objecto de Negocio Producto. .......................................................................................42
Objecto de Negocio Línea de Pedido. ............................................................................45
Objeto de Negocio Pedido..............................................................................................50
Controlador del Caso de Uso..........................................................................................55
Transacciones. ................................................................................................................65
Acceso al modelo desde la capa de presentación. ..........................................................66
Empaquetado de la aplicación enterprise.......................................................................70
Aplicación Cliente de un servidor de aplicaciones J2EE. ..............................................70
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
Herramientas y documentación.
•
•
JBoss 4.0.x (http://www.jboss.com/)
Especificación Enterprise JavaBeans 2.1 (http://java.sun.com/products/ejb/)
Nota: todo este software puede ser descargado de la página de la asignatura
http://dis.um.es/~jbermudez/dad
1
Desarrollo de Aplicaciones Distribuidas
1.
Curso 2005/2006
Introducción.
El objetivo de esta práctica es el estudio de la tecnología Enterprise JavaBeans para la
implantación de sistemas de información empresariales (enterprise). Para ello partiremos del
caso de uso sencillo y típico de la elaboración de un pedido. Estudiaremos un proceso
sistemático para hacer corresponder un modelo expresado en UML obtenido a partir de la fase
de análisis y diseño con los conceptos propuestos por la especificación EJB. Finalizaremos la
práctica estudiando los patrones utilizados en la capa de presentación para acceder a la capa de
negocio con independientemente de la tecnología utilizada para desarrollar esta capa (RMI,
CORBA, DAO, EJB, etc.).
Requisitos.
“Debemos desarrollar una aplicación empresarial que permita a cualquier cliente de la
organización realizar pedidos de nuestros productos”
Llamaremos a este caso de uso “Solicitar de Pedido” para el cual identificamos las siguientes
acciones:
- Consultar todos los productos del sistema.
- Añadir un producto al pedido indicando la cantidad.
- Editar la información del pedido durante su elaboración, permitiendo:
o Modificar la cantidad de un cierto producto.
o Eliminar un producto que formara parte del pedido.
- Confirmar el pedido.
2.
Análisis y Diseño.
A partir del análisis de requisitos obtenemos el siguiente diagrama de secuencia del sistema
para el caso de uso:
: Sistema
: Cliente
getProductos()
addProducto(producto, cantidad)
removeProducto(producto)
updateProductoCantidad(producto, cantidad)
getInfoPedido( )
finalizarPedido( )
2
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Identificamos los objetos de negocio del caso de uso en el siguiente diagrama de clases del
análisis:
Producto
id : String
nombre : String
precio : int
LineaPedido
+producto
cantidad : int
* / subTotal : int
1
*
+lineasPedido
Cliente
nombre : String
1
nif : String
correo : String
+cliente
usuario : String
clave : String
*
1
Pedido
/ total
A continuación hacemos un análisis de los eventos del sistema para identificar las
responsabilidades de cada operación:
Operación getProductos
Nombre: getProductos(): Collection
Responsabilidades: obtiene todos los productos del sistema
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
Postcondiciones:
Operación addProducto
Nombre: addProducto(producto: Producto, cantidad: int)
Responsabilidades: añade una línea de pedido para el producto. Si ya existía una línea de
pedido para ese producto, añade la cantidad.
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
- El producto debe existir en el sistema
Postcondiciones:
- Objeto LineaPedido creado (lp)
- Asociación Pedido(pedidoActual) – LineaPedido (lp) creada
- lp.cantidad = cantidad
- Asociación LineaPedido (lp) – Producto(producto) creada
3
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Operación removeProducto
Nombre: removeProducto(producto: Producto)
Responsabilidades: elimina la línea de pedido asociada al producto.
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
- El producto debe existir en el sistema
Postcondiciones:
- Asociación Pedido(pedidoActual) – LineaPedido (lp) eliminada
- Asociación LineaPedido (lp) – Producto(producto) eliminada
- Objeto LineaPedido(lp) eliminado.
Operación updateProductoCantidad
Nombre: updateProductoCantidad(producto: Producto, cantidad: int)
Responsabilidades: actualiza la cantidad de un producto en su línea de pedido.
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
- El producto debe existir en el sistema
Postcondiciones:
- LineaPedido.cantidad = cantidad
Operación getInfoPedido
Nombre: getInfoPedido(): Pedido
Responsabilidades: devuelve la información del pedido
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
Postcondiciones:
Operación finalizarPedido
Nombre: finalizarPedido()
Responsabilidades: confirma el pedido actual.
Se vuelve a crear un nuevo “pedido actual”.
Tipo: GestorPedido
Caso de uso: Solicitar Pedido
Excepciones:
Precondiciones:
Postcondiciones:
- Pedido.finalizado = true
- Objeto Pedido creado (pedidoActual)
4
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Seguidamente obtenemos las colaboraciones asociadas a esas operaciones. Sólo se muestran
dos de ellas:
Colaboración addProductoCantidad
: Cliente
pedidoActual :
Pedido
: GestorPedido
lp :
LineaPedido
lineasPedido :
Collection
addProductoCantidad(producto, cantidad)
addLineaPedido(producto, cantidad)
create(producto, cantidad)
add(lp)
Colaboración updateProductoCantidad
: Cliente
pedidoActual :
Pedido
: GestorPedido
updateProductoCantidad(producto, cantidad)
updateLineaPedido(producto, cantidad)
Itera sobre "lineasPedido"
hasta localizar la
asociada a "producto"
setCantidad(cantidad)
5
lp :
LineaPedido
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Finalmente obtenemos el siguiente diagrama de clases del diseño:
Producto
id : String
nombre : String
precio : int
getId() : String
setId(id : String)
getNombre() : String
setNombre(nombre : String)
getPrecio() : int
setPrecio(cantidad : int)
LineaPedido
cantidad : int
/ subTotal : int
+producto
1
*
getProducto() : Producto
getCantidad() : int
setCantidad(cantidad : int)
getSubTotal() : String
*
+lineasPedido
Cliente
nombre : String
nif : String
correo : String
usuario : String
clave : String
1
Pedido
/ total
1
getNombre() : String
setNombre(nombre : String)
+cliente
getNif() : String
setNif(nif : String)
getCorreo() : String
setCorreo(correo : String)
getUsuario() : String
getClave() : String
setClave(correo : String)
*
getTotal() : int
getFinalizado() : boolean
s etFinalizado(finalizado : boolean)
getCliente() : Cliente
addLineaPedido(producto : Producto, cantidad : int)
removeLineaPedido(producto : Producto)
updateLineaPedido(producto : Producto, cantidad : int)
getLineasPedido() : Collection
+pedidoActual
GestorPedido
getProductos() : Collection
addProductoCantidad(producto : Producto, cantidad : int)
removeProducto(producto : Producto)
updateProductoCantidad(producto : Producto, cantidad : int)
getInfoPedido() : Pedido
finalizarPedido()
En los siguientes apartados estudiaremos como implantar los objetos de negocio
controlador de caso de uso identificados durante el análisis y diseño.
6
y el
Desarrollo de Aplicaciones Distribuidas
3.
Curso 2005/2006
Instalación del servidor de aplicaciones JBoss.
El servidor de aplicaciones JBoss 4.x.x es una distribución totalmente funcional de un
contenedor de aplicaciones web (contenedor de servlets) y un contenedor de componentes EJB.
Los desarrolladores podrán diseñar un sistema de información tanta en la capa de negocio como
en la capa de diseño.
Sera necesario descargar el servidor de aplicaciones Jboss de la web de la asignatura y
descomprimir. La versión JBoss 4.x.x funciona con cualquier distribución de Java, a diferencia
de versiones anteriores. Si se utilizara una versión de Jbos 3.x.x se debe tener en cuenta que solo
funciona correctamente con versiones Java anteriores a la 1.5.
Una vez descomprimido el servido Jboss podremos arrancarlo accediendo al directorio /bin.
Encontraremos un fichero run.bat listo para ejecutar. Para detener el servidor de aplicaciones es
necesario ejecutar el fichero shutdown con la opción -- (ej: ‘shutdown –‘).
La distribución de JBoss viene preconfigurada con 3 tipos de servidores: all , default, minimal.
Los podremos encontrar en la carpeta /server de la distribución de JBoss. Cada uno deestos
servidor de aplicaciones viene predefinido con un conjunto de servicios. El servidor ‘all’
arranca con todos los servicios de Jboo. Nosotros en la práctica utilizaremos el servidor
‘default’, que arranca los servicios de nombrado y los servicios que permiten los despliegues
automáticos de las aplicaciones web (Tomcat Deployer). Para indicar a JBoss que servidor
debemos utilizar utilizaremos el parámetro ‘-c tipo_servidor’ en la llamada a run.bat. Además,
podemos crear nuevos servidores. Tan solo debemos copiar la carpeta con la configuración del
tipo de servidor que mas nos interesa con otro nombre en la carpeta /server y realizar las
modificaciones oportunas.
La estructura de directorios de la distribución de JBoss es la siguiente:
/bin
/lib
/client
/docs
/server
ficheros ejecutables JBoss
librerías JAR para JBoss
librerías JAR para los clientes.
API de JBoss
servidores predefinidos donde desplegaremos nuestras aplicaciones.
Dentro de cada carpeta de servidor (dentro de /server) encontraremos los datos de configuración
del servidor. La carpeta más importante es /deploy donde desplegaremos los componentes de
nuestra aplicación (componentes EJB y componentes web). En esta carpeta pondremos nuestra
aplicación empaquetada (o los componentes empaquetados) en un fichero ear. Los componentes
podrán ser desplegados de forma independiente mediante ficheros jar y war. Podremos también
descomprimir la aplicación web en una carpeta con la nomenclatura ‘nombre_carpeta.war’.
Dentro de la carpeta de servidores existen otras carpetas:
/conf ficheros de configuración del servidor
/data motor de base de datos de JBoss: Hypersonic
/lib
librerías JAR para la ejecución del servidor
/log
mensajes del servidor JBoss
/work vistas jsp compiladas para el contenedor de servlets.
7
Desarrollo de Aplicaciones Distribuidas
4.
Curso 2005/2006
Servicios de JBoss.
JBoss ofrece una serie de servicios de consulta y configuración a través de la consola jmx. Para
cceder a ella tan solo debemos arrancar JBoss y acceder a la URL http://localhost:8080/jmxconsole. Se nos mostrará la siguiente pantalla:
De entre los distintos servicios que ofrece JBoss destacaremos el servicicio database
(Hypersonic) que nos permitirá explorar y gestionar la base de datos que incorpora JBoss.
8
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Dicho servicio ofrece una lista de operaciones que podemos ejecutar. La más interesante es
startDataBaseManager que nos abrirá un explorador de la base de datos:
9
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Además destacaremos el servicio JNDIView que nos permitirá explorar el contexto JNDI.
De entre las distintas operaciones, disponemos de la operación list que nos muestra todos los
objetos registrados en el contexto que ofrece JBoss.
10
Desarrollo de Aplicaciones Distribuidas
5.
Curso 2005/2006
Instalación de una Fuente de Datos.
Habitualmente una aplicación enterprise utiliza una base de datos relacional, ya sea para ser
utilizada por el programador o para que sea utilizada por el gestor de persistencia transparente
de EJB (CMP 2.x). Por lo tanto, deberemos registrar al menos una fuente de datos en cada
servidor de aplicaciones.
Para instalar una fuente de datos debemos:
•
•
Primero, copiar los drivers JDBC correspondientes en el directorio /lib del servidor
(fichero jar).
Después crearemos un fichero xml con el formato xxx-ds.xml. Este fichero será
desplegado en la carpeta /deploy y dispone de las etiquetas:
<jndi-name>: Obligatorio. Indica el nombre con el que se registrará en JNDI.
<connection-url>: Obligatorio. URL de conexión JDBC. Para odbc sería
jdbc:odbc:DSN
<user-name>: Opcional. Usario de base de datos.
<password>: Opcional. Clave de base de datos.
Para una conexión JDBC-ODBC utilizaremos un fichero de descripción llamado odbc-ds.xml
con el siguiente contenido:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>DataSourceDAD</jndi-name>
<connection-url>jdbc:odbc:dad</connection-url>
<driver-class>sun.jdbc.odbc.JdbcOdbcDriver</driver-class>
<user-name></user-name>
<password></password>
</local-tx-datasource>
</datasources>
11
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Desarrollo y estructura del Proyecto de Programación.
Para el desarrollo de la aplicación se aconseja utilizar Eclipse con el plugin JBossIDE diseñado
por los creadores de JBoss. Actualmente la última versión del plugin JBossIDE solo funciona de
forma totalmente completa con la versión 3.0 de Eclipse, aunque desde la página web de JBoss
aseguran que es compatible con la versión 3.1M4 de Eclipse.
Definiremos un proyecto J2EE 1.4 que está disponible gracias al plugin de JBossIDE.
Daremos un nombre al proyecto:
12
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Y crearemos una carpeta src para los fuentes y otra bin para los archivos compilados:
Para el desarrollo de los componentes de la capa de negocio (EJB) definiremos 2 paquetes en la
carpeta src: ejb y interfaces. En el paquete ejb colocaremos las clases de implementación de los
beans y en interfaces colocaremos las interfaces remote, home, local y localHome. Además
crearemos dentro de src una carpeta META-INF donde ubicaremos el fichero de despliegue
ejb-jar.xml. Crearemos tambien 1 paquete dao para meter los archivos del patrón dao y el pool
de conexiones.
Para el desarrollo de la aplicación web crearemos una carpeta de tipo source llamada srcWeb:
13
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Dentro de esta carpeta crearemos una carpeta WEB-INF donde ubicaremos el fichero web.xml
de nuestra aplicación web.
El plugin JBossIDE dispone de una acción sobre el proyecto denominada Run Packaging que
permite empaquetar nuestra aplicación en los correpondientes ficheros jar,war o ear de forma
declarativa a través de las propiedades del proyecto (Packaging Configurations):
Por último, el plugin de JBossIDE para Eclipse tiene instalada la herramienta XdocLet que nos
permite la creación de EJB solo desde la clase de implementación del bean.
6. Modelos de Persistencia EJB.
La especificación EJB 2.0 define 3 modelos de persistencia que nos permite implantar los
objetos de negocio cumpliendo cualquier requisito de persistencia e integración que tengamos.
Los modelos de persistencia son los siguientes:
- Bean Managed Persistence (BMP): el programador del objeto de negocio ha de seguir
ciertas pautas de implementación que permitirán al contenedor EJB gestionar la
activación/desactivación de las instancias, la persistencia y el comportamiento
transaccional del objeto. Este modelo es adecuado cuando hemos de dotar de
persistencia a los objetos de negocio en un almacenamiento no soportado por el servidor
de aplicaciones EJB, como por ejemplo XML o LDAP.
- Container Managed Persistence versión 1.x (CMP 1.x): en este modelo de persistencia
se extrae del código todo el manejo de la persistencia relacional y se traslada esta
funcionalidad a un fichero de configuración. Con este modelo ganamos en claridad en la
programación del objeto de negocio, ya que todos los aspectos relacionados con la
persistencia residen en un fichero externo, que permite ser modificado sin necesidad de
recompilar el código. El modo en el que se define todo la información relativa a la
persistencia en este modelo suele ser propietario del servidor de aplicaciones. La
14
Desarrollo de Aplicaciones Distribuidas
-
Curso 2005/2006
ventaja de este modelo la encontramos en el hecho de que podemos integrar el nuevo
sistema de información con una base de datos relacional existente en la organización.
Container Managed Persistence versión 2.x (CMP 2.x): con este modelo conseguimos
una persistencia transparente al programador. Se delega en el servidor de aplicaciones
todas las decisiones relativas a la persistencia: creación de tablas, relaciones, consultas,
etc. Aparte, también podemos definir relaciones entre objetos de negocio manteniendo
una semántica cercana a la recogida en los modelos de análisis y diseño en UML.
A lo largo de las prácticas de la asignatura hemos trabajado con el objeto de negocio Cliente.
Utilizaremos este mismo objeto de negocio para ilustrar los 3 modelos de persistencia.
7.
Perspectiva distribuida de un objeto de negocio.
Un componente EJB es un objeto distribuido RMI/IIOP, es decir, un objeto distribuido que
puede ser accesible desde RMI y CORBA. Como tal deberá tener una interface remota para
acceder al mismo (veremos más adelante que podría también tener una interface local). Por
tanto, definimos la interface remota del objeto de negocio Cliente del siguiente modo:
// Objeto de Negocio "Cliente"
package interfaces;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface ClienteEJB extends EJBObject {
public String getNombre() throws RemoteException;
public void setNombre(String nombre) throws RemoteException;
public String getNif() throws RemoteException;
public void setNif(String nif) throws RemoteException;
public String getCorreo() throws RemoteException;
public void setCorreo(String correo) throws RemoteException;
public String getUsuario() throws RemoteException;
public void setUsuario(String usuario) throws RemoteException;
public String getClave() throws RemoteException;
public void setClave(String clave) throws RemoteException;
}
Lo más destacado de la definición de esta interface remota es el hecho de que hereda de
EJBObject en lugar de Remote. El tipo EJBObject es una especialización del tipo marca Remote
que añade las características básicas de todo objeto EJB.
8.
Gestor del Objeto de Negocio (Home).
Todo objeto de negocio representa una entidad identificable y persistente en un sistema de
información. Esto significa que podemos acceder a él utilizando un identificador o clave
primaria, y que las instancias sobreviven a la ejecución de la aplicación. De este modo,
podremos obtener una referencia a ellas en cualquier momento.
Estas características de los objetos de negocio hacen necesario la existencia de ciertos objetos
gestores de los objetos de negocio que actúan como factoría y permiten recuperar los objetos de
negocio. En la terminología EJB estos objetos se denominan Home.
15
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Tal como ocurre con los objetos de negocio, éstos también han de ser distribuidos. A
continuación se muestra la definición del home del objeto de negocio Cliente:
package interfaces;
import java.rmi.RemoteException;
import javax.ejb.*;
import java.util.Collection;
public interface ClienteEJBHome extends EJBHome {
// Métodos de creación
ClienteEJB create(String nombre, String nif, String correo, String usuario,
String clave) throws RemoteException, CreateException;
// Métodos de Localización
// Localización por "clave primaria"
public ClienteEJB findByPrimaryKey (String usuario)
throws RemoteException, FinderException;
// Localizar todos los Clientes
public Collection findAll()
throws RemoteException, FinderException;
}
Como interface remota RMI lo más destacado es que hereda de EJBHome: el supertipo de los
objetos distribuidos Home.
La interface contiene 2 grupos de métodos: los métodos create y los métodos find. Los
métodos create representan los “constructores” del objeto de negocio. Podemos definir todos
los que creamos necesarios. En este ejemplo hemos introducido un método con la información
de todos los campos de objeto de negocio.
Los métodos find se utilizan para recuperar una o varias instancias del objeto de negocio ya
existentes. La especificación EJB obliga a que introduzcamos siempre el método
findByPrimaryKey para obtener un objeto por clave primaria. Este método tendrá como
argumentos un objeto que permita identificar unívocamente a cada objeto. En nuestro ejemplo,
el campo usuario puede ser utilizado como clave primaria. Aparte de este método de
recuperación podemos incluir todos aquellos que creamos necesarios, como por ejemplo,
findAll. En el caso de que el método de localización devuelva varias instancias el tipo de
retorno ha de ser java.util.Collection.
Tanto las operaciones de creación como de localización pueden encontrar errores durante su
ejecución: clave primaria repetida durante la creación, parámetros incorrectos, error en el
almacenamiento, etc. Para notificar estos tipos de errores se definen un par de excepciones
genéricas llamadas CreateExcepcion, para los métodos de creación, y FinderException utilizada
en los métodos de localización.
16
Desarrollo de Aplicaciones Distribuidas
9.
Curso 2005/2006
Persistencia BMP.
Un componente EJB es un objeto distribuido que necesita una clase que defina su
implementación. Hasta ahora hemos visto que para cada objeto de negocio definimos dos
interfaces RMI: la del objeto remoto y la del objeto home. Para facilitar la tarea del programador
la especificación EJB propone que las interfaces remotas sean implementadas en una sola clase,
llama clase bean. Esta clase implementación no sigue las mismas pautas que se vieron en
RMI, es decir, no implementa la interface remota de los tipos, aunque contiene la definición de
todas las operaciones.
Técnicamente esto es debido a que los objetos de la clase bean no van a ser objetos remotos. El
contenedor EJB se encargará de implementar las interfaces remotas de manera que deleguen
convenientemente sus operaciones en los objetos de la clase bean. De este modo el contenedor
EJB introduce un proxy que le permite mediar en cada operación remota controlando así la
persistencia, activación/desactivación, transaccionalidad y seguridad de cada llamada.
El código de la clase bean tiene 3 partes:
- Los atributos y la implementación de las operaciones de la interface remota (Objeto de
Negocio).
- Los atributos de persistencia y las operaciones de la interface home.
- Los atributos y los métodos de retrollamada de la especificación EJB:
activación/desactivación, carga/almacenamiento.
A continuación se muestra el código de la clase bean para una persistencia BMP del objeto de
negocio Cliente. El código utiliza para gestionar la persistencia la estrategia DAO.
package ejb;
import dao.*;
import javax.ejb.*;
import java.util.*;
public class ClienteBeanBMP implements EntityBean
{
// Parte I: Implementación de la interface remota
// Atributos para almacenar las propiedades del componente
private String nombre;
private String nif;
private String correo;
private String usuario;
private String clave;
// Implementación de los métodos de la interface remota
public String getNombre() { return nombre; }
public void setNombre(String nombre) { this.nombre = nombre; }
public String getNif() { return nif; }
public void setNif(String nif) { this.nif = nif; }
public String getCorreo() { return correo; }
public void setCorreo(String correo) { this.correo = correo; }
public String getUsuario() { return usuario = usuario; }
public void setUsuario(String usuario) { this.usuario = usuario;}
public String getClave() { return clave; }
public void setClave(String clave) { this.clave = clave; }
//Parte II: implementación interface Home
//Atributos para almacenar la persistencia
private ClienteDAO clienteDAO;
//Constructor de inicialización
public ClienteBeanBMP () {
//Obtiene la factoría de DAOs
17
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
try {
clienteDAO =
DAOFactoria.getDAOFactoria(DAOFactoria.ACCESS).getClienteDAO();
} catch (DAOException e) {
new EJBException(e.getMessage());
}
}
//Métodos de creación
public String ejbCreate(String nombre, String nif, String correo,
String usuario, String clave) throws CreateException{
//Actualiza la instancia
this.nombre = nombre; this.nif = nif; this.correo = correo;
this.usuario = usuario; this.clave = clave;
//Almacena la instancia en la base de datos
try {
clienteDAO.create(nombre, nif, correo, usuario, clave);
} catch (DAOException e) {
throw new CreateException(e.getMessage());
}
//Devuelve la clave primaria
return usuario;
}
public void ejbPostCreate(String nombre, String nif, String correo,
String usuario, String clave) { }
//Métodos de localización
//Localización por clave primaria
public String ejbFindByPrimaryKey(String usuario) throws FinderException {
Cliente cliente = null;
try {
cliente = clienteDAO.findClienteByUsuario(usuario);
} catch (DAOException e) {
throw new FinderException(e.getMessage());
}
if (cliente == null) {
return null; // No existe
} else {
return usuario; // Devuelve la clave primaria
}
}
public Collection ejbFindAll() throws FinderException {
Collection clientes;
Collection claves = new LinkedList();
Iterator it;
//Obtiene todos los DAO Cliente
try {
clientes = clienteDAO.findAll();
} catch (DAOException e) {
throw new FinderException(e.getMessage());
}
//Itera sobre ellos para devolver una colección de claves primarias
it = clientes.iterator();
while (it.hasNext()) {
Cliente c = (Cliente) it.next();
claves.add(c.getUsuario());
}
return claves;
}
// Método implícito de borrado
public void ejbRemove () throws RemoveException {
try {
Cliente cliente = new Cliente();
cliente.setUsuario(usuario);
clienteDAO.remove(cliente);
} catch (DAOException e) {
throw new RemoveException(e.getMessage());
}
}
18
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
// Parte III: Métodos de retrollamada
// Atributo para guardar el contexto EJB
private EntityContext context;
// Métodos para manejar el contexto EJB
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() {
this.context = null;
}
// Métodos de sincronización instancia-BD
public void ejbStore() {
try {
Cliente c = new Cliente();
c.setNombre(nombre); c.setNif(nif); c.setCorreo(correo);
c.setUsuario(usuario); c.setClave(clave);
clienteDAO.update(c);
} catch (DAOException e) {
throw new EJBException(e.getMessage());
}
}
public void ejbLoad() {
try {
Cliente c = clienteDAO.findClienteByUsuario(usuario);
if (c == null) {
throw new EJBException("Error al recuperar el EJB Cliente");
} else {
nombre = c.getNombre(); nif = c.getNif();
correo = c.getCorreo(); clave = c.getClave();
}
} catch (DAOException e) {
throw new EJBException(e.getMessage());
}
}
// Elimina su identidad y libera recursos
public void ejbPassivate() {
usuario = null;
}
// Obtiene una nueva identidad y adquiere recursos
public void ejbActivate() {
usuario = (String) context.getPrimaryKey();
}
}
19
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Para desplegar el componente en el servidor es necesario proporcionar cierta información de
configuración que permita al contenedor EJB conectar las 2 interfaces y la clase anterior,
conocer el tipo de componente EJB (entity o session) y el modelo de persistencia. Esta
información es declarada en el fichero de configuración “ejb-jar.xml” (directorio META-INF).
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>ClienteBMP</ejb-name>
<ejb-class>ejb.ClienteBeanBMP</ejb-class>
<home>interfaces.ClienteEJBHome</home>
<remote>interfaces.ClienteEJB</remote>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
</entity>
</enterprise-beans>
</ejb-jar>
Los objetos de negocio son componentes EJB entity. Podemos observar que la información
proporcionada contiene el nombre lógico que tendrá el componente en el servidor de
aplicaciones (“ClienteBMP”), las 2 interfaces del componente (home, remote), la clase bean
(ejb-class), el modelo de persistencia BMP (“Bean”), la clase que representa las claves
primarias (“String”, correspondiente al campo usuario) y si la entidad permite reentrancia en
las llamadas.
Desplegamos el nuevo componente reiniciando el servidor:
...
9:28:03,593 INFO
'ClienteBMP'
...
[ProxyFactory] Bound EJB Home 'ClienteBMP' to jndi
20
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
10. Pruebas.
Siempre que se desarrolle una aplicación es conveniente realizar pruebas unitarias a cada una de
las clases que se desarrollen. Para el desarrollo de aplicaciones convencionales en Java existen
herramientas como JUnit para realizar las pruebas de unidad.
En esta práctica no utilizaremos JUnit ni ninguna herramienta similar. Utilizaremos un
componente EJB de pruebas para realizar las pruebas unitarias al objeto de negocio Cliente y
al resto de componentes que desarrollemos durante esta práctica. Esta decisión está justificada
por dos razones: la mayor parte de los componentes EJB que desarrollaremos serán locales y
por tanto sólo accesibles desde el mismo servidor, y para ilustrar el desarrollo de un
componente de tipo session que no sea un controlador de caso de uso.
Típicamente los componentes EJB session representan lógica de negocio. Este tipo de
componentes suelen emplearse para desarrollar controladores de caso de uso. Existen dos tipos:
con estado (stateful) y sin estado (stateless). Existen ligeras diferencias en el desarrollo de
ambos subtipos de componentes. Pero, fundamentalmente, la diferencia reside en la eficiencia
de la gestión de estos objetos por el contenedor EJB. Un componente sin estado es gestionado
de forma más eficiente, ya que las instancias, al no tener estado, no están asociadas a ningún
cliente y pueden ser utilizadas indistintamente en cada llamada. El componente de pruebas que
desarrollemos será sin estado.
Interface remota.
Al igual que los objetos de negocio, los componentes de tipo sesión también son objetos
remotos accesibles a través de una interface RMI. En nuestro caso particular, la interface remota
sólo tendrá un método test que arrancará la batería de pruebas:
package interfaces;
import javax.ejb.EJBObject;
import javax.servlet.jsp.JspWriter;
import java.rmi.RemoteException;
public interface Test extends EJBObject
{
void test(JspWriter out) throws RemoteException;
}
Interface home.
La interface home de un componente de tipo sesión sin estado sólo puede tener un método
de creación sin argumentos. Puesto que no tiene estado no tiene sentido que sean pasados
parámetros en su método de construcción. Además, por ser un componente de tipo sesión (de
naturaleza transitoria) no se definen métodos de localización.
package interfaces;
import java.rmi.*;
import javax.ejb.*;
public interface TestHome extends EJBHome
{
public Test create() throws RemoteException, CreateException;
}
Clase Bean.
21
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
La clase bean que implementa este componente sigue la estructura típica de cualquier
componente EJB: interface remota, interface home y métodos de retrollamada. Este tipo de
componentes permite que la implementación de los dos últimos grupos de métodos (home y
retrollamada) sea vacía.
package ejb;
import
import
import
import
import
import
javax.ejb.*;
javax.naming.*;
javax.rmi.*;
java.util.*;
interfaces.*;
javax.servlet.jsp.JspWriter;
public class TestBean implements SessionBean {
// Parte I: Interface Remota
public void test (JspWriter out) {
test_ClienteBMP(out);
// test_ClienteCMP1(out);
// test_ClienteCMP2(out);
// test_ClienteCMP2Local(out);
// test_Producto(out);
// test_LineaPedido(out);
// test_Pedido(out);
// test_GestorPedido(out);
}
// Pruebas Unitarias del componente "ClienteBMP"
private void test_ClienteBMP(JspWriter out) {
try {
out.println("<BR>Pruebas unitarias \"Cliente BMP \"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia RMI-IIOP remota al home ...");
Object obj = contexto.lookup("ClienteBMP");
ClienteEJBHome clienteHome = (ClienteEJBHome)
PortableRemoteObject.narrow(obj, ClienteEJBHome.class);
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente ...");
ClienteEJB cliente = clienteHome.create("Alumno", "1",
"[email protected]", "alumno-dad", "clave");
out.println("<BR>Objeto Cliente creado");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
cliente = clienteHome.findByPrimaryKey("alumno-dad");
out.println("<BR>Objeto Cliente recuperado");
out.println("<BR>Utilizando los métodos de la interface remota...");
out.println("<BR>Nombre: " + cliente.getNombre() + "" +
"NIF: " + cliente.getNif() + "" +
"Correo: " + cliente.getCorreo() + "" +
22
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
"Usuario: " + cliente.getUsuario() + "" +
"Clave: " + cliente.getClave()
);
out.println("<BR>Recuperando todos los objetos Cliente ...");
Collection col = clienteHome.findAll();
out.println("<BR>Numero de clientes: " + col.size());
out.println("<BR>Eliminando el objeto Cliente ...");
cliente.remove();
out.println("<BR>Objeto cliente eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
// Pruebas Unitarias del componente "ClienteCMP1"
private void test_ClienteCMP1(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Cliente CMP1 \"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia RMI-IIOP remota al home ...");
Object obj = contexto.lookup("ClienteCMP1");
ClienteEJBHome clienteHome = (ClienteEJBHome)
PortableRemoteObject.narrow(obj, ClienteEJBHome.class);
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente (alumno, 2,
[email protected], alumno-dad2, clave)...");
ClienteEJB cliente = clienteHome.create("alumno", "2",
"[email protected]", "alumno-dad2", "clave");
out.println("<BR>Objeto Cliente creado");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
cliente = clienteHome.findByPrimaryKey("alumno-dad2");
out.println("<BR>Objeto Cliente recuperado");
out.println("<BR>Utilizando los métodos de la interface remota ...");
out.println(
"Nombre: " + cliente.getNombre() + "" +
"NIF: " + cliente.getNif() + "" +
"Correo: " + cliente.getCorreo() + "" +
"Usuario: " + cliente.getUsuario() + "" +
"Clave: " + cliente.getClave()
);
out.println("<BR>Recuperando todos los objetos Cliente ...");
Collection col = clienteHome.findAll();
23
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
out.println("<BR>Numero de clientes: " + col.size());
out.println("<BR>Eliminando el objeto Cliente ...");
cliente.remove();
out.println("<BR>Objeto cliente eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
/*
// Pruebas Unitarias del componente "ClienteCMP2"
private void test_ClienteCMP2(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Cliente CMP2 \"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia RMI-IIOP remota al home ...");
Object obj = contexto.lookup("ClienteCMP2");
ClienteEJBHome clienteHome = (ClienteEJBHome)
PortableRemoteObject.narrow(obj, ClienteEJBHome.class);
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente (alumno, 3,
[email protected], alumno-dad3, clave)...");
ClienteEJB cliente = clienteHome.create("alumno", "3",
"[email protected]", "alumno-dad3", "clave");
out.println("<BR>Objeto Cliente creado");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
cliente = clienteHome.findByPrimaryKey("alumno-dad3");
out.println("<BR>Objeto Cliente recuperado");
out.println("<BR>Utilizando los métodos de la interface remota ...");
out.println(
"Nombre: " + cliente.getNombre() + "" +
"NIF: " + cliente.getNif() + "" +
"Correo: " + cliente.getCorreo() + "" +
"Usuario: " + cliente.getUsuario() + "" +
"Clave: " + cliente.getClave()
);
out.println("<BR>Recuperando todos los objetos Cliente ...");
Collection col = clienteHome.findAll();
out.println("<BR>Numero de clientes: " + col.size());
24
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
out.println("<BR>Eliminando el objeto Cliente ...");
cliente.remove();
out.println("<BR>Objeto cliente eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
// Pruebas Unitarias del componente "ClienteCMP2 Local"
private void test_ClienteCMP2Local(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Cliente CMP2 Local\"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia Local al home ...");
ClienteLocalEJBHome clienteHome =
(ClienteLocalEJBHome)contexto.lookup("local/ClienteCMP2");
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente (alumno, 4,
[email protected], alumno-dad4, clave)...");
ClienteLocalEJB cliente = clienteHome.create("alumno", "4",
"[email protected]", "alumno-dad4", "clave");
out.println("<BR>Objeto Cliente creado");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
cliente = clienteHome.findByPrimaryKey("alumno-dad4");
out.println("<BR>Objeto Cliente recuperado");
out.println("<BR>Utilizando los métodos de la interface
...");
out.println(
"Nombre: " + cliente.getNombre() + "" +
"NIF: " + cliente.getNif() + "" +
"Correo: " + cliente.getCorreo() + "" +
"Usuario: " + cliente.getUsuario() + "" +
"Clave: " + cliente.getClave()
);
out.println("<BR>Recuperando todos los objetos Cliente ...");
Collection col = clienteHome.findAll();
out.println("<BR>Numero de clientes: " + col.size());
out.println("<BR>Eliminando el objeto Cliente ...");
25
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
cliente.remove();
out.println("<BR>Objeto cliente eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
// Pruebas Unitarias Producto
public void test_Producto(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Producto\"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia Local al home ...");
ProductoLocalHome productoHome =
(ProductoLocalHome)contexto.lookup("local/Producto");
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Producto (1, Libro, 20)...");
ProductoLocal producto = productoHome.create("1", "Libro", 20);
out.println("<BR>Objeto Producto creado");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
producto = productoHome.findByPrimaryKey("1");
out.println("<BR>Objeto Producto recuperado");
out.println("<BR>Utilizando los métodos de la interface
...");
out.println(
"ID: " + producto.getId() + "" +
"Nombre: " + producto.getNombre() + "" +
"Precio: " + producto.getPrecio()
);
out.println("<BR>Recuperando todos los objetos Producto ...");
Collection col = productoHome.findAll();
out.println("<BR>Numero de productos: " + col.size());
out.println("<BR>Eliminando el objeto Producto ...");
producto.remove();
out.println("<BR>Objeto producto eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
26
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
}
}
// Pruebas Unitarias Linea Pedido
public void test_LineaPedido(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Linea de Pedido\"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia Local al home ...");
LineaPedidoLocalHome lineaPedidoHome =
(LineaPedidoLocalHome)contexto.lookup("local/LineaPedido");
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Producto (1, Libro, 20)...");
ProductoLocalHome productoHome =
(ProductoLocalHome)contexto.lookup("local/Producto");
ProductoLocal producto = productoHome.create("1", "Libro", 20);
out.println("<BR>Objeto Producto creado");
out.println("<BR>Creacion de una Linea de Pedido para el producto con 10
unidades ...");
LineaPedidoLocal lp = lineaPedidoHome.create(producto, 10);
out.println("<BR>Linea de Pedido creada");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
lp = lineaPedidoHome.findByPrimaryKey(lp.getId());
out.println("<BR>Objeto Linea Pedido recuperado");
out.println("<BR>Utilizando los métodos de la interface
producto = lp.getProducto();
out.println(
"ID Linea: " + lp.getId() + "" +
"ID Producto: " + producto.getId() + "" +
"Nombre: " + producto.getNombre() + "" +
"Precio: " + producto.getPrecio() + "" +
"Cantidad: " + lp.getCantidad() + "" +
"Subtotal: " + lp.getSubTotal()
);
out.println("<BR>Eliminando el objeto Producto ...");
producto.remove();
out.println("<BR>Objeto producto eliminado");
27
...");
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
out.println("<BR>Eliminando el objeto Linea de Pedido ...");
lp.remove();
out.println("<BR>Objeto Linea de Pedido eliminado");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
// Pruebas Unitarias Pedido
public void test_Pedido(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"Pedido\"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia Local al home ...");
PedidoLocalHome pedidoHome =
(PedidoLocalHome)contexto.lookup("local/Pedido");
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente (alumno, 5,
[email protected], alumno-dad5, clave)...");
ClienteLocalEJBHome clienteHome =
(ClienteLocalEJBHome)contexto.lookup("local/ClienteCMP2");
ClienteLocalEJB cliente = clienteHome.create("alumno", "5",
"[email protected]", "alumno-dad5", "clave");
out.println("<BR>Creacion de un objeto Producto (1, Libro, 20)...");
ProductoLocalHome productoHome =
(ProductoLocalHome)contexto.lookup("local/Producto");
ProductoLocal producto = productoHome.create("1", "Libro", 20);
out.println("<BR>Creacion de un segundo Producto (2, CD, 10)...");
ProductoLocal producto2 = productoHome.create("2", "CD", 10);
out.println("<BR>Objetos Producto creados");
out.println("<BR>Creacion de un Pedido ...");
PedidoLocal pedido = pedidoHome.create(cliente);
out.println("<BR>Pedido creado y asociado con el cliente");
out.println("<BR>Añadiendo el primer producto al pedido con 2 unidades
...");
28
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
pedido.addLineaPedido(producto, 2);
out.println("<BR>Producto añadido");
out.println("<BR>Añadiendo el segundo producto al pedido con 5
unidades...");
pedido.addLineaPedido(producto2, 5);
out.println("<BR>Segundo producto añadido");
out.println("<BR>Recuperando el objeto utilizando la clave primaria
...");
pedido = pedidoHome.findByPrimaryKey(pedido.getId());
out.println("<BR>Objeto Pedido recuperado");
out.println("<BR>Utilizando los metodos de la interface
...");
out.println("<BR>Informacion lineas de pedido");
out.println("<BR>ID: " + pedido.getId() + "" +
"Total: " + pedido.getTotal());
Iterator it = pedido.getLineasPedido().iterator();
while (it.hasNext()) {
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
ProductoLocal p = lp.getProducto();
out.println(
"ID Linea: " + lp.getId() + "" +
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio() + "" +
"Cantidad: " + lp.getCantidad() + "" +
"Subtotal: " + lp.getSubTotal()
);
}
out.println("<BR>Elimina la primera linea ...");
pedido.removeLineaPedido(producto);
out.println("<BR>Primera linea eliminada");
out.println("<BR>Actualiza la segunda linea con 3 unidades ...");
pedido.updateLineaPedido(producto2, 3);
out.println("<BR>Segunda linea actualizada");
out.println("<BR>Pedido resultante: ");
it = pedido.getLineasPedido().iterator();
while (it.hasNext()) {
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
ProductoLocal p = lp.getProducto();
out.println(
"ID Linea: " + lp.getId() + "" +
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio() + "" +
"Cantidad: " + lp.getCantidad() + "" +
"Subtotal: " + lp.getSubTotal()
);
29
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
}
out.println("<BR>Eliminando el objeto Pedido ...");
pedido.remove();
out.println("<BR>Objeto Pedido eliminado");
out.println("<BR>Eliminando los productos ...");
producto.remove();
producto2.remove();
out.println("<BR>Productos eliminados");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}
// Pruebas Unitarias del Controlador "Gestor Pedido"
public void test_GestorPedido(JspWriter out) {
try {
out.println("<BR>----------------------------------------");
out.println("<BR>Pruebas unitarias \"GestorPedido\"");
out.println("<BR>Resolviendo el contexto JNDI ...");
InitialContext contexto = new InitialContext();
out.println("<BR>Contexto JNDI resuelto");
out.println("<BR>Obteniendo la referencia a la interface home ...");
GestorPedidoHome gestorPedidoHome = (GestorPedidoHome)
PortableRemoteObject.narrow(contexto.lookup("GestorPedido"),
GestorPedido.class);
out.println("<BR>Referencia al home correcta");
out.println("<BR>Creacion de un objeto Cliente (alumno, 6,
[email protected], alumno-dad6, clave) ...");
ClienteLocalEJBHome clienteHome =
(ClienteLocalEJBHome)contexto.lookup("local/ClienteCMP2");
ClienteLocalEJB cliente = clienteHome.create("alumno", "6",
"[email protected]", "alumno-dad6", "clave");
out.println("<BR>Creacion de un objeto Producto (1, Libro, 20)...");
ProductoLocalHome productoHome =
(ProductoLocalHome)contexto.lookup("local/Producto");
ProductoLocal producto = productoHome.create("1", "Libro", 20);
out.println("<BR>Creacion de un segundo Producto (2, CD, 10) ...");
ProductoLocal producto2 = productoHome.create("2", "CD", 10);
out.println("<BR>Objetos Producto creados");
30
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
out.println("<BR>Creacion del Gestor de Pedidos para el cliente...");
GestorPedido gestor = gestorPedidoHome.create("alumno-dad6");
out.println("<BR>Gestor de Pedidos creado");
out.println("<BR>Añadiendo el primer producto al pedido con 2
unidades...");
gestor.addProducto("1", 2);
out.println("<BR>Producto añadido");
out.println("<BR>Añadiendo el segundo producto al pedido con 5
unidades...");
gestor.addProducto("2", 5);
out.println("<BR>Segundo producto añadido");
out.println("<BR>Recorre la lista de productos utilizando next ...");
IteradorRemoto it = gestor.getProductos();
while (it.hasNext()) {
ProductoVO p = (ProductoVO) it.next();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio()
);
}
out.println("<BR>Recorre la lista de productos utilizando previous
...");
while (it.hasPrevious()) {
ProductoVO p = (ProductoVO) it.previous();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio()
);
}
out.println("<BR>Recorre la lista de productos utilizando next(n) ...");
while (it.hasNext()) {
Iterator it2 = it.next(1).iterator();
while (it2.hasNext()) {
ProductoVO p = (ProductoVO) it2.next();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio()
);
}
}
out.println("<BR>Recorre la lista de productos utilizando previous(n)
...");
while (it.hasPrevious()) {
Iterator it2 = it.previous(1).iterator();
while (it2.hasNext()) {
ProductoVO p = (ProductoVO) it2.next();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio()
31
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
);
}
}
out.println("<BR>Elimina el iterador ...");
it.remove();
out.println("<BR>Iterador eliminado");
out.println("<BR>Obtiene la informacion del Pedido ...");
PedidoVO pedidoVO = gestor.getInfoPedido();
out.println("<BR>Total: " + pedidoVO.getTotal());
Iterator it2 = pedidoVO.getLineasPedido().iterator();
while (it2.hasNext()) {
LineaPedidoVO lp = (LineaPedidoVO) it2.next();
ProductoVO p = lp.getProducto();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio() + "" +
"Cantidad: " + lp.getCantidad()
);
}
out.println("<BR>Actualiza la primera liena a 1 unidad ...");
gestor.updateProductoCantidad("1", 1);
out.println("<BR>Linea actualizada");
out.println("<BR>Elimina la segunda linea ...");
gestor.removeProducto("2");
out.println("<BR>Linea eliminada");
out.println("<BR>Obtiene la informacion del Pedido modificado");
pedidoVO = gestor.getInfoPedido();
it2 = pedidoVO.getLineasPedido().iterator();
while (it2.hasNext()) {
LineaPedidoVO lp = (LineaPedidoVO) it2.next();
ProductoVO p = lp.getProducto();
out.println(
"ID Producto: " + p.getId() + "" +
"Nombre: " + p.getNombre() + "" +
"Precio: " + p.getPrecio() + "" +
"Cantidad: " + lp.getCantidad()
);
}
out.println("<BR>Confirma el pedido ...");
// gestor.confirmarPedido();
out.println("<BR>Pedido confirmado");
out.println("<BR>Elimina el gestor de pedidos ...");
gestor.remove();
32
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
out.println("<BR>Gestor eliminado");
out.println("<BR>Eliminando los productos ...");
producto.remove();
producto2.remove();
out.println("<BR>Productos eliminados");
} catch (Exception e) {
System.out.println("<BR>Fallo:"+e.getMessage());
return;
}
}*/
// Parte II: Interface Home
public void ejbCreate() { }
public void ejbRemove() { }
// Parte III: Métodos de retrollamada
private SessionContext context;
public void setSessionContext(SessionContext context){
this.context = context;
}
public void ejbActivate() { }
public void ejbPassivate() { }
}
Destacamos de la implementación que la clase implementa la interface SessionBean, que
contiene los métodos propios de retrollamada de los componentes de tipo sesión. Como se
puede observar de las últimas líneas del código, ya no son necesarios los métodos ejbLoad y
ejbStore: estos componentes no son persistentes.
Nota: En la anterior clase de implementación aparecen todos los metodos que se utilizarán para
las pruebas de este guión práctico. En el listado anterior aparecen comentados los metodos que
hacen uso de los EJB que se irán creando en los siguientes apartados. El alumno deberá
decomentarlos según proceda.
Añadimos la información de este nuevo componente al fichero de configuración “ejb-jar.xml”:
...
<session>
<ejb-name>Test</ejb-name>
<home>interfaces.TestHome</home>
<remote>interfaces.Test</remote>
<ejb-class>ejb.TestBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
Se utiliza la etiqueta session para definir los componentes de tipo sesión. A diferencia de los
componentes entidad aquí no es necesario indicar la clave primaria ni el modelo de persistencia.
Lo que sí se especifica es el tipo de componente sesión (“Stateless”) y la transaccionalidad
gestionada por el contenedor (“Container”).
33
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Por último, creamos una aplicación web Cliente que acceda de forma remota al componente
Test y lance la batería de pruebas. Por simplicidad, crearemos una vista jsp llamada index.jsp
que haga uso del anterior EJB session de prueba.
34
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
<!-- JSP que lanza las pruebas -->
<%@page contentType="text/html" %>
<%@page import="javax.naming.*" %>
<%@page import="javax.rmi.*" %>
<%@page import="interfaces.*" %>
<%@page import="java.util.Properties" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Testeo Beans</title>
</head>
<body>
<%
Test test = null;
try {
out.println("Testeando Beans...<br>");
// Obtiene la referencia remota al componente de
pruebas
Properties propContext = new Properties();
propContext.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
propContext.put(Context.URL_PKG_PREFIXES,
"org.jboss.naming:org.jnp.interfaces");
//propContext.put(Context.PROVIDER_URL,
"jnp://localhost:1099");
InitialContext contexto = new
InitialContext(propContext);
out.println("Recuperando Referencia...<br>");
Object obj = contexto.lookup("Test");
out.println("Aplicando Narrow...<br>");
TestHome testHome =
(TestHome)PortableRemoteObject.narrow(obj, TestHome.class);
// Crea un objeto
out.println("Creando el objeto...<br>");
test = testHome.create();
// Lanza las pruebas
out.println("Probando el objeto...<br>");
test.test(out);
} catch (Exception e) {
e.printStackTrace();
}
%>
</body>
</html>
En la sencilla aplicación anterior vemos el modo de acceso típico a un componente EJB. Para
poder obtener correctamente la referencia al objeto home es necesario que el contexto inicial
JNDI esté bien configurado (ver en negrita). Estas propiedades de configuración vienen
establecidas en un fichero de propiedades Java con nombre “jndi.properties”en la carpeta /conf
de cada tipo de servidor, pero son necesarias indicarlas en la aplicación web.
35
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
11. Persistencia CMP 1.x
El siguiente modelo de persistencia propuesto por EJB es el denominado CMP versión 1.x. La
diferencia con el modelo anterior es que sacamos de la clase bean toda la programación relativa
a la gestión de la persistencia y la establecemos de forma de declarativa en un fichero de
configuración (jaws.xml en la carpeta META-INF).
En este apartado desarrollaremos el objeto de negocio Cliente con este modelo de persistencia.
Este nuevo componente mantiene la interface remota y home del objeto de negocio, ya que
sigue siendo el mismo. El código de la clase implementación sería el siguiente:
// Objeto de negocio Cliente con persistencia "CMP 1.x"
package ejb;
import javax.ejb.*;
public class ClienteBeanCMP1 implements EntityBean{
// Atributos para almacenar las propiedades del componente
public String nombre;
public String nif;
public String correo;
public String usuario;
public String clave;
// Implementación de los métodos de la interface remota
public String getNombre() { return nombre; }
public void setNombre(String nombre) { this.nombre = nombre; }
public String getNif() { return nif; }
public void setNif(String nif) { this.nif = nif; }
public String getCorreo() { return correo; }
public void setCorreo(String correo) { this.correo = correo; }
public String getUsuario() { return usuario = usuario; }
public void setUsuario(String usuario) { this.usuario = usuario;}
public String getClave() { return clave; }
public void setClave(String clave) { this.clave = clave; }
// Parte II: Interface Home
public String ejbCreate(String nombre, String nif, String correo,
String usuario, String clave) throws CreateException{
// Actualiza la instancia
this.nombre = nombre; this.nif = nif; this.correo = correo;
this.usuario = usuario; this.clave = clave;
return null;
}
public void ejbPostCreate(String nombre, String nif, String correo,
String usuario, String clave) { }
public void ejbRemove() {}
//Parte III: Implementación de los métodos de retrollamada
//Atributo para guardar el contexto EJB
private EntityContext context;
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() throws EJBException {
this.context = null;
}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbLoad() {}
public void ejbStore() {}
}
36
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Las características más destacadas de la implementación anterior son las siguientes:
- Los atributos que almacenan la información de la instancia se declaran como públicos
para que puedan ser accesibles por el contenedor durante las operaciones de
persistencia.
- El método ejbCreate no almacena la instancia en la base de datos y tampoco
devuelve la clave primaria (devuelve null para que compile el método). La clave
primaria es gestionada por el contenedor.
- Los métodos find no son implementados. Las consultas para estos métodos se
encuentran declaradas en un fichero de configuración.
- Los métodos ejbRemove y los de retrollamada tienen una implementación vacía.
Las responsabilidades asociadas a estos métodos son realizadas por el contenedor.
El siguiente paso es declarar la metainformación de este nuevo componente en el fichero de
configuración “ejb-jar.xml”:
<entity>
<ejb-name>ClienteCMP1</ejb-name>
<home>interfaces.ClienteEJBHome</home>
<remote>interfaces.ClienteEJB</remote>
<ejb-class>ejb.ClienteBeanCMP1</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
<cmp-version>1.x</cmp-version>
<cmp-field><field-name>nombre</field-name></cmp-field>
<cmp-field><field-name>nif</field-name></cmp-field>
<cmp-field><field-name>correo</field-name></cmp-field>
<cmp-field><field-name>usuario</field-name></cmp-field>
<cmp-field><field-name>clave</field-name></cmp-field>
<primkey-field>usuario</primkey-field>
</entity>
Las diferencias frente al modelo de persistencia BMP radican en que estamos indicando que la
persistencia sea gestionada por el contenedor (“Container”) siguiendo la versión “1.x”. En
este modelo debemos indicar qué atributos públicos de la instancia han de ser persistentes
(“cmp-field”) y de ellos cuál representa la clave primaria (“primkey-field”);
La información relacionada con la persistencia de este componente (metodos localizadores)
se declara en un fichero propietario del servidor de aplicaciones. En JRun este fichero se
llamada “jaws.xml”.
37
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
<jaws>
<datasource>java:/DataSourceDAD</datasource>
<type-mapping>MappingAccess</type-mapping>
<enterprise-beans>
<entity>
<ejb-name>ClienteCMP1</ejb-name>
<table-name>ClienteCMP1</table-name>
<create-table>false</create-table>
<cmp-field>
<field-name>nombre</field-name>
<column-name>nombre</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>VARCHAR(255)</sql-type>
</cmp-field>
<cmp-field>
<field-name>nif</field-name>
<column-name>nif</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>VARCHAR(255)</sql-type>
</cmp-field>
<cmp-field>
<field-name>correo</field-name>
<column-name>correo</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>VARCHAR(255)</sql-type>
</cmp-field>
<cmp-field>
<field-name>usuario</field-name>
<column-name>usuario</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>VARCHAR(255)</sql-type>
</cmp-field>
<cmp-field>
<field-name>clave</field-name>
<column-name>clave</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>VARCHAR(255)</sql-type>
</cmp-field>
</entity>
</enterprise-beans>
</jaws>
En este fichero de configuración utilizamos la etiqueta source para indicar la fuente de datos
por defecto que ha de utilizar para la persistencia. El contenedor JAWS de Jboss proporciona
de forma automática los métodos: findAll(), findByPrimaryKey(pk) y todos los findByXX(YY)
donde XX y YY referencia a los atributos del EJB.
Finalizamos activando y ejecutando las pruebas de este componente.
38
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
12. Persistencia CMP 2.x.
El modelo de persistencia CMP 2.x proporciona persistencia transparente al programador. Ya
no será necesario crear las tablas ni definir todos los métodos relacionados con la persistencia.
Salvo que tengamos algún requisito de persistencia que nos obligue a utilizar un
almacenamiento no relacional (BMP) o que tengamos que integrar la aplicación con una base de
datos dada (CMP 1.x), este es el modelo de persistencia recomendable de propósito general.
Mantenemos la interface home y remota igual que los componentes anteriores, ya que sigue
siendo el mismo objeto de negocio. La clase bean para este modelo de persistencia sería la
siguiente:
// Objeto de negocio "Cliente" con persistencia CMP 2.x
package ejb;
import javax.ejb.*;
import java.util.*;
public abstract class ClienteBeanCMP2 implements EntityBean
{
// Parte I: Implementación de objeto de negocio
// En este modelo de persistencia no se definen los atributos ni se
// implementan los métodos de acceso/modificación
public abstract String getNombre();
public abstract void setNombre(String nombre);
public abstract String getNif();
public abstract void setNif(String nif);
public abstract String getCorreo();
public abstract void setCorreo(String correo);
public abstract String getUsuario();
public abstract void setUsuario(String usuario);
public abstract String getClave();
public abstract void setClave(String clave);
// Parte II: Interface Home
public String ejbCreate(String nombre, String nif, String correo,
String usuario, String clave) throws CreateException{
// Actualiza la instancia utilizando los métodos de la interface
setNombre(nombre); setNif(nif); setCorreo(correo);
setUsuario(usuario); setClave(clave);
return null;
}
public void ejbPostCreate(String nombre, String nif, String correo,
String usuario, String clave) { }
public void ejbRemove() {}
// Parte III: Métodos de retrollamada
private EntityContext context;
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() throws EJBException {
this.context = null;
}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
Seguidamente definimos el nuevo componente en fichero de configuración “ejb-jar.xml” con
este modelo de persistencia:
39
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
<entity>
<ejb-name>ClienteCMP2</ejb-name>
<home>interfaces.ClienteEJBHome</home>
<remote>interfaces.ClienteEJB</remote>
<ejb-class>ejb.ClienteBeanCMP2</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>ClienteEsquema</abstract-schema-name>
<cmp-field><field-name>nombre</field-name></cmp-field>
<cmp-field><field-name>nif</field-name></cmp-field>
<cmp-field><field-name>correo</field-name></cmp-field>
<cmp-field><field-name>usuario</field-name></cmp-field>
<cmp-field><field-name>clave</field-name></cmp-field>
<primkey-field>usuario</primkey-field>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params />
</query-method>
<ejb-ql> SELECT OBJECT(c) FROM ClienteEsquema c </ejb-ql>
</query>
</entity>
Destacamos de la declaración anterior el nuevo modelo de persistencia (“2.x”) y el nombre
lógico del componente en el almacenamiento (“ClienteEsquema”).
La definición de los métodos create, load, store, etc. del modelo anterior es automática, excepto
para el método findAll, que es propio del objeto de negocio. Para ayudar al contenedor a generar
la consulta SQL adecuada, definimos a alto nivel en lenguaje EJB-QL la consulta de
recuperación.
La estructura de declaración de una consulta es la siguiente:
- query-method: indicamos el nombre del método de consulta (“findAll”) y una lista de
parámetros indicando sólo su tipo. Por ejemplo, con <method-param>java.lang.String
</method-param> establecemos un parámetro de tipo String en la consulta. Estos
parámetros se definen dentro de la etiqueta method-params.
- ejb-ql: consulta expresada en lenguaje EJB-QL. La consulta hace referencia a los
nombres lógicos de los componentes (“ClienteEsquema”) y permite establecer
parámetros (?1, ?2, etc.)
Para indicar el DataSource a utilizar por el contenedor CMP 2.0 de Jboss modificaremos el
fichero standardjbosscmp-jdbc.xml:
<jbosscmp-jdbc>
<defaults>
<datasource>java:/DataSourceDAD</datasource>
<datasource-mapping>MappingAccess</datasource-mapping> …
Por último, activaremos las pruebas unitarias de este componente: ClienteCMP2.
13. Interfaces Locales.
40
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Los componentes EJB pueden tener interfaces locales para colaborar con otros objetos de
negocio dentro del servidor de aplicaciones sin recurrir a la sobrecarga de una llamada RMI.
Aprovechamos el ejemplo de implementación del objeto de negocio Cliente con persistencia
CMP 2.x para ilustrar el uso de estos interfaces.
En nuestro ejemplo, definimos la interface local del objeto con los mismos métodos que la
interface remota. En general, las interfaces locales y remotas no tienen por qué ser iguales.
// Interface Local del objeto de negocio "Cliente"
package interfaces;
import javax.ejb.*;
public interface ClienteLocalEJB extends EJBLocalObject {
public String getNombre();
public void setNombre(String nombre);
public String getNif();
public void setNif(String nif);
public String getCorreo();
public void setCorreo(String correo);
public String getUsuario();
public void setUsuario(String usuario);
public String getClave();
public void setClave(String clave);
}
Debemos prestar atención a que en esta interface el tipo base es EJBLocalObject, diferente a la
interface remota. Además, ninguno de los métodos lanza la excepción RemoteException, ya
que no serán invocados a través de RMI.
El siguiente paso es definir la interface home local que controle el ciclo de vida del
componente. Mantenemos los mismos métodos que la interface home remota.
// Interface Home Local del componente "Cliente"
package interfaces;
import javax.ejb.*;
import java.util.Collection;
public interface ClienteLocalEJBHome extends EJBLocalHome {
ClienteLocalEJB create(String nombre, String nif, String correo,
String usuario, String clave) throws CreateException;
public ClienteLocalEJB findByPrimaryKey (String usuario)
throws FinderException;
public Collection findAll() throws FinderException;
}
Esta interface hereda del tipo base EJBLocalHome y ninguno de sus métodos lanza la
excepción RemoteException.
41
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Las interfaces locales son independientes de las interfaces remotas. Un componente puede tener
cualquiera de los dos tipos de interfaces o los dos a la vez.
Añadimos una interface remota a un componente incluyendo las etiquetas local y local-home en
el fichero de configuración “ejb-jar.xml”:
...
<home>interfaces.ClienteEJBHome</home>
<remote>interfaces.ClienteEJB</remote>
<local-home>interfaces.ClienteLocalEJBHome</local-home>
<local>interfaces.ClienteLocalEJB</local>
<ejb-class>ejb.ClienteBeanCMP2</ejb-class>
<persistence-type>Container</persistence-type>
...
Finalizamos activando las pruebas de la interface local de este objeto: ClienteCMP2Local.
14. Objetos de Negocio del Caso de Uso.
En los apartados anteriores hemos estudiado los modelos de persistencia que proporciona EJB.
Cuando implementamos un caso de uso las colaboraciones entre las entidades se realizarán en el
servidor como respuesta a la ejecución de operaciones del controlador. Para mejorar el
rendimiento de esta colaboración es conveniente que utilicemos interfaces locales.
En los siguientes apartados implantaremos el resto de objetos de negocio del caso de uso
(Producto, Línea de Pedido y Pedido) utilizando sólo interfaces locales y el modelo de
persistencia CMP 2.x.
15. Objecto de Negocio Producto.
Interface Local.
package interfaces;
import javax.ejb.EJBLocalObject;
public interface ProductoLocal extends EJBLocalObject
{
public String getId();
public void setId(String id);
public String getNombre();
public void setNombre(String nombre);
public int getPrecio();
public void setPrecio(int precio);
}
42
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Interface Home.
package interfaces;
import javax.ejb.*;
import java.util.Collection;
public interface ProductoLocalHome extends EJBLocalHome
{
public ProductoLocal create(String id, String nombre, int precio)
throws CreateException;
public ProductoLocal findByPrimaryKey(String id)
throws FinderException;
public Collection findAll() throws FinderException;
}
Clase Bean.
package ejb;
import javax.ejb.*;
public abstract class ProductoBean implements EntityBean
{
// Parte I: Objeto de Negocio
public abstract String getId() ;
public abstract void setId(String id);
public abstract String getNombre();
public abstract void setNombre(String nombre);
public abstract int getPrecio() ;
public abstract void setPrecio(int precio) ;
// Parte II: Interface Home
public String ejbCreate(String id, String nombre, int precio)
throws CreateException {
setId(id);
setNombre(nombre); setPrecio(precio);
return null;
}
public void ejbPostCreate(String id, String nombre, int precio) { }
public void ejbRemove() {}
// Parte III: Métodos de Retrollamada
private EntityContext context;
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() throws EJBException {
this.context = null;
}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
43
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Configuración.
Añadimos al fichero “ejb-jar.xml” la siguiente declaración:
<entity>
<ejb-name>Producto</ejb-name>
<local-home>interfaces.ProductoLocalHome</local-home>
<local>interfaces.ProductoLocal</local>
<ejb-class>ejb.ProductoBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>ProductoEsquema</abstract-schema-name>
<cmp-field><field-name>id</field-name></cmp-field>
<cmp-field><field-name>nombre</field-name></cmp-field>
<cmp-field><field-name>precio</field-name></cmp-field>
<primkey-field>id</primkey-field>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params />
</query-method>
<ejb-ql> SELECT OBJECT(p) FROM ProductoEsquema p </ejb-ql>
</query>
</entity>
Activamos las pruebas de este componente: Producto.
44
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
16. Objecto de Negocio Línea de Pedido.
El objeto de negocio Línea de Pedido es el primer componente que define una relación con
otro objeto de negocio, en este caso Producto. Esta relación la expresaremos en la interface del
objeto utilizando un método get que devuelva una referencia de tipo ProductoLocal.
Interface Local.
package interfaces;
import javax.ejb.EJBLocalObject;
public interface LineaPedidoLocal extends EJBLocalObject
{
public ProductoLocal getProducto(); // Relación con Producto
public int getCantidad();
public void setCantidad(int cantidad);
public int getSubTotal(); // Atributo derivado
public String getId(); // Sólo para utilizarlo en las pruebas Unitarias
}
Se ha introducido en esta interface el atributo derivado subTotal, que es calculado a partir de
la cantidad y el precio del producto. Dado que es un atributo derivado no necesita ser
persistente.
Interface Home.
La interface home del componente contiene un método create que establece la información que
define una línea de pedido: producto y cantidad. No es necesario incluir ningún método de
localización adicional.
package interfaces;
import javax.ejb.*;
public interface LineaPedidoLocalHome extends EJBLocalHome
{
public LineaPedidoLocal create(ProductoLocal producto, int cantidad)
throws CreateException;
public LineaPedidoLocal findByPrimaryKey(String id) throws FinderException;
}
45
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Clase Bean.
Todos los componentes EJB deben poseer algún campo persistente que represente su clave
primaria. Para los objetos Cliente y Producto la clave primaria ha sido fácil de identificar, ya
que por sus definiciones tienen campos que identifican unívocamente a cada objeto, como son el
identificador de usuario y el identificador del producto (podría ser su código de barras).
Las líneas de pedido son objetos de negocio dependientes de Pedido, por lo que no es fácil
identificar un campo que haga de clave primaria. Para estos casos se propone utilizar alguna
técnica de generación de claves primarias. Un tipo de identificadores únicos bien conocidos
son los UUID (Universal Unique IDentifier)
Un identificador UUID es una cadena de 32 caracteres generados mediante un algoritmo que
garantiza la unicidad de la clave. Una clave UUID está formada por 4 bloques de 8 caracteres
cada uno:
- Hora actual: representa el momento en el que se genera la clave obtenido con la
llamada System.currentTimeMillis() y expresada en un número hexadecimal de 8
dígitos.
- Dirección IP: dirección IP de la máquina en la que se genera la clave expresada como
un número hexadecimal de 8 dígitos.
- Hash de un objeto: hash del objeto generador de la clave. Se expresa también como un
hexadecimal de 8 dígitos.
- Número aleatorio en hexadecimal con 8 dígitos.
Una posible implementación de este algoritmo como un objeto Singleton sería la siguiente:
package ejb;
import java.security.*;
import java.net.*;
// Generador de claves UUID
public class GeneradorClaves {
// Implementado como un Singleton
private static GeneradorClaves instancia = null;
public static GeneradorClaves getInstancia() {
if (instancia == null) {
instancia = new GeneradorClaves();
}
return instancia;
}
// IP de la máquina
private String IP;
// Generador de números aleatorios
private SecureRandom random;
// Hash del objeto generador
String hashObj;
private GeneradorClaves() {
try {
InetAddress inet = InetAddress.getLocalHost();
byte[] bytes = inet.getAddress();
46
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
IP = hexformat(getInt(bytes), 8);
random = new SecureRandom();
hashObj = hexformat(System.identityHashCode(this),8);
} catch (Exception e) {
e.printStackTrace();
}
}
// Devuelve un identificador UUID
public synchronized String getId() {
String numAleatorio = hexformat(random.nextInt(), 8);
long time = System.currentTimeMillis();
String timeString = hexformat(((int) time) & 0xFFFFFFFF, 8);
return timeString + IP + hashObj + numAleatorio;
}
// Funciones de conversión
private int getInt(byte[] bytes) {
int valor
valor <<=
valor <<=
valor <<=
= (int) bytes[0];
8; valor |= (int) bytes[1];
8; valor |= (int) bytes[2];
8; valor |= (int) bytes[3];
return valor;
}
private String hexformat(int valor, int tam) {
String hex = Integer.toHexString(valor).toUpperCase();
StringBuffer str = new StringBuffer();
for (int i = hex.length(); i < tam; i++) {
str.append("0");
}
return str.toString() + hex;
}
}
En un modelo relacional la clave de este objeto podría estar formada por la combinación de las
claves de Pedido y Producto. Esta solución no es adecuada para EJB, ya que la clave primaria
debe ser un solo campo. Podría crearse una clase ficticia que representara la composición de
estas claves y que actuara como clave primaria, pero no es una buena idea ya que estaríamos
perdiendo el carácter orientado a objeto de las entidades.
En resumen, cuando nos encontremos con objetos de negocio que no tienen un identificador
claro podemos recurrir a la generación de identificadores UUID para generar su clave. Este
identificador será persistente. Es decisión del programador hacer público este campo a través de
su interface. Hemos optado por introducirlo en la interface para utilizarlo en las pruebas
unitarias.
47
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
package ejb;
import javax.ejb.*;
import javax.naming.*;
public abstract class LineaPedidoBean implements EntityBean
{
// Parte I: Objeto de Negocio
// Asociación con Producto
public abstract ProductoLocal getProducto();
public abstract void setProducto(ProductoLocal producto);
public abstract void setId(String id);
public abstract String getId();
public abstract int getCantidad();
public abstract void setCantidad(int cantidad);
// Atributo calculado
public int getSubTotal() {
return getProducto().getPrecio() * getCantidad();
}
// Parte II: Interface Home
public String ejbCreate (ProductoLocal producto, int cantidad)
{
// Obtiene un identificador UUID que utilizará como clave primaria
String id = GeneradorClaves.getInstancia().getId();
setId(id);
setCantidad(cantidad);
setProducto(producto);
return null;
}
public void ejbPostCreate (ProductoLocal producto, int cantidad) {}
// Parte III: Métodos de retrollamada
private EntityContext context;
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() throws EJBException {
this.context = null;
}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
48
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Configuración.
La metainformación del componente que introducimos en el fichero “ejb-jar.xml” sería la
siguiente:
<entity>
<ejb-name>LineaPedido</ejb-name>
<local-home>interfaces.LineaPedidoLocalHome</local-home>
<local>interfaces.LineaPedidoLocal</local>
<ejb-class>ejb.LineaPedidoBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>LineaPedidoEsquema</abstract-schema-name>
<cmp-field><field-name>id</field-name></cmp-field>
<cmp-field><field-name>cantidad</field-name></cmp-field>
<primkey-field>id</primkey-field>
</entity>
Aparte, añadimos en este mismo fichero la información relativa a las relaciones establecidas
con otros objetos de negocio de la aplicación. Todas las relaciones se agrupan bajo la etiqueta
relationships, que se declara después de la etiqueta enterprise-beans.
<relationships>
<ejb-relation>
<ejb-relation-name>LineaPedido-Producto</ejb-relation-name>
<ejb-relationship-role>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>LineaPedido</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>producto</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>Producto</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
</relationships>
</ejb-jar>
...
Una relación en EJB está constituida por dos partes o roles. En cada parte indicamos la
cardinalidad del objeto en la relación (multiplicity), que puede ser Many o One, e identificamos
el lado de la asociación con relationship-role-source. Si la parte de la relación que estamos
definiendo permite la navegabilidad hacia el otro extremo indicamos el nombre del campo con
cmr-field (“producto”). Este campo deberá definirse en la clase bean con un par de métodos
abstractos getProducto y setProducto. Esta información puede trasladarse directamente de la
información contenida en el diagrama de clases UML.
49
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Por último, desplegamos el componente y realizamos sus pruebas unitarias: LineaPedido.
17. Objeto de Negocio Pedido.
Interface Local.
package interfaces;
import javax.ejb.EJBLocalObject;
public interface PedidoLocal extends EJBLocalObject
{
// Atributos del Objeto
public String getId();
public boolean getFinalizado();
public void setFinalizado(boolean finalizado);
public int getTotal(); // Atributo derivado
// Relaciones
// Relación con Cliente
public ClienteLocalEJB getCliente();
// Para manejar la relación con Líneas de Pedido
public void addLineaPedido(ProductoLocal producto, int cantidad);
public void removeLineaPedido(ProductoLocal producto);
public void updateLineaPedido(ProductoLocal producto, int cantidad);
public java.util.Collection getLineasPedido();
}
Interface Home.
package interfaces;
import java.util.Collection;
import javax.ejb.*;
public interface PedidoLocalHome extends EJBLocalHome
{
public PedidoLocal create(ClienteLocalEJB cliente) throws CreateException;
public PedidoLocal findByPrimaryKey(String id) throws FinderException;
}
50
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Clase Bean.
package ejb;
import javax.ejb.*;
import javax.naming.*;
import java.util.*;
public abstract class PedidoBean implements EntityBean
{
// Parte I: Implementación del Objeto de Negocio
public abstract String getId();
public abstract void setId(String id);
public abstract boolean getFinalizado();
public abstract void setFinalizado(boolean finalizado);
public int getTotal() { // Calcula el total del pedido
Iterator it = getLineasPedido().iterator();
int total = 0;
while (it.hasNext()) {
total += ((LineaPedidoLocal)it.next()).getSubTotal();
}
return total;
}
// Métodos para mantener la relación con Cliente
public abstract ClienteLocalEJB getCliente();
public abstract void setCliente(ClienteLocalEJB cliente);
// Métodos para mantener la relación con las Líneas de Pedido
public abstract Collection getLineasPedido();
public abstract void setLineasPedido(Collection lineas);
// Añade una Línea de Pedido
public void addLineaPedido(ProductoLocal producto, int cantidad) {
// Si existe la línea, añade la cantidad
Iterator it = getLineasPedido().iterator();
while (it.hasNext()) {
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
if (lp.getProducto().equals(producto)) {
lp.setCantidad(lp.getCantidad() + cantidad);
return;
}
}
// Obtiene el Home de las Líneas de Pedido
LineaPedidoLocalHome lpHome = null;
try {
InitialContext contextoInicial = new InitialContext();
lpHome = (LineaPedidoLocalHome)
contextoInicial.lookup("local/LineaPedido");
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
// Crea la línea de pedido
LineaPedidoLocal lp = null;
try {
lp = lpHome.create(producto, cantidad);
} catch (CreateException e) {
throw new EJBException (e.getMessage());
}
// La añade al campo líneas de pedido
getLineasPedido().add(lp);
}
51
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
// Elimina una línea de pedido a partir del producto
public void removeLineaPedido(ProductoLocal producto) {
// Itera sobre la colección
// Cuando la encuentra la elimina de la colección
// y la borra
Iterator it = getLineasPedido().iterator();
while (it.hasNext()) {
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
if (lp.getProducto().equals(producto)) {
try {
lp.remove();
} catch (RemoveException e) {
throw new EJBException (e.getMessage());
}
break;
}
}
}
// Actualiza una línea de pedido a partir del producto
public void updateLineaPedido(ProductoLocal producto, int cantidad) {
// Itera sobre la colección
Iterator it = getLineasPedido().iterator();
while (it.hasNext()) {
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
if (lp.getProducto().equals(producto)) {
lp.setCantidad(cantidad); // Actualiza la cantidad
break;
}
}
}
// Parte II: Interface Home
public String ejbCreate(ClienteLocalEJB cliente) throws CreateException
{
// Obtiene un identificador único UUID
String id = GeneradorClaves.getInstancia().getId(this);
setId(id);
setCliente(cliente);
return null;
}
public void ejbPostCreate(ClienteLocalEJB cliente) { }
// Parte III: Métodos de Retrollamada
private EntityContext context;
public void setEntityContext(EntityContext context) {
this.context = context;
}
public void unsetEntityContext() throws EJBException {
this.context = null;
}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
52
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
De la implementación de este componente podemos destacar los métodos que actúan sobre la
relación entre el pedido y las líneas de pedido. Esta relación es 1:N, por lo que el tipo de los
métodos getLineasPedido y setLineasPedido es java.util.Collection. La especificación EJB
propone dos tipos de colecciones para trabajar con las relaciones 1:N: java.util.Set y
java.util.Collection.
Estrictamente, la relación entre pedido y sus líneas de pedido es una asociación qualificada de
UML, cuyo campo de indexación sería el producto. La colección más adecuada para manejar
este tipo de relación sería un Map. Dado que no se nos ofrece esta funcionalidad, identificamos
la línea de pedido en los métodos addLineaPedido, removeLineaPedido y updateLineaPedido,
iterando sobre la colección hasta localizar el objeto que contenga el producto.
Cabe destacar que el método addLineaPedido tiene la responsabilidad de construir una línea de
pedido. Para ello ha de obtener la interface home del tipo. Como podemos observar, el acceso a
la interface local es diferente al acceso remoto que hemos estudiado en apartados anteriores. El
nombre JNDI del componente va prefijado por “local/” para que no coincida con el nombre
dado a la interface remota. Tampoco es necesario hacer un narrow portable RMI-IIOP.
InitialContext contextoInicial = new InitialContext();
lpHome = (LineaPedidoLocalHome)
contextoInicial.lookup("local/LineaPedido");
Por último, podemos observar que se ha utilizado un generador de claves UUID para obtener
el identificador del pedido.
Configuración.
Añadimos la definición del componente dentro de la etiqueta enterprise-beans del fichero “ejbjar.xml”:
<entity>
<ejb-name>Pedido</ejb-name>
<local-home>interfaces.PedidoLocalHome</local-home>
<local>interfaces.PedidoLocal</local>
<ejb-class>ejb.PedidoBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>true</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>PedidoEsquema</abstract-schema-name>
<cmp-field><field-name>id</field-name></cmp-field>
<cmp-field><field-name>finalizado</field-name></cmp-field>
<primkey-field>id</primkey-field>
</entity>
El objeto de negocio Pedido establece una relación con Cliente y Línea de Pedido. Definimos
las relaciones tal cual fueron establecidas en UML. Cabe destacar que la relación entre Pedido y
Línea de Pedido es de composición, es decir, que las líneas de pedido no tienen sentido si se
elimina el Pedido donde fueron creadas. Para conseguir esta semántica se introduce la etiqueta
cascade-delete en la parte correspondiente a la Línea de Pedido en la relación. La declaración de
las relaciones en el fichero “ejb-jar.xml” sería la siguiente:
53
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
<ejb-relation>
<ejb-relation-name>Pedido-Cliente</ejb-relation-name>
<ejb-relationship-role>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>Pedido</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>cliente</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<multiplicity>One</multiplicity>
<cascade-delete />
<relationship-role-source>
<ejb-name>ClienteCMP2</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
<ejb-relation>
<ejb-relation-name>Pedido-LineaPedido</ejb-relation-name>
<ejb-relationship-role>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>Pedido</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>lineasPedido</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<multiplicity>Many</multiplicity>
<cascade-delete />
<relationship-role-source>
<ejb-name>LineaPedido</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
Finalizamos desplegando el componente y ejecutando sus pruebas: Pedido.
54
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
18. Controlador del Caso de Uso.
En los anteriores apartados hemos implementado los objetos de negocio del caso de uso. En este
apartado programaremos el controlador del caso de uso. Un controlador tiene una naturaleza
distinta a la de un objeto de negocio. Su propósito es recoger la lógica de negocio que gobierna
el caso de uso. Además, su tiempo de vida está limitado a la ejecución del caso de uso para
algún usuario. Por lo tanto tiene una naturaleza transitoria y no persistente.
La especificación EJB propone un tipo de componentes denominados “session” adecuados para
modelar un controlador de caso de uso. Como vimos en apartados anteriores, estos componentes
pueden ser de dos tipos: con estado (stateful) y sin estado (stateless). El controlador que
implementaremos corresponde al primer tipo, ya que su estado es el pedido que solicita el
usuario.
Una de las responsabilidades más importantes de un controlador es adaptar la información
que es enviada por la capa de presentación en cada operación con la información que esperan
los objetos de negocio. Por ejemplo, la operación addProducto, cuya responsabilidad es añadir
cierta cantidad de un producto al pedido, tiene como parámetro una cadena que representa la
clave primaria del producto. El controlador obtendrá el objeto producto asociado a esa clave
primaria para realizar la llamada addLineaPedido sobre el pedido.
El controlador ha de ser un objeto distribuido, ya que debe de ser accesible desde la capa de
presentación. Por lo tanto, sólo definiremos su interface remota. A continuación se muestra la
interface remota que contiene las operaciones identificadas en la fase de análisis y diseño.
// Interface Remota del Controlador del cdu "Solicitar Pedido"
package interfaces;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface GestorPedido extends EJBObject{
public IteradorRemoto getProductos() throws RemoteException;
public void addProducto(String idProducto, int cantidad)
throws RemoteException;
public void removeProducto(String idProducto) throws RemoteException;
public void updateProductoCantidad(String idProducto, int cantidad)
throws RemoteException;
public void confirmarPedido() throws RemoteException;
public PedidoVO getInfoPedido() throws RemoteException;
}
55
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Una comunicación inadecuada entre la capa de presentación y la de negocio influye
sensiblemente en el rendimiento de la aplicación. Habitualmente suelen ser dos las causas que
limitan el rendimiento de la aplicación:
- Devolver referencias remotas a objetos de negocio con el propósito de obtener su
información. Esto suele ser una mala idea, ya que cada invocación de un método de la
interface supone una llamada remota.
- Devolver todos los resultados de una consulta. En la capa de presentación suelen
mostrarse los resultados por páginas y normalmente sólo se muestran las primeras.
Para resolver el primer problema se suele hacer uso de los llamados ValueObjects (VO). Éstos
son objetos contenedores de datos cuyo propósito es recoger la información que interese de un
objeto de negocio para ser devuelta en una sola llamada. En la interface remota podemos ver un
ejemplo de VO: PedidoVO es devuelto cuando se requiere la información del pedido.
El segundo problema anteriormente planteado queda ilustrado en el método getProductos. En
lugar de devolver una colección de VO con todos los resultados se devuelve un iterador sobre
los resultados. Este iterador permite obtener los resultados conforme vayan siendo necesitados,
mejorando el uso de recursos y difiriendo la instanciación de los VO para cuando sean
necesarios.
Estudiaremos la solución a estos dos problemas en los siguientes subapartados.
Objetos de Datos (Value Objects).
La capa de presentación siempre requiere información de los objetos de negocio. Utilizamos
objetos VO para devolver la información de los objetos de negocio. Según los patrones básicos
de diseño, la clase encargada de crear estos objetos de datos ha ser quien mantiene la
información, es decir, la clase bean que implementa los objetos de negocio.
Para trabajar con objetos de datos definimos el tipo base ValueObject del siguiente modo:
package interfaces;
public interface ValueObject extends java.io.Serializable {}
Podemos observar que el tipo ValueObject no es más que una interface marca que define un
supertipo de las clases VO y además caracteriza la propiedad serializable que permite que los
objetos de estas clases sean enviados por valor utilizando RMI.
Definimos también el tipo de los objetos de negocio que devuelven un VO (EntityValueObject),
que define un método para devolver el VO y otro para actualizar el objeto con los valores de un
VO:
package interfaces;
public interface EntityValueObject {
public ValueObject getVO();
public void setVO(ValueObject vo);
}
56
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Volviendo a nuestro caso de uso, el objeto de negocio Producto debe devolver un VO. La
clase que define este VO con toda su información sería la siguiente:
package ejb;
public class ProductoVO implements ValueObject {
private String id;
private String nombre;
private int precio;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getNombre() { return nombre; }
public void setNombre(String nombre) { this.nombre = nombre; }
public int getPrecio() { return precio; }
public void setPrecio(int precio) {
this.preciobase = preciobase;}
}
Modificamos la interface del objeto Producto (ProductoLocal) para indicar que devuelve
objetos de datos (implementa el tipo EntityValueObject):
...
import javax.ejb.EJBLocalObject;
public interface ProductoLocal extends EJBLocalObject, EntityValueObject
{
...
Implementamos los dos métodos de la interface en la clase bean:
// Parte IV: Implementación de la interface EntityValueObject
public ValueObject getVO() {
ProductoVO producto = new ProductoVO();
producto.setId(getId()); producto.setNombre(getNombre());
producto.setPrecio(getPrecio());
return producto;
}
public void setVO (ValueObject vo) {
ProductoVO producto = (ProductoVO) vo;
setId(producto.getId()); setNombre(producto.getNombre());
setPrecio(producto.getPrecio());
}
57
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Iterador Remoto.
Un iterador remoto es un componente distribuido EJB que permite a la capa de presentación
iterar sobre los resultados de una consulta. De los dos tipos de componentes EJB, entidad y
sesión, la naturaleza de estos objetos encaja con el tipo sesión, ya que son objetos transitorios
cuyo uso está limitado a la iteración de un usuario de la aplicación sobre los resultados de una
consulta. Además, sería un componente sesión de tipo stateful, puesto que mantiene el estado de
la iteración actual.
Definimos de forma genérica la interface remota de los iteradores del siguiente modo:
package interfaces;
import java.util.Collection;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface IteradorRemoto extends EJBObject {
public ValueObject next() throws RemoteException;
public ValueObject previous() throws RemoteException;
public Collection next(int n) throws RemoteException;
public Collection previous (int n) throws RemoteException;
public boolean hasNext() throws RemoteException;
public boolean hasPrevious() throws RemoteException;
}
La interface home de este tipo de componentes sería del siguiente modo:
package interfaces;
import javax.ejb.*;
import java.rmi.RemoteException;
public interface IteradorRemotoHome extends EJBHome {
public IteradorRemoto create(java.util.Collection col)
throws CreateException, RemoteException;
}
58
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
La implementación del componente que implementa un iterador remoto genérico sería la
siguiente:
package ejb;
import javax.ejb.*;
import java.util.*;
public class IteradorRemotoBean implements SessionBean {
// Parte I: Implementación del Iterador
// Guarda la colección y un iterador para recorrerla
private Collection col;
private ListIterator it;
// Se asume que los objetos de la colección son de
//tipo EntityValueObject
public ValueObject next() {
return ((EntityValueObject) it.next()).getVO();
}
public ValueObject previous() {
return ((EntityValueObject) it.previous()).getVO();
}
public Collection next(int n) {
ArrayList lista = new ArrayList(n);
for (int i = 0; i < n; i++) {
if (it.hasNext()) {
lista.add(((EntityValueObject)it.next()).getVO());
} else
break;
}
return lista;
}
public Collection previous (int n) {
ArrayList lista = new ArrayList(n);
for (int i = 0; i < n; i++) {
if (it.hasPrevious()) {
lista.add(
((EntityValueObject)it.previous()).getVO());
} else
break;
}
return lista;
}
public boolean hasNext() {
return it.hasNext();
}
public boolean hasPrevious() {
return it.hasPrevious();
}
// Parte II: Interface Home
public void ejbCreate (Collection col) {
this.col = col;
// Crea un ListIterator para facilitar el recorrido
this.it = new ArrayList(col).listIterator();
}
59
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
// Parte III: Métodos de Retrollamada
private SessionContext context;
public void setSessionContext(SessionContext context) {
context = context;
}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
Finalmente, sólo nos falta configurar el nuevo componente en el fichero “ejb-jar.xml”:
<session>
<ejb-name>IteradorRemoto</ejb-name>
<home>interfaces.IteradorRemotoHome</home>
<remote>interfaces.IteradorRemoto</remote>
<ejb-class>ejb.IteradorRemotoBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Container</transaction-type>
</session>
60
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Custom Value Objects.
Habitualmente los objetos de negocio suelen contener mucha información. Cada caso de uso e
incluso cada interfaz de ellos pueden requerir distinta información de un objeto de negocio,
por lo que puede llegar a ser ineficiente retornar toda la información, siendo necesario definir
numerosos value objects.
En otras ocasiones lo que interesa no es sólo un objeto de negocio, sino determinada
información de un conjunto de objetos relacionados. En nuestro ejemplo, cuando queremos
consultar un pedido también nos interesa información de sus líneas junto con la de los
productos.
Delegar la responsabilidad de construir estos custom value object a la clase bean del objeto de
negocio sería inadecuado, ya que nos llevaría a estar continuamente modificando, recompilando
y desplegando el objeto de negocio.
La mejor solución es que el controlador construya un VO según sus necesidades. En nuestro
ejemplo, el método getInfoPedido es el encargado de construir un objeto PedidoVO a partir de
la información del modelo. La clase PedidoVO estaría formada por una colección de VO
LineaPedidoVO:
package ejb;
import java.util.Collection;
public class PedidoVO implements ValueObject {
private Collection lineas;
private int total;
public Collection getLineasPedido() { return lineas; }
public void setLineasPedido(Collection lineas) { this.lineas = lineas; }
public int getTotal() { return total; }
public void setTotal(int total) { this.total = total; }
}
package ejb;
import java.util.Collection;
public class LineaPedidoVO implements ValueObject {
private ProductoVO producto;
private int cantidad;
public ProductoVO getProducto() { return producto; }
public void setProducto(ProductoVO producto) {this.producto = producto;}
public int getCantidad() { return cantidad; }
public void setCantidad (int cantidad) { this.cantidad = cantidad; }
}
61
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Clase Bean del Controlador.
// Implementación del Controlador del cdu "Solicitar Pedido”
package ejb;
import
import
import
import
javax.ejb.*;
java.util.*;
javax.naming.*;
javax.rmi.*;
public class GestorPedidoBean implements SessionBean {
// Parte I: Implementación del controlador
private InitialContext contexto;
private PedidoLocal pedido;
public IteradorRemoto getProductos() {
IteradorRemoto it = null;
try {
// Realiza la consulta para recuperar todos los productos
ProductoLocalHome productoHome = (ProductoLocalHome)
contexto.lookup("local/Producto");
Collection col = productoHome.findAll();
IteradorRemotoHome iteradorHome = (IteradorRemotoHome)
PortableRemoteObject.narrow(
contexto.lookup("IteradorRemoto"),
IteradorRemotoHome.class);
it = iteradorHome.create(col);
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
return it;
}
public void addProducto(String idProducto, int cantidad) {
try {
// Obtiene el producto representado por "idProducto"
ProductoLocalHome productoHome = (ProductoLocalHome)
contexto.lookup("local/Producto");
ProductoLocal producto =
productoHome.findByPrimaryKey(idProducto);
// Añade la línea de pedido al pedido actual
pedido.addLineaPedido(producto, cantidad);
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
}
62
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
public void removeProducto(String idProducto) {
try {
// Obtiene el producto representado por "idProducto"
ProductoLocalHome productoHome = (ProductoLocalHome)
contexto.lookup("local/Producto");
ProductoLocal producto =
productoHome.findByPrimaryKey(idProducto);
// Elimina la línea de pedido
pedido.removeLineaPedido(producto);
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
}
public void updateProductoCantidad(String idProducto, int cantidad) {
try {
// Obtiene el producto representado por "idProducto"
ProductoLocalHome productoHome = (ProductoLocalHome)
contexto.lookup("local/Producto");
ProductoLocal producto =
productoHome.findByPrimaryKey(idProducto);
// Actualiza la información de la linea de pedido
pedido.updateLineaPedido(producto, cantidad);
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
}
// Confirma el pedido actual y crea un nuevo pedido
public void confirmarPedido() {
pedido.setFinalizado(true);
try {
PedidoLocalHome pedidoHome = (PedidoLocalHome)
contexto.lookup("local/Pedido");
pedido = pedidoHome.create(pedido.getCliente());
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
}
public PedidoVO getInfoPedido() {
// Crea un "Custom Value Object" Pedido para este cdu
PedidoVO pedidoVO = new PedidoVO();
// Itera sobre las líneas de pedido
Iterator it = pedido.getLineasPedido().iterator();
Collection col = new ArrayList();
while (it.hasNext()) {
// Construye LineaPedidoVO
LineaPedidoLocal lp = (LineaPedidoLocal) it.next();
LineaPedidoVO lpVO = new LineaPedidoVO();
lpVO.setProducto((ProductoVO)lp.getProducto().getVO());
lpVO.setCantidad(lp.getCantidad());
// Construye la coleccion de LineaPedidoVO
col.add(lpVO);
}
pedidoVO.setLineasPedido(col);
pedidoVO.setTotal(pedido.getTotal());
return pedidoVO;
}
63
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
// Parte II: Interface Home
public void ejbCreate(String idCliente) throws CreateException {
// Crea el contexto inicial JNDI
try {
this.contexto = new InitialContext();
// Obtiene el objeto de negocio asociado a "idCliente"
ClienteLocalEJBHome clienteHome = (ClienteLocalEJBHome)
contexto.lookup("local/ClienteCMP2");
ClienteLocalEJB cliente =
clienteHome.findByPrimaryKey(idCliente);
// Crea el pedido actual
PedidoLocalHome pedidoHome = (PedidoLocalHome)
contexto.lookup("local/Pedido");
pedido = pedidoHome.create(cliente);
} catch (Exception e) {
throw new CreateException (e.getMessage());
}
}
// Parte III: Métodos de retrollamada
private SessionContext context;
public void setSessionContext(SessionContext context) {
context = context;
}
public void ejbRemove() {
// Si el pedido no ha sido confirmado por el cliente y finaliza
// el cdu lo eliminamos
try {
if (!pedido.getFinalizado())
pedido.remove();
} catch (Exception e) {
throw new EJBException (e.getMessage());
}
}
public void ejbActivate() { }
public void ejbPassivate() { }
}
Configuración del Controlador.
Añadimos al fichero de configuración “ejb-jar.xml” la metainformación del controlador:
<session>
<ejb-name>GestorPedido</ejb-name>
<home>interfaces.GestorPedidoHome</home>
<remote>interfaces.GestorPedido</remote>
<ejb-class>ejb.GestorPedidoBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Container</transaction-type>
</session>
Desplegamos el componente y realizamos las pruebas de unidad al controlador: GestorPedido.
64
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
19. Transacciones.
Los componentes EJB entidad (objetos de negocio) son transaccionales. Esto quiere decir
que la ejecución de cualquiera de sus métodos está monitorizada por una transacción de objetos
distribuidos que garantiza las propiedades ACID.
Los componentes de tipo sesión también pueden ser transaccionales si indican en el fichero de
configuración que las transacciones serán controladas por el contenedor: etiqueta transactiontype con valor “Container”.
Para la ejecución de un caso de uso el comportamiento transaccional que nos interesa es el
siguiente: la colaboración que arranca de cada ejecución de una operación del controlador debe
ser monitorizada por la misma transacción distribuida. Si la llamada remota contiene una
transacción distribuida, utilizará ésta para ejecutar la operación. Si no es así, creará una
transacción para la ejecución de la colaboración. El modelo transaccional que ofrece este
comportamiento se denomina Required.
Indicamos en el fichero de configuración (ejb-jar.xml), después de la etiqueta relationships,
que todos los componentes implicados en el caso de uso han de utilizar el modelo transaccional
Required:
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>ClienteCMP2</ejb-name>
<method-name> * </method-name>
</method>
<method>
<ejb-name>Producto</ejb-name>
<method-name> * </method-name>
</method>
<method>
<ejb-name>LineaPedido</ejb-name>
<method-name> * </method-name>
</method>
<method>
<ejb-name>Pedido</ejb-name>
<method-name> * </method-name>
</method>
<method>
<ejb-name>GestorPedido</ejb-name>
<method-name> * </method-name>
</method>
<method>
<ejb-name>IteradorRemoto</ejb-name>
<method-name> * </method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
Si la transacción aborta por cualquier motivo, el cliente que ejecuta la operación del caso de uso
recibe una excepción RemoteException con la notificación del error.
65
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
20. Acceso al modelo desde la capa de presentación.
Uno de los principios básicos para el desarrollo de una aplicación empresarial es desacoplar la
capa de presentación de la tecnología que implementa el modelo. Es decir, la capa de
presentación no debe conocer que el modelo está desarrollado en EJB. Este principio favorece el
mantenimento y desarrollo de la aplicación.
Podemos lograr este propósito utilizando el patrón Business Delegate. Este patrón propone que
la capa de presentación se programe hacia la interfaz y no hacia la implementación. En nuestro
caso de uso, la capa de presentación colabora con el controlador GestorPedido y con el
IteradorRemoto.
Para el primero definiremos la interface Java GestorPedidoDelegate con las operaciones
identificadas en la fase de análisis:
package interfaces;
public interface GestorPedidoDelegate {
public IteradorRemotoDelegate getProductos() throws Exception;
public void addProducto(String idProducto, int cantidad)
throws Exception;
public void removeProducto(String idProducto) throws Exception;
public void updateProductoCantidad(String idProducto, int cantidad)
throws Exception;
public void confirmarPedido() throws Exception;
public PedidoVO getInfoPedido() throws Exception;
}
Las operaciones de esta interface lanzan potencialmente la excepción genérica Exception, que
es supertipo de excepciones más específicas RemoteException de EJB y RMI, y DAOException
del acceso DAO.
66
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Implementamos la interface haciendo uso de la tecnología con la que trabajemos. En nuestro
caso es EJB. Esta implementación delegara todas las operaciones en el controlador EJB:
package ejb;
import interfaces.*;
public class GestorPedidoDelegateEJB implements GestorPedidoDelegate {
GestorPedido gestorEJB;
public GestorPedidoDelegateEJB (String cliente) {
GestorPedidoHome gestorHome = (GestorPedidoHome)
ServiceLocator.getInstance().getEJBHome(
"GestorPedido", GestorPedidoHome.class);
try {
gestorEJB = gestorHome.create(cliente);
} catch (Exception e) {
e.printStackTrace();
}
}
public IteradorRemotoDelegate getProductos() throws Exception {
return new IteradorRemotoDelegateEJB(gestorEJB.getProductos());
}
public void addProducto(String idProducto, int cantidad)throws Exception {
gestorEJB.addProducto(idProducto, cantidad);
}
public void removeProducto(String idProducto) throws Exception {
gestorEJB.removeProducto(idProducto);
}
public void updateProductoCantidad(String idProducto, int cantidad)
throws Exception {
gestorEJB.updateProductoCantidad(idProducto, cantidad);
}
public void confirmarPedido() throws Exception {
gestorEJB.confirmarPedido();
}
public PedidoVO getInfoPedido() throws Exception {
return gestorEJB.getInfoPedido();
}
}
Podemos observar que la implementación de este delegate tiene un constructor que toma como
parámetro el identificador del cliente para el que se va a gestionar el pedido. Dentro de este
constructor se obtiene un home EJB de controlador GestorPedido haciendo uso de un
ServiceLocator.
La clase ServiceLocator es un singleton que actúa como único punto de acceso a todos los
servicios remotos. En nuestro caso, entendemos como servicios remotos los objetos home de
los controladores EJB. Por ello esta clase contiene el método getEJBHome que devuelve el
objeto home de un componente EJB a partir su nombre JNDI y del tipo al que se hará el narrow
portable RMI.
El uso de un ServiceLocator tiene como ventaja la centralización en un solo objeto de todo el
trabajo relativo al acceso a servicios remotos, pudiendo mantener una caché de referencias a
estos servicios, que mejorará el rendimiento de la capa de presentación. Otros servicios que
podría proporcionar esta clase serían factorías de objetos distribuidos CORBA, factorías de
colas de mensajes JMS, etc.
67
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
package ejb;
import interfaces.*;
import javax.naming.*;
import javax.ejb.EJBHome;
import javax.rmi.PortableRemoteObject;
public class ServiceLocator {
// Singleton
private static ServiceLocator instancia = null;
public static ServiceLocator getInstance() {
if (instancia == null)
instancia = new ServiceLocator();
return instancia;
}
private InitialContext contexto;
private java.util.HashMap tabla = new java.util.HashMap();
private ServiceLocator () {
try {
contexto = new InitialContext();
} catch (Exception e) {
e.printStackTrace();
}
}
// Obtiene un home remoto EJB manteniendo una caché de referencias
public EJBHome getEJBHome(String nombre, Class clase) {
EJBHome factoria = (EJBHome) tabla.get(nombre);
if (factoria == null) {
try {
Object obj = contexto.lookup(nombre);
factoria = (EJBHome) PortableRemoteObject.narrow(obj, clase);
} catch (Exception e) {
e.printStackTrace();
}
tabla.put(nombre, factoria);
}
return factoria;
}
}
A continuación definimos un delegate para el iterador remoto:
package interfaces;
import java.util.Collection;
public interface IteradorRemotoDelegate
public
public
public
public
public
public
{
ValueObject next() throws Exception;
ValueObject previous() throws Exception;
Collection next(int n) throws Exception;
Collection previous (int n) throws Exception;
boolean hasNext() throws Exception;
boolean hasPrevious() throws Exception;
}
68
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Seguidamente proporcionamos una implementación EJB del delegate iterador utilizando
EJB.
package ejb;
import interfaces.* ;
import java.util.Collection;
public class IteradorRemotoDelegateEJB
implements IteradorRemotoDelegate {
private IteradorRemoto it;
public IteradorRemotoDelegateEJB(IteradorRemoto it) {
this.it = it;
}
public ValueObject next() throws Exception {
return it.next();
}
public ValueObject previous() throws Exception {
return it.previous();
}
public Collection next(int n) throws Exception {
return it.next(n);
}
public Collection previous (int n) throws Exception {
return it.previous(n);
}
public boolean hasNext() throws Exception {
return it.hasNext();
}
public boolean hasPrevious() throws Exception {
return it.hasPrevious();
}
}
Finalmente, definimos un singleton Factoría de Delegates que contendrá un método factoría
por cada tipo de delegate. De este modo, la capa de presentación no conocerá la implementación
concreta que se está usando.
package ejb;
public class FactoriaDelegates {
// Singleton
private static FactoriaDelegates instancia = null;
public static FactoriaDelegates getInstance() {
if (instancia == null)
instancia = new FactoriaDelegates();
return instancia;
}
private FactoriaDelegates () {}
public GestorPedidoDelegate getGestorPedidoDelegate(String clienteId) {
// Devuelve un controlador EJB
return new GestorPedidoDelegateEJB(clienteId);
}
}
69
Desarrollo de Aplicaciones Distribuidas
Curso 2005/2006
Desde la capa de presentación obtendremos el controlador de caso de uso con la siguiente
llamada:
GestorPedidoDelegate gestorPedido =
FactoriaDelegates.getInstance().getGestorPedidoDelegate("alumno");
IteradorRemotoDelegate it = gestorPedido.getProductos();
...
21. Empaquetado de la aplicación enterprise.
Una vez finalizado el desarrollo de la aplicación podemos empaquetar la aplicación de un modo
portable entre servidores de aplicaciones J2EE. El fichero que contendrá la aplicación enterprise
será de tipo EAR. Creamos este fichero con la ejecución del siguiente comando desde el
directorio default-ear:
> jar cvf dad.ear *
Este fichero contiene una aplicación enterprise. Como hemos estudiado en apartados anteriores,
una aplicación enterprise está constituida por una capa de presentación implementada por una
aplicación web, y una capa de negocio implementada en EJB. En esta práctica sólo hemos
desarrollado la capa de negocio. Por lo tanto, estrictamente la aplicación dad.ear no es una
aplicación enterprise completa J2EE. Por último, debemos evitar incluir el código fuente dentro
del fichero dad.ear.
22. Aplicación Cliente de un servidor de aplicaciones J2EE.
Una aplicación cliente de un servidor de aplicaciones EJB (un contenedor de servlets) sólo
necesita para su compilación y ejecución las clases que constituyen el paquete javax.ejb, las
interfaces remotas con las que interactúan los delegates, el fichero de configuración de JNDI y
las clases VO.
En nuestra aplicación, la capa de presentación sólo interactúa con los objetos remotos
GestorPedido e IteradorRemoto. Por lo tanto, sólo necesitaría estas dos interfaces compiladas.
Por último, sólo son necesarias las clases VO: ProductoVO, PedidoVO y LineaPedidoVO.
Ejercicio Alternativo.
El ejercicio alternativo de esta práctica lo constituyen los apartados 14 al 22.
70
Descargar