El contenedor aisla al bean enterprise de accesos directos por parte de aplicaciones cliente. Cuando una aplicación cliente invoca un método remoto de un bean enterprise, el contenedor primero intercepta la llamada para asegurar que la persistencia, las transaciones, y la seguridad son aplicadas apropiadamente a cada operación que el cliente realiza en el Bean. El contenedor maneja estos aspectos de forma automática, por eso el desarrollador no tiene que escribir este tipo de lógica dentro del propio código del bean. El desarrollador de beans enterprise puede enfocarse en encapsular la reglas del negocio, mientras el contenedor se ocupa de todo lo demás.
Los contenedores manejan muchos Beans simultáneamente de igual forma que un Java WebServer maneja muchos servlets. Para reducir el consumo de memoria y de proceso, los contenedores almacenan los recursos y manejan los ciclos de vida de todos los beans de forma muy cuidadosa. Cuando un Bean no está siendo utilizado, un contenedor lo situará en un almacén para ser reutilizado por otros clientes, o posiblemente lo sacará de la memoria y sólo lo traerá de vuelta cuando sea necesario. Como las aplicaciones cliente no tienen acceso directo a los beans --el contenedor trata con el cliente y el bean-- la aplicación cliente se despreocupa completamente de las actividades de control de recursos del contenedor. Por ejemplo, un bean que no está en uso, podría ser eliminado de la memoria del servidor, mientras que su referencia remota en el cliente permanece intacta. Cuando un cliente invoca a un método de la referencia remota, el contenedor simplemente re-activa el bean para servir la petición. La aplicación cliente se despreocupa de todo el proceso.
Un bean enterprise depende del contenedor para todo lo que necesite. Si un bean enterprise necesita acceder a un conexión JDBC o a otro bean enterprise, lo hace a través del contenedor; si un bean enterprise necesita acceder a la identidad de su llamador, obtiene una referencia a sí mismo, o accede a las propiedades a través e su contenedor. El bean enterprise interactúa con su contenedor a través de uno de estos tres mecanismos:
La especificación EJB define un contrato bean-contenedor, que incluye los mecanismos (retrollamadas, EJBContext, JNDI ENC) descritos arriba así como un estricto conjunto de reglas que describe cómo se comportarán los beans enterprise y sus contenedores en tiempo de ejecución, como se comprobarán los accesos de seguridad, cómo se manejarán las transaciones, cómo se aplicará la persistencia, etc. El contrato bean-contenedor está diseñado para hacer portables los beans enterprise entre contenedores EJB para que los beans enterprise puedan ser desarrollados una sóla vez y puedan ejecutarse en cualquier contenedor EJB. Los vendedores como BEA, IBM, y GemStone venden aplicaciones servidores que incluyen contenedores EJB. Idealmente, cualquier bean enterprise que cumpla la especificación debería poder ejecutarse en cualquier contenedor compatible EJB.
La portabilidad es el principal valor que EJB pone encima de la mesa. La portabilidad asegura que un bean desarrollado para un contenedor puede migrarse a otro si ese otro ofrece mejor rendimiento, características, o ahorros. Portabilidad también significa que la destreza del desarrollador de beans puede influenciarse a través de varios contenedores EJB, proporcionando mejores oporturnidades para las organizaciones y los desarrolladores.
Además de la portabilidad, la simplicidad del modelo de programación EJB hace que este sea muy valioso. Como el contenedor tiene cuidado de manejar las complejas tareas como la seguridad, las transaciones, la persistencia, la concurrencia y el control de recursos, el desarrollador de beans es libre para enfocar su atención en las reglas del negocio y en un modelo de programación muy sencillo. Esto quiere decir que los beans pueden desarrollarse rápidamente sin necesidad de profundos conocimientos en objetos distribuidos, transaciones y otros sistemas enterprise.
Los beans enterprise viven en un contenedor EJB y son accedidos por aplicaciones clientes a través de la red usando sus interfaces remoto y home. Estos interfaces exponen las capacidades del bean y proporcionan todos los métodos necesarios para crear, actualizar, borrar e interactuar con el bean. Un bean es un componente del lado del servidor que representa un concepto de negocio como un Cliente o un Dependiente de Hotel.
El interface home representa los métodos del ciclo-de-vida del componente (crear, destruir, encontrar) mientras que el interface remoto representa los métodos de negocio del bean. Los interfaces remoto y home extienden los interfaces javax.ejb.EJBObject y javax.ejb.EJBHome respectivamente. Estos tipos de interfaces EJB definen un conjunto estándard de métodos de utilidad y proporcionan tipos base comunes para todos los interfaces remotos y home.
Los clientes usan el interface home del bean para obtener referencias al interface remoto del bean. El interface remoto define los métodos del negocio como métodos accesores o modificadores para cambiar el nombre del cliente, o métodos de negocio que realizan tareas como usar el bean HotelClerk para reservar una habitación del hotel. Abajo tenemos un ejemplo de como un bean Customer podría ser accedido por una aplicación cliente. En este caso el interface home es el tipo CustomerHome y el interface remoto es el tipo Customer:
CustomerHome home = // ... obtain a reference that
// implements the home interface.
// Use the home interface to create a
// new instance of the Customer bean.
Customer customer = home.create(customerID);
// using a business method on the Customer.
customer.setName(someName);
El interface remoto define los métodos de negocio del bean; los métodos que son específicos para el concepto de negocio que representa. Los interfaces remotos son subclases del interface javax.ejb.EJBObject que es una subclase del interface java.rmi.Remote. La importancia del árbol de herencias de los interfaces remotos se explica más adelante. Por ahora, nos enfocaremos en los métodos del negocio y su significado. Abajo tenemos la definición de un interface remoto para un bean Customer:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer extends EJBObject {
public Name getName()
throws RemoteException;
public void setName(Name name)
throws RemoteException;
public Address getAddress()
throws RemoteException;
public void setAddress(Address address)
throws RemoteException;
}
El interface remoto define métodos accesores y modificadores para leer y actualizar información sobre un concepto de negocio. Esto es típico de un tipo de beans llamado bean de entidad, que representan un objeto de negocio persistente; objetos de negocio cuyos datos se almacenan en una base de datos. Los beans de entidad representan datos del negocio en una base de datos y añaden comportamiento específico para esos datos.
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface HotelClerk
extends EJBObject {
public void reserveRoom(Customer cust,
RoomInfo ri,
Date from, Date to)
throws RemoteException;
public RoomInfo availableRooms(
Location loc, Date from, Date to)
throws RemoteException;
}
Los métodos de negocio definidos en el interface remoto HotelClerk representan procesos en vez de simples accesores. El Bean HotelClerk actúa como un agente en el sentido en que realiza tareas en beneficio del usuario, pero no persiste él mismo en la base de datos. Nosotros no necesitamos información sobre el HotelClerk, necesitamos que el dependiente del hotel realice tareas por nosotros. Este es un comportamiento típico de un bean de sesión.
Hay dos tipos básicos de beans enterprise: beans de entidad, que representan datos en una base de datos, y beans de sesión, que representan procesos o actúan como agentes que realizan tareas. Cuando construyamos aplicaciones EJB crearemos muchos beans enterprise, cada uno representando un concepto diferente del negocio. Cada concepto del negocio será manifestado como un bean de entidad o de sesión. Elegiremos el tipo de bean basándonos en lo que se espera que haga el bean.
import javax.ejb.EntityBean;
public class CustomerBean
implements EntityBean {
Address myAddress;
Name myName;
CreditCard myCreditCard;
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
public Address getAddress() {
return myAddress;
}
public void setAddress(Address address) {
myAddress = address;
}
...
}
CustomerBean es la implementación de la clase. Contiene los datos y proporciona métodos accesores y otros métodos del negocio. Como bean de entidad, el CustomerBean proporciona un objeto view de los datos del cliente. En lugar de escribir la lógica de acceso a la base de datos en una aplicación, la aplicación simplemente puede usar el interface remoto hacia el bean Customer para acceder a los datos del cliente. Los beans de entidad implementan el tipo javax.ejb.EntityBean que define un conjunto de métodos de notificación que el bean usa para interactúar con su contenedor.
import javax.ejb.SessionBean;
public class HotelClerkBean
implements SessionBean {
public void reserveRoom(
Customer cust, RoomInfo ri,
Date from, Date to) {
CreditCard card = cust.getCreditCard();
RoomHome roomHome =
// ... get home reference
Room room =
roomHome.findByPrimaryKey(ri.getID());
double amount = room.getPrice(from,to);
CreditServiceHome creditHome =
// ... get home reference
CreditService creditAgent =
creditHome.create();
creditAgent.verify(card, amount);
ReservationHome resHome =
// ... get home reference
Reservation reservation =
resHome.create(cust,room,from,to);
}
public RoomInfo[] availableRooms(
Location loc,
Date from,
Date to) {
// Make an SQL call to find
//available rooms
Connection con = // ... get database
//connection
Statement stmt = con.createStatement();
ResultSet results =
stmt.executeQuery("SELECT ...");
...
return roomInfoArray;
}
}
Podrías haber observado que las clases bean definidas arriba no implementan los interfaces remoto o home. EJB no requiere que la clase bean implemente estos interfaces; de hecho está desaconsejado que lo haga porque los tipos bases de los interfaces remoto y home (EJBObject y EJBHome) definen muchos otros métodos que son implementados automáticamente por el contenedor. Sin embargo, la clase bean no proporciona implementaciones para todos los métodos de negocio definidos en el interface remoto.
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.rmi.RemoteException;
public interface CustomerHome
extends EJBHome {
public Customer create(Integer
customerNumber)
throws RemoteException,
CreateException;
public Customer findByPrimaryKey(Integer
customerNumber)
throws RemoteException,
FinderException;
public Enumeration findByZipCode(int zipCode)
throws RemoteException,
FinderException;
}
El método create() se utiliza para crear una nueva entidad. Esto resultará en un nuevo registro de la base de datos. Un interface home podría tener muchos métodos create(). El número y tipo de los argumentos de cada create() se deja para el desarrollador del bean, pero el tipo de retorno debe ser el mismo tipo del interface remoto. En este caso, llamar a create() sobre el interface CustomerHome devolverá un ejemplar de Customer. Los métodos findByPrimaryKey() y findByZipCode() se usan para localizar ejemplares específicos del bean Customer. De nuevo, podremos definir tantos métodos de estos tipos como necesitemos.
CustomerHome home =
// Get a reference to the
//CustomerHome object
Customer customer =
home.create(new Integer(33));
Name name = new Name("Richard",
"Wayne", "Monson-Haefel");
customer.setName(name);
Enumeration enumOfCustomers =
home.findByZip(55410);
Customer customer2 =
home.findByPrimaryKey(
new Integer(33));
Name name2 = customer2.getName();
// output is "Richard Wayne
//Monson-Haefel"
System.out.println(name);
El interface javax.ejb.EJBHome también define otros métodos que CustomerBean hereda automáticamente, incluyendo un conjunto de métodos remove() que permite a la aplicación destruir ejemplares del bean.
Para hacer que un ejemplar de un objeto en un espacio de direccionamiento esté disponible para otros requiere un pequeño truco que involucra sockets de red. Para hacer que el truco funcione, envolvemos el ejemplar en un objeto especial llamado esqueleto (skeleton) que tiene una conexión de red con otro objeto especial llamado talón (stub). El stub implementa el interface remoto por eso se parece a un objeto de negocio. Pero el stub no contiene la lógica del negocio; contiene una conexión socket con el skeleton. Cada vez que se invoca un método de negocio sobre el interface remoto del stub, éste envía un mensaje de red al skeleton diciéndole qué método ha sido invocado. Cuando el skeleton recibe un mensaje de red desde el stub, identifica el método invocado y los argumentos, y luego invoca al método correspondiente del ejemplar actual. El ejemplar ejecuta el método de negocio y devuelve el resultado al skeleton, que lo envía de vuelta al stub. Este diagrama lo ilustra un poco:
El stub devuelve el resultado a la aplicación que invocó este método del interface remoto. Desde la perspectiva de la aplicación que usa el stub, es como si el stub no trabajara localmente. Realmente, el stub es sólo un objeto de red mudo que envía peticiones a través de la red al skeleton, que en invoca al método del ejemplar real. El ejemplar hace todo el trabajo, el stub y el skeleton sólo pasan la identidad del método y los argumentos de vuelta a través de la red.
En EJB, el skeleton para los interfaces remoto y home están implementados por el contenedor, no por la clase bean. Esto es para asegurar que cada método invocado sobre estos tipos de referencia por una aplicacion cliente son manejados primero por el contenedor y luego delegados al ejemplar del bean. El contenedor debe interceptar aquellas peticiones que vayan al bean para poder aplicar la persistencia (beans de entidad), las transaciones, y controlar los accesos automáticamente.
Los protocolos de objetos distribuidos definen el formado de mensajes de red enviados entre espacios de direccionamiento. Estos protocolos son bastantes complicados, pero afortunadamente no tenemos que preocuparnos de ellos, ya que son manejados automáticamente. La mayoría de los servidores EJB soportan "Java Remote Method Protocol" (JRMP) o el "Internet Inter-ORB Protocol" (IIOP) de CORBA. El programador de beans y aplicaciones sólo ve la clase del bean y su interface remoto, los detales de la comunicación de red están ocultos.
Con respecto al API EJB, al programador no le importa si el servidor EJB usa JRMP o IIOP -- el API es el mismo. La especificación EJB requiere que usemos una versión especializada del API Java RMI, cuando trabajos con un bean de forma remota. Java RMI es un API para acceder a objetos distribuidos y de alguna forma es un protocolo agnóstico -- de la misma forma que JDBC es una base de datos agnóstica. Por eso, un servidor EJB puede soportar JRMP o IIOP, pero el desarrollador de beans y aplicaciones siempre usa el mismo API Java RMI. Para que el servidor EJB pueda tener la opción de soportar IIOP, una versión especializada de Java RMI, llamada Java RMI-IIOP. Java RMI-IIOP usa IIOP como protocolo y el API Java RMI. Los servidores EJB no tienen que usar IIOP, pero tienen que respetar las restricciones Java RMI-IIOP, por eso EJB 1.1 usa las convenciones Java RMI-IIOP y los tipos especializados, pero el protocolo oculto puede no ser ninguno de ellos.
Hay dos tipos de beans de entidad: Persistencia Manejada por el Contenedor (CMP) y Persistencia Manejada por el Bean (BMP). Con CMP, el contenedor maneja la persistencia del bean de entidad. Las herramientas de los vendedores se usan para mapear los campos de entidad a la base de datos y no se escribe ningún código de acceso a las bases de datos en la clase del bean. Con BMP, el bean de entidad contiene código de acceso a la base de datos (normalmente JDBC) y es responsable de leer y escribir en la base de datos su propio estado. Las entidades BMP tienen mucha ayuda ya que el contenedor alertará al bean siempre que sea necesario hacer una actualización o leer su estado desde la base de datos. El contenedor también puede manejar cualquier bloqueo o transación, para que la base de datos se mantenga íntegra.
En esta sección, ampliaremos el CustomerBean desarrollado anteriormente para una completa definición de un bean de persistencia manejada por contenedor. En la siguiente sección de bean con persistencia manejada por el bean modificaremos el CustomerBean para que maneje su propia persistencia.
import javax.ejb.EntityBean;
public class CustomerBean
implements EntityBean {
int customerID;
Address myAddress;
Name myName;
CreditCard myCreditCard;
// CREATION METHODS
public Integer ejbCreate(Integer id) {
customerID = id.intValue();
return null;
}
public void ejbPostCreate(Integer id) {
}
public Customer ejbCreate(Integer id, Name name) {
myName = name;
return ejbCreate(id);
}
public void ejbPostCreate(Integer id, Name name) {
}
// BUSINESS METHODS
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
public Address getAddress() {
return myAddress;
}
public void setAddress(Address address) {
myAddress = address;
}
public CreditCard getCreditCard() {
return myCreditCard;
}
public void setCreditCard(CreditCard card) {
myCreditCard = card;
}
// CALLBACK METHODS
public void setEntityContext(EntityContext cntx) {
}
public void unsetEntityContext() {
}
public void ejbLoad() {
}
public void ejbStore() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
}
Este es un buen ejemplo de un bean de entidad CMP bastante simple. Observa que no hay lógica de acceso a base de datos en el bean. Esto es porque el vendedor del EJB proporciona herramientas para mapear los campos del CustomerBean a la base de datos. La clase CustomerBean, por ejemplo, podría ser mapeada a cualquier base de datos proporcionando los datos que contienen y que son muy similares a los campos del bean. En este caso lo campos de ejemplar del bean están comprendidos por un int y unos sencillos objetos dependientes (Name, Address, y CreditCard) con sus propios atributos. Abajo tenemos las definiciones de estos objetos dependientes:
// The Name class
public class Name
implements Serializable {
public String lastName,
firstName, middleName;
public Name(String lastName, String firstName,
String middleName) {
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
}
public Name() {}
}
// The Address class
public class Address
implements Serializable {
public String street,
city, state, zip;
public Address(String street,
String city,
String state,
String zip) {
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
}
public Address() {}
}
// The CreditCard class
public class CreditCard
implements Serializable {
public String number,
type, name;
public Date expDate;
public CreditCard(String
number, String type,
String name,
Date expDate) {
this.number = number;
this.type = type;
this.name = name;
this.expDate = expDate;
}
public CreditCard() {}
}
Estos campos se llaman campos manejados por contenedor porque el contenedor es el responsable de sincronizar su estado con la base de datos; el contenedor maneja los campos. Estos campos pueden ser cualquier tipo de dato primitivo o tipo serializable. Este caso usa tanto un tipo primitivo int (customerID) y objetos serializables (Address, Name, CreditCard). Para mapear los objetos dependientes a la base de datos necesitaríamos usar una herramienta de mapeado muy sofisticada. No todos los campos del bean son automáticamente campos manejados por el contenedor; algunos de ellos son sólo campos de ejemplar para el uso temporal del bean. Un desarrollador de beans distingue un campo manejado por conetenedor de un campo de ejemplar normal indicando los campos manejados por el contenedor en el descriptor de desarrollo.
Los campos manejados por contenedor deben tener sus tipos correspondientes (columnas en RDBMS) en la base de datos bien directamente o a través de mapeo Objeto-Relacional. El CustomerBean podría, por ejemplo, mapear una tabla CUSTOMER en la base de datos que tenga la siguiente definición:
CREATE TABLE CUSTOMER
{
id INTEGER PRIMARY KEY,
last_name CHAR(30),
first_name CHAR(20),
middle_name CHAR(20),
street CHAR(50),
city CHAR(20),
state CHAR(2),
zip CHAR(9),
credit_number CHAR(20),
credit_date DATE,
credit_name CHAR(20),
credit_type CHAR(10)
}
Con la persistencia controlada por contenedor, el vendedor debe tener alguna clase de herramienta propietaria que pueda mapear los datos manejados por el contenedor del bean a sus correspondientes columnas en la tabla específica, CUSTOMER en este caso.
Una vez que los campos del bean se han mapeado a la base de datos, y se ha desarrollado el bean CustomerBean, el contenedor manejará la creacción de registros, el borrado, la carga y la actualización de registros en la tabla CUSTOMER en respuesta a los métodos invocados en los interfaces home y remoto del bean Customer.
Un subconjunto (uno o más) de los campos manejados por el contenedor será identificado como la clave primaria del bean. Esta clave primaria es el índice o puntero a un único registro(s) en la base de datos que crea el estado del bean. En el caso de CustomerBean, el campo id es el campo clave primario y será usado para localizar los datos de los beans en la base de datos. Los campos clave de un campo sencillo primitivo se representa como su correspondiente objeto envoltura. La clave primaria del bean CustomerBean por ejemplo es un int primitivo en la clase bean pero los clientes del bean la manifestarán como del tipo java.lang.Integer. Las claves primarias que se componen de varios campos, llamadas claves primarias compuestas, serán representadas por un clase especial definida por el desarrollador del Bean.
public interface CustomerHome
extends javax.ejb.EJBHome {
public Customer create(
Integer customerNumber)
throws RemoteException,CreateException;
public Customer create(Integer customerNumber,
Name name)
throws RemoteException,CreateException;
public Customer findByPrimaryKey(Integer
customerNumber)
throws RemoteException, FinderException;
public Enumeration findByZipCode(int zipCode)
throws RemoteException, FinderException;
}
Abajo tenemos un ejemplo de cómo se usaría el interface home en una aplicación cliente para crear un nuevo cliente:
CustomerHome home =
// Get a reference to the
//CustomerHome object
Name name =
new Name("John", "W", "Smith");
Customer customer =
home.create(
new Integer(33), name);
Un interface home de un bean podría declarar cero o más métodos create(), cada uno de los cuales debe tener sus correspondientes métodos ejbCreate() y ejbPostCreate() en la clase del bean. Estos métodos de creacción son enlazados en tiempo de ejecución, para que cuando se llame a un método create() del interface home, el contenedor delegue la invocación a los correspondientes métodos ejbCreate() y ejbPostCreate() de la clase Bean.
Cuando se invoca al método create() de un interface home, el contenedor delega esta llamada al correspondiente método ejbCreate() en el bean. Estos métodos se usan para inicializar el estado del ejemplar antes de que el registro sea insertado en la base de datos. En este caso, inicializa los campos customerID y Name. Cuando el método ejbCreate() ha finalizado (devuelven null en CMP) el contenedor lee los campos manejados por el contenedor e inserta un nuevo registro en la tabla CUSTOMER indexado por la clave primaria, en este caso customerID que se mapea a la columna CUSOTMER.ID.
En EJB un bean de entidad no existe técnicamente hasta después de que sus datos hayan sido insertados en la base de datos, lo que ocurre durante el método ejbCreate(). Una vez que los datos han sido insertados, el bean de entidad existe y puede acceder a su propia clave primaria y a referencias remotas, lo que no es posible hasta que se complete el método ejbCreate() y que los datos estén en la base de datos. Si el bean necesita acceder a su propia clave primaria o a una referencia remota después de haber sido creado, pero antes de servir a cualquier método de negocio, puede hacerlo en el método ejbPostCreate(). Este método permite al bean realizar cualquier proceso post-creacción antes de empezar a servir peticiones de clientes. Por cada ejbCreate() debe haber un método ejbPostCreate() correspondiente (con los mismos argumentos).
Los métodos del interface home que empiezan con "find" son llamados métodos de búsqueda. Se usan para consultar al servidor EJB sobre beans de entidad específicos, basándose en el nombre del método y los argumentos pasados. Desafortunadamente, no hay un lenguaje de consultas estándard definido para los métodos de búsqueda, por eso cada vendedor implementará estos métodos de forma diferente. En los beans de entidad CMP, los métodos de búsqueda no están implementados con los métodos correspondientes en la clase del bean; los contenedores los implementan cuando el bean se desarrolla de una forma específica del vendedor. El desarrollador usará las herramientas específicas del vendedor para decirle al contenedor cómo se debería comportar un método de búsqueda particular. Algunos vendedores usaran herramientas de mapeo Objeto-Relacional para definir el comportamiento de un método de búsqueda mientras que otros sólo requeriran que el desarrollador introduzca el comando SQL apropiado.
Hay dos clases básicas de métodos de búsqueda: métodos de búsqueda uni-entidad y multi-entidad. Los métodos de búsqueda uni-entidad devuelven una referencia remota a un bean de entidad específico que corresponda con la búsqueda solicitada. Si no se encuentra un bean de entidad, el método lanza una excepción ObjectNotFoundException. Todo bean de entidad debe definir el método de búsqueda uni-entidad con el nombre del método findByPrimaryKey(), que toma el tipo de la clave primaria del bean como argumento.
Los métodos de búsqueda multi-entidad devuelven una colección (tipo Enumeration o Collection) de entidades que corresponden con la búsqueda solicitada. Si no se encuentran entidades, estos métodos devuelven una colección vacía. (Observa que una colección vacía no es lo mismo que una referencia nula).
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer
extends EJBObject {
public Name getName()
throws RemoteException;
public void setName(Name name)
throws RemoteException;
public Address getAddress()
throws RemoteException;
public void setAddress(
Address address)
throws RemoteException;
public CreditCard getCreditCard()
throws RemoteException;
public void setCreditCard(CreditCard card)
throws RemoteException;
}
Abajo tenemos un ejemplo de cómo una aplicación cliente podría usar el interface remoto para interactuar con un bean:
Customer customer = // ... obtain a remote //reference to the bean // get the customer's address Address addr = customer.getAddress(); // change the zip code addr.zip = "56777"; // update the customer's address customer.setAddress(addr);Los métodos de negocio en el interface remoto se delegan a los correspondientes métodos de negocio en el ejemplar del bean. En el bean Customer, los métodos de negocios son todos simples accesores y modificadores, pero podrían ser más complicados. En otras palabras, los métodos de negicio de una entidad no están limitados a leer y escribir datos, también pueden realizar tareas de cálculo.
Si, por ejemplo, los clientes tuvieran un programa de fidelidad que premiara el uso frecuente, podría haber un método para mejorar los premios en el programa, basado en una acumulación de estancias superiores a una noche:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer extends EJBObject {
public MembershipLevel addNights(int nights_stayed)
throws RemoteException;
public MembershipLevel upgradeMembership()
throws RemoteException;
public MembershipLevel getMembershipLevel()
throws RemoteException;
... Simple accessor business methods go here
}
Los métodos addNights() y upgradeMembership() son más sofisticados que los sencillos métodos accesores. Aplican reglas de negocio para cambiar el nivel del premio y van más allá de leer y escribir datos.
public interface javax.ejb.EntityBean {
public void setEntityContext();
public void unsetEntityContext();
public void ejbLoad();
public void ejbStore();
public void ejbActivate();
public void ejbPassivate();
public void ejbRemove();
}
El método setEntityContext() proporciona al bean un interface con el contenedor llamado el EntityContext. Este interface contiene métodos para obtener información sobre el contexto bajo el que opera el bean en un momento particular. El interface EntityContext se usa para acceder a información de seguridad sobre su llamador; para determinar el estado de la transación actual o para forzar a deshacer una transación; o para obtener una referencia al propio bean, su interface home o su clave primaria. Este interface sólo se configura una vez en la vida de un ejemplar de bean de entidad, por eso su referencia debería ponerse en uno de los campos de ejemplar del bean si la vamos a necesitar más tarde.
El bean Customer de arriba no usa EntityContext, pero podría hacerlo. Por ejemplo, podría usarlo para validad al llamador en un rol de seguridad particular. Abajo tenemos un ejemplo, donde se usa el EntityContext para validad que el llamador es un Manager, el único rol (indentidad de seguridad) que puede seleccionar el tipo de la tarjeta de crédito de un cliente para que sea una tarjeta mundial, no una tarjeta limitada. (A los clientes con esta tarjeta se le ofrecen servicios especiales).
import javax.ejb.EntityBean;
public class CustomerBean
implements EntityBean {
int customerID;
Address myAddress;
Name myName;
CreditCard myCreditCard;
EntityContext ejbContext;
// CALLBACK METHODS
public void setEntityContext(
EntityContext cntx) {
ejbContext = cntx
}
public void unsetEntityContext() {
ejbContext = null;
}
// BUSINESS METHODS
public void
setCreditCard(CreditCard card) {
if (card.type.equals("WorldWide"))
if (ejbContext.isCallerInRole(
"Manager"))
myCreditCard = card;
else
throw new SecurityException();
else
myCreditCard = card;
}
public CreditCard getCreditCard() {
return myCreditCard;
}
...
}
El método unsetEntityContext() se usa al final del ciclo de vida del bean -- antes de que el ejemplar sea sacado de la memoria -- para desrefenciar el EntityContext y realizar cualquier pequeña tarea de limpieza.
Los métodos ejbLoad() y ejbStore() en entidades CMP se invocan cuando el estado del bean de entidad está siendo sincronizado con la base de datos. El ejbLoad() se invoca justo después de que el contenedor haya refrescado los campos del bean manejados por contenedor con su estado desde la base de datos. El método ejbStore() se invoca justo antes de que el contenedor escriba los campos del bean manejados por contenedor en la base de datos. Estos métodos se usan para modificar datos que están siendo sincronizados. Los métodos podrían usarse, por ejemplo, para comprimir los datos antes de almacenarlos y descomprimirlos cuando son recuperados de la base de datos.
En el bean Customer los métodos ejbLoad() y ejbStore() podrían usarse para converitir objetos dependientes (Name, Address, CreditCard) a sencillos objetos String y tipos primitivos, si el contenedor EJB no es lo suficientemente sofisticado para mapear los objetos dependientes a la tabla CUSTOMER. Abajo tenemos un ejemplo de como se podría modificar el Bean:
import javax.ejb.EntityBean;
public class CustomerBean
implements EntityBean {
//container-managed fields
int customerID;
String lastName;
String firstName;
String middleName;
...
// not-container-managed fields
Name myName;
Address myAddress;
CreditCard myCreditCard;
// BUSINESS METHODS
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
...
public void ejbLoad() {
if (myName == null)
myName = new Name();
myName.lastName = lastName;
myName.firstName = firstName;
myName.middleName = middleName;
...
}
public void ejbStore() {
lastName = myName.lastName;
firstName = myName.firstName;
middleName = myName.middleName;
...
}
}
Los métodos ejbPassivate() y ejbActivate() se invocan sobre el bean justo antes de que el bean sea "pasivizado" o justo después de que se activado, respectivamente. "Pasivizado" en un bean de entidad significa que el ejemplar del bean ha sido des-asociado con su referencia remota para que el contenedor pueda eliminarlo de la memoria o reutilizarlo. Es una medida de conservación de recursos que emplea el contenedor para reducir el número de ejemplares en memoria. Un bean podría ser "pasivizado" si no ha sido utilizado durante un tiempo o como una operación normal realizada por el contenedor para maximizar la reutilización del los recursos. Algunos contenedores eliminarán los beans de la memoria, mientras que otros reutilizarán ejemplares para otros interfaces remotos más activos. Los métodos ejbPassivate() y ejbActivate() proporcionan al bean una notificación cuando está apunto de ser "pasivizado" (des-asociado con la referencia remota) o activado (asociado con un referencia remota).
En esta sección, modificaremos la clase CustomerBean para crear un bean con persistencia manejada por el Bean (BMP). Esta modificación no impactará en absoluto en los interfaces home o remoto. De hecho, no modificaremos directamente el CustomerBean original. En su lugar, lo cambiaremos a persistencia manejada por el bean extendiendo el bean y sobreescribiendo la clase para crear una entidad BMP. En la mayoría de los casos, no extenderemos el bean para hacerlo BMP, sólo implementaremos el bean como BMP directamente. Esta estrategia (extender el bean CMP) se hace por dos razones: permite al bean ser un bean CMP o BMP; y acorta significativamente la cantidad de código a mostrar. Abajo tenemos la definición de la clase BMP:
public class CustomerBean_BMP
extends CustomerBean {
public void ejbLoad() {
// override implementation
}
public void ejbStore() {
// override implementation
}
public void ejbCreate() {
// override implementation
}
public void ejbRemove() {
// override implementation
}
private Connection getConnection() {
// new helper method
}
}
Con beans BMP, los métodos ejbLoad() y ejbStore() los usa el contenedor de forma diferente a como lo hacía con un bean CMP. En BMP, estos métodos contienen código para leer los datos de la base de datos y para escribir los cambios en la base de datos, respectivamente. Estos métodos se llaman sobre el bean cuando el servidor EJB decide que es un buen momento para leer o escribir los datos.
El bean CustomerBean_BMP maneja su propia persistencia. En otras palabras, los métodos ejbLoad() y ejbStore() deben incluir lógica de acceso a base de datos para que el bean pueda cargar y almacenar sus datos cuando el contenedor EJB se lo diga. El contenedor ejecutará automátiamente los métodos ejbLoad() y ejbStore() cuando crea apropiado.
El método ejbLoad() se invoca normalmente al principio de una transación, justo antes de que el contenedor delege un método de negocio del bean. El código mostrado abajo muestra cómo implementar el método ejbLoad() usando JDBC:
import java.sql.Connection;
public class CustomerBean_BMP
extends CustomerBean {
public void ejbLoad() {
Connection con;
try {
Integer primaryKey =
(Integer)ejbContext.getPrimaryKey();
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT *
FROM Customer " +
" WHERE customerID = " +
primaryKey.intValue());
ResultSet results = sqlStmt.executeQuery();
if (results.next()) {
// get the name information
//from the customer table
myName = new Name();
myName.first = results.getString("
FIRST_NAME");
myName.last = results.getString("
LAST_NAME");
myName.middle = results.getString("
MIDDLE_NAME");
// get the address information from
//the customer table
myAddress = new Address();
myAddress.street =
results.getString("STREET");
myAddress.city =
results.getString("CITY");
myAddress.state =
results.getString("STATE");
myAddress.zip = results.getInt("ZIP");
// get the credit card information
//from the customer table
myCreditCard = new CreditCard();
myCreditCard.number =
results.getString("CREDIT_NUMBER");
myCreditCard.expDate =
results.getString("CREDIT_DATE");
myCreditCard.type =
results.getString("CREDIT_TYPE");
myAddress.name =
results.getInt("CREDIT_NAME");
}
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
}
El método ejbLoad(), usa la referencia ejbContext() hacia el EntityContext del bean para obtener la clave primaria del ejemplar. Esto asegura que usamos el índice correcto en la base de datos. Obviamente, el CustomerBean_BMP necesitará usar los métodos heredados setEntityContext() y unsetEntityContext().
El método ejbStore() es invocado sobre el bean, al final de la transación, justo antes de que el contenedor intente enviar todos los cambios a la base de datos:
import java.sql.Connection;
public class CustomerBean_BMP
extends CustomerBean {
public void ejbLoad() {
... read data from database
}
public void ejbStore() {
Connection con;
try {
Integer primaryKey =
(Integer)ejbContext.getPrimaryKey();
con = this.getConnection();
PreparedStatement sqlPrep =
con.prepareStatement(
"UPDATE customer set " +
"last_name = ?, first_name = ?,
middle_name = ?, " +
"street = ?, city = ?, state = ?,
zip = ?, " +
"card_number = ?, card_date = ?, " +
"card_name = ?, card_name = ?, " +
"WHERE id = ?"
);
sqlPrep.setString(1,myName.last);
sqlPrep.setString(2,myName.first);
sqlPrep.setString(3,myName.middle);
sqlPrep.setString(4,myAddress.street);
sqlPrep.setString(5,myAddress.city);
sqlPrep.setString(6,myAddress.state);
sqlPrep.setString(7,myAddress.zip);
sqlPrep.setInt(8, myCreditCard.number);
sqlPrep.setString(9, myCreditCard.expDate);
sqlPrep.setString(10, myCreditCard.type);
sqlPrep.setString(11, myCreditCard.name);
sqlPrep.setInt(12,primaryKey.intValue());
sqlPrep.executeUpdate();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
}
En ambos métodos ejbLoad() y ejbStore() el bean sincroniza su propio estado con la base de datos usando JDBC. Si estudiamos el código cuidadosamente podríamos haber observado que el bean otiene sus conexiones a la base de datos desde una invocación misteriosa al método this.getConnection(). Un método todavía no implementado. El método getConnection() no es un método estándard EJB, sólo una ayuda privada implementada para ocultar los mecanismos de obtención de conexiones a bases de datos. Abajo tenemos la definicón del método getConnection():
import java.sql.Connection;
public class CustomerBean_BMP
extends CustomerBean {
public void ejbLoad() {
... read data from database
}
public void ejbStore() {
... write data to database
}
private Connection getConnection()
throws SQLException {
InitialContext jndiContext =
new InitialContext();
DataSource source = (DataSource)
jndiContext.lookup("
java:comp/env/jdbc/myDatabase");
return source.getConnection();
}
}
Las conexiones a bases de datos se obtienen desde el contenedor usando un contexto JNDI por defecto llamado "JNDI Environment Naming Context" (ENC). El ENC proporciona acceso a almacenes de conexiones JDBC transacionales a través de la factoría de conexiones estándard, del tipo javax.sql.DataSource.
En BMP, los métodos ejbLoad() y ejbStore() los invoca el contenedor para sincronizar el ejemplar del bean con los datos en la base de datos. Para insertar y eliminar entidades en la base de datos, se implementan los métodos ejbCreate() y ejbRemove() con una lógica de acceso a bases de datos muy similar. Los métodos ejbCreate() se implementan para insertar un nuevo registro en la base de datos y los métodos ejbRemove() para eliminar los datos de una entidad de la base de datos. Estos métodos de una entidad son invocados por el contenedor en respuesta a invocaciones a sus correspondientes métodos en los interface home y remoto. Abajo podemos ver las implementaciones de estos métodos:
public void ejbCreate(Integer id) {
this.customerID = id.intValue();
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("INSERT INTO
customer id VALUES (" +
customerID + ")");
sqlStmt.executeUpdate();
return id;
}
catch(SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
public void ejbRemove() {
Integer primaryKey =
(Integer)ejbContext.getPrimaryKey();
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("DELETE FROM
customer WHERE id = "
primaryKey.intValue());
sqlStmt.executeUpdate();
}
catch(SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
En BMP, la clase bean es responsable de implementar los métodos de búsqueda definidos en el interface home. Por cada método de búsqueda definido en el interface home debe haber el correspondiente método ejbFind() en la clase bean. Estos métodos localizan los registros del bean apropiado en la base de datos y devuelve sus claves primarias al contenedor. El contenedor convierte la clave primara en referencias a beans y los devuelve al cliente. Abajo tenemos un ejemplo de implementación del método ejbFindByPrimaryKey() en la clase CustomerBean_BMP, que corresponde al findByPrimaryKey() definido en el interface CustomerHome:
public Integer ejbFindByPrimaryKey(
Integer primaryKey)
throws ObjectNotFoundException {
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT *
FROM Customer " +
" WHERE customerID = " +
primaryKey.intValue());
ResultSet results = sqlStmt.executeQuery();
if (results.next())
return primaryKey;
else
throw ObjectNotFoundException();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
Los métodos de búsqueda de entidades simples como el de arriba devuelven una sóla clave primara y lanza una ObjectNotFoundException si no se localiza un registro correspondiente. Los métodos de búsqueda multi-entidad devuelven una colección (java.util.Enumeration o java.util.Collection) de claves primarias. El contenedor convierte la colección de claves primarias en una colección de referencias remotas, que son devueltas al cliente. Abajo tenemos un ejemplo de cómo el método ejbFindByZipCode() multi-entidad, que corresponde al método findByZipCode() definido en el interface CustomerHome, sería implementado en la clase CustomerBean_BMP:
public Enumeration ejbFindByZipCode(
int zipCode) {
Connection con;
try {
con = this.getConnection();
Statement sqlStmt =
con.createStatement("SELECT id
FROM Customer " +
" WHERE zip = " +zipCode);
ResultSet results =
sqlStmt.executeQuery();
Vector keys = new Vector();
while(results.next()){
int id = results.getInt("id");
keys.addElement(new Integer(id));
}
return keys.elements();
}
catch (SQLException sqle) {
throw new EJBException(sqle);
}
finally {
if (con!=null)
con.close();
}
}
Si no se encuentran registros correspondientes, se devuelve una colección vacía al contenedor, que devuelve una colección vacía al cliente.
Con la implementación de todos estos métodos y unos pocos cambios menores en el descriptor de desarrollo del bean, el CustomerBean_BMP esta listo para ser desplegado como una entidad BMP.
Hay dos clases básicas de beans de sesión: sin estado y con estado. Los beans de sesión sin estado configuran métodos de negocio que se comportan como procedimientos; operan sólo sobre los argumentos que se les pasan. Los bean sin estado son temporales; no mantienen el estado del negocio entre llamadas. Los beans de sesión con estado encapsulan la lógica del negocio y el estado específico de un cliente. Los beans con estado si que mantienen es estado de negocio entre llamadas al método, las mentienen en memoria y no es persistente.
Un ejemplo de bean de sesión sin estado es un bean CreditService, que representa un servicio de crédito que puede validar y procesar cargos en tarjetas de crédito. Una cadena de Hoteles podría desarrollar un bean CreditService para encapsular el proceso de verificación del número de una tarjeta de crédito, hacer un cargo, y grabar el cargo en la base de datos para propósitos de contabilidad. Abajo tenemos el interface home y remoto para el bean CreditService:
// remote interface
public interface CreditService
extends javax.ejb.EJBObject {
public void verify(CreditCard
card, double amount)
throws RemoteException,
CreditServiceException;
public void charge(CreditCard
card, double amount)
throws RemoteException,
CreditServiceException;
}
// home interface
public interface CreditServiceHome
extends java.ejb.EJBHome {
public CreditService create()
throws RemoteException,
CreateException;
}
El interface remoto, CreditService, define dos métodos, verify() y charge()
que se usan para verificar y realizar cargos en tarjetas de crédito. El Hotel podría usar el método verify() para hacer una reserva, pero no cargar al cliente. El método charge() se usaría para cargar al cliente una habitación. El interface home, CreditServiceHome proporciona un método create() sin argumentos. Todos los interfaces home de los beans de sesión sin estado definirán sólo un método, un método create() sin argumento, porque los beans de sesión no tienen métodos de búsqueda y no puden ser inicializados con ningún argumento cuando son creados. Los beans de sesión sin estado no tienen métodos de búsqueda porque son todos equivalentes y no son persistentes. En otras palabras, no hay beans de sesión sin estado únicos que puedan ser localizados en la base de datos. Como los beans de sesión sin estado no son persistentes, son servicios temporales. Cada cliente que usa el mismo tipo de bean de sesión sin estado obtiene el mismo servicio.
Abajo tenemos la definición de la clase del bean para CreditService. Este bean encapsula el acceso a los servicios de procesamiento de las Tarjetas de Crédito Acme. Específicamente, este bean accede a servidor web seguro de Acme y postea una solicitud para validar o realizar un cargo en la tarjeta de crédito del cliente:
import javax.ejb.SessionBean;
public class CreditServiceBean
implements SessionBean {
URL acmeURL;
HttpURLConnection acmeCon;
public void ejbCreate() {
try {
InitialContext jndiContext =
new InitialContext();
URL acmeURL = (URL)
jndiContext.lookup("
java:comp/ejb/env/url/acme");
acmeCon =
acmeURL.openConnection();
}
catch (Exception e) {
throws new EJBException(e);
}
}
public void verify(CreditCard card,
double amount) {
String response = post("verify:" +
card.postString() +
":" + amount);
if (response.substring("
approved")== -1)
throw new
CreditServiceException("denied");
}
public void charge(CreditCard card,
double amount)
throws CreditCardException {
String response = post("charge:" +
card.postString() +
":" + amount);
if (response.substring("approved")== -1)
throw new CreditServiceException("
denied");
}
private String post(String request) {
try {
acmeCon.connect();
acmeCon.setRequestMethod("
POST "+request);
String response =
acmeCon.getResponseMessage();
}
catch (IOException ioe) {
throw new EJBException(ioe);
}
}
public void ejbRemove() {
acmeCon.disconnect();
}
public void setSessionContext(
SessionContext cntx) {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
El bean sin estado CreditService demuestra que un bean sin estado puede representar una colección de servicios independientes pero relacionados. En este caso la validación y los cargos de una tarjeta de crédido están relacionados pero no son necesariamente interdependientes. Los beans sin estado podrían usarse para acceder a recursos poco usuales -- como es el caso de CreditService -- a bases de datos o para realizar cálculos complejos. El bean CreditService se usa para demostrar el servicio natural de los beans sin estado y para proporcionar un contexto para explicar el comportamiento de los métodos de retrollamada. El uso de beans de sesión sin estado no está limitado al comportamiento ilustrado en este ejemplo; se pueden utilizar para realizar cualquier tipo de servicio.
La clase CreditServiceBean usa una factoría de recursos URL (acmeURL()) para obtener y mantener una referencia al servidor web de Acme que existe en otro ordenador muy lejano. El CreditServiceBean usa el acmeURL() para obtener una conexión al servidor web y postear solicitudes para la validación o el cargo de tarjetas de crédito. El bean CreditService lo usan los clientes en lugar de una conexión directa para que el servicio pueda ser mejor manejado por el contenedor EJB, que almacenará conexiones y manejará las transaciones y la seguirdad automátiamente por el cliente.
El método ejbCreate() es invocado al principio de su vida y es invocado sólo una vez. Este método es un método conveniente para inicializar recursos de conexión y variables que serán usadas por el bean sin estado durante su vida. En el ejemplo anterior, el CreditServiceBean usa ejbCreate() para obtener una referencia a la factoría HttpURLConnection, que será usada para obtener conexiones al servidor web de Acme.
El CreditServiceBean usa el JNDI ENC para obtener una factoría de conexiones URL de la misma forma que el CustomerBean usaba el JNDI ENC para obtener una factoría de recursos DataSource para conexiones JDCB. El JNDI ENC es un contexto JNDI por defecto por que cual todos los beans tienen acceso automático. El JNDI ENC se usa para acceder a propiedades estáticas, otros beans, y factorías de recursos como java.net.URL y JDBC javax.sql.DataSource. Además, el JNDI ENC también proporciona acceso a factorias de recursos como JavaMail y Java Messaging Service.
Los métodos ejbCreate() y ejbRemove() son invocados sólo una vez en sus vidas por el contenedor; cuando el bean se crea por primera vez y cuando es destruido. Las invocaciones de los métodos create() y remove() en sus interface home y remoto por el cliente no resultan en invocaciones de los métodos ejbCreate() y ejbRemove() en el ejemplar del bean. En su lugar, una invocación al método create() le proporciona al cliente una referencia la tipo de bean sin estado y los métodos remove() invalidan la referencia. El contenedor decidirá cuando los ejemplares del bean son realmente creados y destruidos e invocará a los métodos ejbCreate() y ejbRemove() en esos momentos. Esto permite a los ejemplares sin estado ser compartidos entre muchos clientes sin impactar en las referencias de esos propios clientes.
En el CreditServiceBean, los métodos ejbCreate() y ejbRemove() son usados para obtener una conexión URL al principio de la vida del ejemplar del bean y para desconectarlo al final del ciclo de vida del ejemplar del bean. Durante el tiempo entre que la conexión URL es obtenida y es desconectada es usada por los métodos de negocio. Esta aproximación se usa para reducir el número de veces que se necesita obtener y desconectar una conexión, conservando los recursos. Podría parecer costoso mantener la conexión URL entre llamadas a métodos, pero los beans de sesión sin estado están diseñados para ser compartidos entre muchos clientes, por lo que están en uso constantemente. Tan pronto como una ejemplar sin estado completa una llamada de método para un cliente, automáticamente puede servir a otro cliente. Idealmente, hay poco tiempo sin utilización, por lo que tiene sentido mantener la conexión URL.
Los métodos verify() y charge() delegan sus peticiones al método post(), un método privado de ayuda. El método post() usa un HttpURLConnection para enviar información sobre la tarjeta de crédito al servidor web de Acme y devuelve la respuesta al método verify() o charge(). El HttpURLConnection podría haber sido desconectado automáticamente por el contenedor -- esto podría ocurrir si, por ejemplo, ha pasado mucho tiempo desde su último uso -- por eso el método post() siempre invoca al método connect(), que no hace nada si la conexión ya está establecida. Los métodos verify() y charge() analizan el valor de retorno buscando la sub-cadena "approved" que indica que la tarjeta de crédito no era denegada. Si no se encuentra "approved", se asume que la tarjeta de crédito fue denegada y se lanza una excepción de negocio.
El método setSessionContext() porporciona al ejemplar del bean una referencia a SessionContext que sirve para el mismo propósito que EntityContext lo era para CustomerBean en la sección Beans de Entidad. El SessionContext no se usa en este ejemplo.
Los métodos ejbActivate() y ejbPassivate() no están implementados en el bean CreditService porque la "pasivación" no se utiliza en los beans de sesión sin estado. Estos métodos están definidos en el interface javax.ejb.SessionBean para los beans de sesión con estado, y por eso se debe proporcionar una implementación vacía para los beans de sesión sin estado. Los beans de sesión sin estado nunca proporcionarán nada distinto que una implementación vacía para estos métodos.
Los beans de sesión sin estado también pueden usarse para acceder a la base de datos así como para coordinar la interacion de otros beans para realizar una tarea. Abajo tenemos la definición del HotelClerkBean mostrado anteriormente en este tutorial:
import javax.ejb.SessionBean;
import javax.naming.InitialContext;
public interface HotelClerkBean
implements SessionBean {
InitialContext jndiContext;
public void ejbCreate() {}
public void reserveRoom(Customer
cust, RoomInfo ri,
Date from, Date to) {
CreditCard card =
cust.getCreditCard();
RoomHome roomHome = (RoomHome)
getHome("java:comp/env/ejb/RoomEJB",
RoomHome.class);
Room room =
roomHome.findByPrimaryKey(
ri.getID());
double amount =
room.getPrice(from,to);
CreditServiceHome creditHome =
(CreditServiceHome) getHome(
"java:comp/env/ejb/CreditServiceEJB",
CreditServiceHome.class);
CreditService creditAgent =
creditHome.create();
creditAgent.verify(card, amount);
ReservationHome resHome =
(ReservationHome) getHome(
"java:comp/env/ejb/ReservationEJB",
ReservationHome.class);
Reservation reservation = resHome.create(cust.getName(),
room,from,to);
}
public RoomInfo[] availableRooms(Location loc,
Date from, Date to) {
// do a SQL call to find available rooms
Connection con = db.getConnection();
Statement stmt = con.createStatement();
ResultSet results =
stmt.executeQuery("SELECT ...");
...
return roomInfoArray;
}
private Object
getHome(String path, Class type) {
Object ref =
jndiContext.lookup(path);
return
PortableRemoteObject.narrow(ref,type);
}
}
El HotelClerkBean es un bean sin estado. Toda la información necesaria para precesar una reserva o para consultar una lista de habitaciones disponibles se obtiene desde los argumentos al método. En el método reserveRoom(), las operaciones sobre otros beans (Room, CreditService, y Reservation) están coordinadas para ralizar una larga tarea, reservar una habitación para un cliente. Este es un ejemplo de un bean de sesión que maneja la interacción con otros beans en beneficio del cliente. El método availableRooms() se usa para consultar la base de datos y obtener una lista de habitaciones -- la información devuelta al cliente es una colección de envolturas de datos definida por la clase RoomInfo. El uso de esta clase, mostrado abajo, es un patrón de diseño que proporciona al cliente una envoltura de peso ligero con sólo la información necesaria.
public class RoomInfo {
public int id;
public int bedCount;
public boolean smoking;
}
Para obtener una referencia a otro bean, como se hace tres veces en el método reserveRoom(), se usa el método de ayuda privado getHome(). Este método usa el JNDI ENC para obtener referencias a otros beans:
private Object getHome(String path,
Class type) {
Object ref =
jndiContext.lookup(path);
return
PortableRemoteObject.narrow(
ref,type);
}
En la especificación EJB 1.1, RMI sobre IIOP es el modelo de programación específicado, por eso los tipos de referencia CORBA deben ser soportados. Las referencias CORBA no pueden ser forzadas usando Java nativo. En su lugar se debe usar el método PortableRemoteObject.narrow() para apuntar explícitamente a una referencia desde un tipo a su subtipo. Como JNDI siempre devuelve un tipo Object, todas las referencias deben apuntar explícitamente para soportar la portabilidad entre contenedores.
Como ejemplo, el bean HotelClerk puede ser modificado para que sea un bean con estado que pueda mantener estado-conversacional entre llamadas a métodos. Esto sería útil, por ejemplo, si queremos que el bean HotelClerk pueda realizar muchas reservas, pero pueda procesarlas todas juntas bajo una tarjeta de crédito. Esto ocurre frecuentemente, cuando las familias necesitan reservar dos o más habitaciones o cuando las corporaciones reservan un bloque de habitaciones para algún evento. Abajo tenemos el HotelClerkBean modificado para ser un bean con estado:
import javax.ejb.SessionBean;
import javax.naming.InitialContext;
public class HotelClerkBean
implements SessionBean {
InitialContext jndiContext;
//conversational-state
Customer cust;
Vector resVector = new Vector();
public void ejbCreate(Customer
customer) {}
cust = customer;
}
public void addReservation(Name
name, RoomInfo ri,
Date
from, Date to) {
ReservationInfo resInfo =
new ReservationInfo(name,
ri,from,to);
resVector.addElement(resInfo);
}
public void reserveRooms() {
CreditCard card =
cust.getCreditCard();
Enumeration resEnum =
resVector.elements();
while
(resEnum.hasMoreElements()) {
ReservationInfo resInfo =
(ReservationInfo)
resEnum.nextElement();
RoomHome roomHome = (RoomHome)
getHome("java:comp/env/ejb/RoomEJB",
RoomHome.class);
Room room =
roomHome.findByPrimaryKey(
resInfo.roomInfo.getID());
double amount = room.getPrice(
resInfo.from,restInfo.to);
CreditServiceHome creditHome =
(CreditServiceHome)
getHome("java:comp/env/ejb/CreditServiceEJB",
CreditServiceHome.class);
CreditService creditAgent = creditHome.create();
creditAgent.verify(card, amount);
ReservationHome resHome = (ReservationHome)
getHome("java:comp/env/ejb/ReservationEJB",
ReservationHome.class);
Reservation reservation =
resHome.create(resInfo.getName(),
resInfo.roomInfo,resInfo.from,resInfo.to);
}
public RoomInfo[] availableRooms(Location loc,
Date from, Date to) {
// Make an SQL call to find available rooms
}
private Object getHome(String path, Class type) {
Object ref = jndiContext.lookup(path);
return PortableRemoteObject.narrow(ref,type);
}
}
En esta versión con estado de la clase HotelClerkBean, el estado conversacional es la referencia Customer que es obtenerida cuando se crea el bean y el Vector de objetos ReservationInfo. Manteniendo el estado-conversacional en el bean, el cliente está absuelto de responsabilidades de seguir la pista del estado de la sesión. El bean mantiene la pista de las reservas y de procesarlas en un proceso batch cuando se llama el método serverRooms().
Para conservar recursos, los beans de sesión con estado podrían ser "pasivizados" cuando no están en uso por el cliente. La "pasivización" en beans de sesión con estado es diferente de los beans de entidad. En los beans con estado, la "pasivización" significa que el estado-conversacional del bean es escrito en un almacenamiento secundario (en disco, normalmente) y el ejemplar es eliminado de la memoria. La referencia del cliente al bean no se ve afectada por la "pasivización", permanece viva y utilizable mientras el bean es "pasivizado". Cuando el cliente invoca a un método de un bean que está "pasivizado", el contenedor activará el bean ejemplarizando un nuevo ejemplar y rellenandolo con el estado-convsersacional escrito en el almacenamiento secundario.
Los beans de sesión con estado, usan los métodos ejbActivate() y ejbPassivate(). El contenedor invocará estos métodos para notificar al bean cuando está a punto de ser "pasivizado" (ejbPassivate()) e inmediatamente despues de la activación (ejbActivate()). Los desarrolladores de bean deberían usar estos métodos para cerrar recursos abiertos y para hacer otras operaciones de limpieza antes de que el estado del ejemplar sea escrito en el almacenamiento secundario y eliminarlo de la memoria.
El método ejbRemove() es invocado sobre el ejemplar con estado cuando el cliente invoca al método remove() del interface home o remoto. El bean debería usar el método ejbRemove() para hacer el mismo tipo de limpieza realizada en el método ejbPassivate().
Un descriptor de desarrollo tiene un formato predefinido que deben usar todos los beans compatibles EJB y que todos los servidores EJB deben saber como leer. Este formato se especifica como un "Document Type Definition", o DTD de XML. El descriptor de desarrollo describe el tipo de bean (sesión o entidad) y las clases usadas para el interface remoto, home y la clase del bean. También especifica los atributos transacionales de cada método del bean, qué roles de seguridad pueden acceder a cada método (control de acceso), y si la persistencia de un bean de entidad es manejada automáticamente o sea manejada por el bean. Abajo tenemos un ejemplo de descriptor de desarrollo usado para describir el bean Customer:
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC "-
//Sun Microsystems, Inc.
//DTD Enterprise
JavaBeans 1.1//EN"
"http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar>
<enterprise-beans>
<entity>
<description>
This bean represents a customer
</description>
<ejb-name>CustomerBean</ejb-name>
<home>CustomerHome</home>
<remote>Customer</remote>
<ejb-class>CustomerBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>Integer</prim-key-class>
<reentrant>False</reentrant>
<cmp-field><field-name>myAddress</field-name></cmp-field>
<cmp-field><field-name>myName</field-name></cmp-field>
<cmp-field><field-name>myCreditCard</field-name></cmp-field>
</entity>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<description>
This role represents everyone who is allowed full access
to the Customer bean.
</description>
<role-name>everyone</role-name>
</security-role>
<method-permission>
<role-name>everyone</role-name>
<method>
<ejb-name>CustomerBean</ejb-name>
<method-name>*</method-name>
</method>
</method-permission>
<container-transaction>
<description>
All methods require a transaction
</description>
<method>
<ejb-name>CustomerBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
Los servidores de aplicaciones EJB normalmente proporcionan herramientas que pueden ser usadas para construir descriptores de desarrollo; esto simplifica mucho el proceso.
Cuando un bean va ser desplegado, sus ficheros class de los interfaces home y remoto y de la clase del bean y del descriptor de desarrollo deben empaquetarse juntos en un fichero JAR. El descriptor de desarrollo debe almacenarse en el fichero JAR con un nombre especial META-INF/ejb-jar.xml. Este fichero JAR, llamado un ejb-jar, puede ser deplegado en cualquier contenedor EJB que soporte la especificación EJB. Cuando un bean es desplegado en cualquier contenedor EJB su descriptor de desarrollo XML es leído desde el fichero JAR para determinar cómo manejar el bean en tiempo de ejecución. La persona que despliega el bean mapeará los atributos del descriptor de desarrollo al entorno del contenedor. Esto incluirá el mapeo de la seguirdad de accesos al sistema de seguridad del entorno, añadir el bean al sistema de nombrado del contenedor EJB, etc. Una vez que el desarrollador del bean han finalizado su despligue el bean estará disponible para que lo utilicen las aplicaciones cliente y otros beans.
El interface remoto define los métodos de negocio, como los métodos accesores o modificadores para cambiar el nombre de un cliente, o métodos de negocio que realizan tareas como usar el bean HotelClerk para reservar una habitación en le Hotel. Abajo tenemos un ejemplo de cómo se podría acceder al bean Customer desde una aplicación cliente. En este caso el interface home es CustomerHome y el interface remoto es Customer:
CustomerHome home;
Object ref;
// Obtain a reference to the CustomerHome
ref = jndiContext.lookup("java:comp/env/ejb/Customer");
// Cast object returned by the JNDI lookup to the
// appropriate datatype
home = PortableRemoteObject.narrow(ref, CustomerHome.class);
// Use the home interface to create a
// new instance of the Customer bean.
Customer customer = home.create(customerID);
// Use a business method on the Customer.
customer.setName(someName);
Un cliente, primero obtiene una referencia al interface home usando JNDI ENC para buscar el servidor del bean. En EJB 1.1, Java RMI-IIOP es el modelo de programación especificado. Como consecuencia, todas las referencias CORBA deben ser soportadas. Las referencias CORBA no pueden ser forzadas usando Java nativo. En su lugar se debe usar el método PortableRemoteObject.narrow() para apuntar explícitamente a una referencia desde un tipo a su subtipo. Como JNDI siempre devuelve un tipo Object, todas las referencias deben apuntar explícitamente para soportar la portabilidad entre contenedores.
Después de obtener el interface home, el cliente usa los métodos definidos en este interface para crear, buscar, o eliminar el bean servidor. Invocar uno de los métodos create() del interface home devuelve al cliente una referencia remota al bean servidor, que el cliente usa para realizar su trabajo.