Tan recientemente como hace diez a�os, el c�lculo distribuido generalmente significaba que teniamos clientes PCs en una habitaci�n con un servidor en otra. El problema con esta arquitectura es que si se pierde la conexi�n con el servidor, los clientes no pueden actualizar las bases de datos de la compa��a.
Para evitar esta p�rdida de tiempo, se crearon los diferentes modelos de red. Un ejemplo es el modelo de servidor maestro y esclavo donde si el maestro falla, el esclavo toma el relevo. El problema con los distintos modelos de red es que todos requieren alguna forma de intervenci�n manual y se unieron con un sistema operativo o un lenguaje. Y aunque estas aproximaciones consiguieron reducir el tiempo de parada, no cumplen con los sistemas de distribuci�n heterog�nea que consiste en una mezcla de protocolos de red y m�quinas.
La plataforma Java combinada con otros avances como Common Object Request Broker Architecture (CORBA), servidores multi-fila, y redes sin cables han llevado un paso mas all� la realizaci�n de la computaci�n totalmentedistribuida, de la tradicional aproximaci�n cliente y servidor.
Ahora podemos construir aplicaciones que incluyan servicios de redundancia por defecto. Si una conexi�n de servidor falla, podemos usar un servicio de otro servidor. CORBA y los puentes "Distributed Component Object Model" (DCOM) significan que los objetos pueden ser transferidos entre casi todas las m�quinas y lenguajes. Y con el nuevo sistema de software Jini, el entorno de c�lculo distribuido pude estar pronto en todos los hogares, oficinas o escuelas.
�Servicios de B�squeda
Los servicios de b�squeda permiten las comunicaciones a trav�s de la red. Un programa cliente puede usar un protocolo de b�squeda para obtener informaci�n sobre programas remotos o m�quinas que usen esa informaci�n para establecer una comunicaci�n.
- Un servicio de b�squeda com�n con el que podr�amos estar familiarizados es el Directory Name Service (DNS). Mapea direcciones de Internet Protocol (IP) a nombres de m�quinas. Los programas usan el mapeo DNS para buscar direcciones IP asociadas con un nombre de m�quina y usar la direcci�n IP para establecer una comunicaci�n.
- De la misma forma, el AuctionServlet presentado en el Cap�tulo 2 usa el servicio de nombres interno de la arquitectura de JavaBeans Enterprise para buscar unas referencias a Beans Enterprise registrados con el servidor de JavaBeans Enterprise.
Adem�s de los servicios de nombres, algunos protocolos de b�squeda proporcionan servicios de directorio. Este servicios como el Lightweight Directory Access Protocol (LDAP) y el NIS+ de Sun proporcionan otra informaci�n y servicios m�s all� de los disponibles con el servicio de nombres. Por ejemplo, NIS+ asocia un atributo workgroup con una cuenta de usuario. Este atributo puede usarse para restringir el acceso a una m�qu�na, por lo que s�lo los usuarios especificados en el workgroup tienen acceso.
Este cap�tulo describe como se usa el "Naming and Directory Interface (JNDI)" de Java en la aplicaci�n de subastas para buscar los Beans de Enterprise. Tambi�n explica como usar algunos de los otros muchos servicios de b�squeda que tenemos disponibles. El c�digo para usar estos servicios no es tan sencillo como el c�digo de la b�squeda en la aplicaci�n de la subasta del cap�tulo 2, pero las ventajas que ofrecen estos otros servicios hacen que algunas veces merezca la pena ese c�digo m�s complejo.
�Java Naming and Directory Interface (JNDI)
El API de JNDI hace sencillo conectar servicios de b�squeda de varios proveedores en un programa escrito en lenguaje Java. Siempre que el cliente y el servidor usen el mismo servicio de b�squeda, el cliente puede f�cilmente buscar informaci�n registrada en el servidor y establecer una comunicaci�n.
Los Beans de sesi�n de la aplicaci�n de subasta usan JNDI y una f�brica de nombres JNDI especial de BEA Weblogic para buscar Beans de entidad. Los servicios JNDI normalmente inicializan la f�brica de nombres como una propiedad de la l�nea de comandos o como un valor de inicializaci�n.
Primero, la f�brica de nombres weblogic.jndi.TengahInitialContextFactory se pone dentro de un objeto java.util.Property, luego este objeto se pasa como par�metro al constructor de InitialContexT. Aqu� tenemos un ejemplo del m�todo ejbCreate:
Context ctx; //JNDI context
public void ejbCreate()
throws CreateException, RemoteException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.TengahInitialContextFactory");
try{
ctx = new InitialContext(env);
}catch(Exception e) {
System.out.println("create exception: "+e);
}
}
Una vez creado, el contexto JNDI se usa para buscar los interfaces principales de los Beans Enterprise. En este ejemplo, se recupera una referencia a un Bean Enterprise uinda al nombre registration y se usa para operaciones posteriores:
RegistrationHome rhome =
(RegistrationHome) ctx.lookup("registration");
RegistrationPK rpk=new RegistrationPK();
rpk.theuser=buyer;
Registration newbidder =
rhome.findByPrimaryKey(rpk);
En el lado del servidor, el descriptor de desarrollo para el RegistrationBean tiene su valor beanhomename como registration. Las herramientas de JavaBeans de Enterprise generan el resto del c�digo de nombres para el servidor.
El servidor llama a ctx.bind para unir el nombre registration al contexto JNDI. El par�metro this referencia a la clase _stub que representa el RegistrationBean.
ctx.bind("registration", this);
JNDI no es la �nica forma de localizar objetos remotos. Los servicios de b�squeda tambi�n est�n disponibles en las plataformas RMI, JNI y CORBA. Podemos usar directamente los servicios de b�squeda de estas plataformas directamente desde el API del JNDI. JNDI permite a las aplicaciones cambiar el servicio de nombres con poco esfuerzo. Por ejemplo, aqu� est� el c�digo que hace que el m�todo BidderBean.ejbCreate use el servicio de b�squeda de org.omb.CORBA en vez del servicio de b�squeda por defecto de BEA Weblogic.
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
Context ic = new InitialContext(env);
>
�Servico de Nombres CORBA
El "Common Object Request Broker Architecture" (CORBA) define una especificaci�n para que los objetos de un sistema distribuido se comuniquen unos con otros. Los objetos que usan la especificaci�n CORBA para comunicarse se llaman objetos CORBA, y consisten en objetos cliente y servidor.
Los objetos CORBA puede estar escritos en cualquier lenguaje con el mapeo "Interface Definition Language" (IDL). Estos lenguajes incluyen lenguajes de programaci�n como Java, C++, y muchos otros lenguajes tradicionales no orientados a objetos.
El servicio de b�squeda de nombres, al igual que otras especificaciones CORBA, est� definido en t�rminos de IDL. El m�dulo IDL para el servicio de b�squeda CORBA se llama CosNaming. Cualquier plataforma con un mapeo IDL, como la herramienta idltojava, puede usar este servicio para descubrir objetos CORBA. El m�dulo IDL para este servicio de b�squeda CORBA est� disponible en la plataforma Java 2 en el paquete org.omg.CosNaming.
El interface clave en el m�dulo CosNaming es NamingContext. Este interface define m�todos para unir objetos a un nombre, listar estas uniones, y recuperar referencias a dichos objetos.

Adem�s de estos interfaces p�blicos hay clases de ayuda. La clase NameComponent se usa en programas cliente y servidor CORBA para construir el nombre completo para el nombre del objeto referencia. El nombre completo es un array de uno o m�s NameComponents que indica donde encontrar los objetos. El esquema de nombrado puede ser espec�fico de la aplicaci�n.
Por ejmplo en la aplicaci�n de subastas, el nombre completo puede ser definido para usar auction como la ra�z del contexto de nombres y RegistrationBean y AuctionItemBean como hijos del contexto ra�z. Esto en efecto utiliza un esquema de nombres similar al usado a los paquetes de clases.
En este ejemplo, la aplicaci�n de subastas a adaptado SellerBean a un servicio de nombres CORBA para buscar el RegistrationBean CORBA. El siguiente c�digo se ha extra�do de SellerBean, y act�a como un cliente CORBA, y el servidor CORBA RegistrationServer.
�CORBA RegistrationServer
Este c�digo del programa RegistrationServer crea un objeto NameComponent que indica d�nde est� localizado el RegistrationBean usando auction y RegistrationBean como el nombre completo:
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
El siguiente c�dido une el fullname como un nuevo contexto. Los primeros elementos en el nombre completo (auction en este ejemplo) son huecos para construir el �rbol del contexto de nombrado. El �ltimo elemento del nombre completo (RegistrationBean en este ejemplo) es el nombre enviado para unirlo al objeto:
String[] orbargs = { "-ORBInitialPort 1050"};
ORB orb = ORB.init(orbargs, null) ;
RegistrationServer rs= new RegistrationServer();
orb.connect(rs);
try{
org.omg.CORBA.Object nameServiceObj =
orb.resolve_initial_references("NameService");
NamingContext nctx =
NamingContextHelper.narrow(nameServiceObj);
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
NameComponent[] tempComponent =
new NameComponent[1];
for(int i=0; i < fullname.length-1; i++ ) {
tempComponent[0]= fullname[i];
try{
nctx=nctx.bind_new_context(tempComponent);
}catch (Exception e){}
}
tempComponent[0]=fullname[fullname.length-1];
// finally bind the object to the full context path
nctx.bind(tempComponent, rs);
Una vez que se ha unido el objeto RegistrationServer, puede ser localizado con una b�squeda JNDI usando el proveedor de servicio CosNaming como se describe al final de la secci�n JNDI, o usando el servicio de b�squedas de nombres CORBA. De cualquier forma, el servidor de nombres CORBA debe arrancarse antes de que pueda suceder cualquier b�squeda. En la plataforma Java 2, el nameserver CORBA se arranca con este comando:
tnameserv
Esto arranca el RegistrationServer CORBA en el puerto TCP por defecto 900. Si necesitamos usar otro puerto diferente, podemos arrancar el servidor de esta forma. En sistemas Unix, s�lo el administrador puede acceder a los n�meros de puerto inferiores a 1025,
tnameserv -ORBInitialPort 1091
�CORBA SellerBean
En el lado del cliente, la b�squeda CORBA usa el objeto NameComponent para construir el nombre. Arrancamos el servidor de objetos de esta forma:
java registration.RegistrationServer
La diferencia en el cliente es que este nombre se pasa al m�todo resolve que devuelve el objeto CORBA. El siguiente c�digo del objeto SellerBean ilustra este punto:
String[] args = { "-ORBInitialPort 1050"};
orb = ORB.init(args, null) ;
org.omg.CORBA.Object nameServiceObj =
orb.resolve_initial_references("NameService") ;
nctx= NamingContextHelper.narrow(nameServiceObj);
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
org.omg.CORBA.Object cobject= nctx.resolve(fullname);
El m�todo narrow, desde el m�todo Helper, es generado por el compilador IDL, que proporciona una mapeo detallado para traducir cada campo CORBA en su respectivo campo del lenguaje Java. Por ejemplo, el m�todo SellerBean.insertItem busca un objeto CORBA registrado usando el nombre RegistrationBean, y devuelve un objeto RegistrationHome. Con el objeto RegistrationHome podemos devolver un registro Registration llamando a su m�todo findByPrimaryKey.
org.omg.CORBA.Object cobject= nctx.resolve(fullname);
RegistrationHome regHome=
RegistrationHomeHelper.narrow(cobject);
RegistrationHome regRef =
RegistrationHomeHelper.narrow(
nctx.resolve(fullname));
RegistrationPKImpl rpk= new RegistrationPKImpl();
rpk.theuser(seller);
Registration newseller =
RegistrationHelper.narrow(
regRef.findByPrimaryKey(rpk));
if((newseller == null)||
(!newseller.verifyPassword(password))) {
return(Auction.INVALjdcbook_USER);
}
�Interoperable Object References (IOR)
Usar un servicio de nombres CORBA funciona para la mayor�a de las aplicaciones CORBA, especialmente cuando el (ORB) est� suministrado por un vendedor. Sin embargo, podr�amos encontrar que el servicio de nombres no es totalmente compatible con todos los ORBs, y podr�amos obtener el frustante mensaje COMM_FAILURE cuando el cliente CORBA intente conectarse con el servidor CORBA.
La soluci�n es usar un "Interoperable Object Reference" (IOR) en su lugar. Este est� disponible en los ORBs que soportan el protocolo "Internet Inter-ORB Protocol" (IIOP). Conteine la informaci�n que el servicio de nombres podr�a mantener para cada objeto como el host y el puerto donde reside el objeto, una �nica clave de b�squeda para el objeto en ese host, y qu� version de IIOP soporta.
�Servidor IOR
Para crear un IOR todo lo que tenemos que hacer es llamar al m�todo object_to_string desde la clase ORB y pasarle un ejemplar del objeto. Por ejemplo, para convertir el objeto RegistrationServer en un IOR, necesitamos a�adir la l�nea String ref = orb.object_to_string(rs); del siguiente c�digo en el programa principal:
String[] orbargs= {"-ORBInitialPort 1050"};
ORB orb = ORB.init(orbargs, null);
RegistrationServer rs = new RegistrationServer();
//Add this line
String ref = orb.object_to_string(rs);
Por eso, en lugar de recuperar la informaci�n de este objeto desde un servicio de nombres, hay otra forma para que el servidor env�e esta informaci�n a un cliente. Podemos registrar el string devuelto con un nombre sustitutivo del servidor, que puede ser un sencillo servidor web HTTP porque el objeto ya est� en un formato transmitible.
�Cliente IOR
Este ejemplo usa una conexi�n HTTP para convertir el string IOR de nuevo en un objeto. Podemos llamar al m�todo string_to_object desde la clase ORB. Este m�todo llama al IOR desde el RegistrationServer y devuelve el string ORB. Este string se pasa al ORB usando el m�todo ORB.string_to_object, y el ORB devuelve la referencia al objeto remoto:
URL iorserver = new URL(
"http://server.com/servlet?object=registration");
URLConnection con = ioserver.openConnection();
BufferedReader br = new BufferReader(
new InputStreamReader(con.getInputStream));
String ref = br.readLine();
org.omg.CORBA.Object cobj = orb.string_to_object(ref);
RegistrationHome regHome =
RegistrationHomeHelper.narrow(cobj);
El nombre sustituto del servidor puede mantener registros persistentes IOR que pueden sobrevivir a paradas si es necesario.
�Remote Method Invocation (RMI)
El API "Remote Method Invocation" (RMI) originalmente usaba su propio protocolo de comunicaci�n llamado "Java Remote Method Protocol" (JRMP), que resultaba en tener su propio servicio de b�squeda. Las nuevas versiones de RMI pueden usar el protocolo IIOP, adem�s de JRMP, RMI-IIOP se cubre en la siguiente secci�n.
El servicio de nombrado del JRMP RMI es similar a otros servicios de b�squeda y nombrado. La b�squeda real se consigue llamando a Naming.lookup y pas�ndole un par�metro URL a este m�todo. La URL especifica el nombre de la m�quina, y opcionalmente el puerto donde est� el servidor de nombres, rmiregistry, que sabe que objeto se est� ejecutando, y el objeto remoto que queremos referenciar para llamar a sus m�todos.
Por ejemplo:
SellerHome shome =
(SellerHome)Naming.lookup(
"rmi://appserver:1090/seller");
Este c�digo devuelve la referencia remota de SellerHome desde el objeto unido al nombre seller en la m�quina llamada appserver. La parte rmi de la URL es opcional y podr�amos haber visto URLs RMI sin ella, pero si est�mos usando JNDI o RMI-IIOP, incluir rmi en la URL nos ahorra confusiones posteriores. Una vez que tenemos la referencia a SellerHome, podemos llamar a sus m�todos.
En contraste con la b�squeda JNDI realizada por AuctionServlet.java, que requiere una b�squeda de dos estados para crear un contexto y luego la b�squeda real, RMI inicializa la conexi�n con servidor de nombres RMI, rmiregistry, y tambi�n obtiene la referencia remota con una llamada.
Esta referencia remota ser� el cliente inquilino de rmiregistry. Inquilino significa que a menos que el cliente informe al servidor de que todav�a necesita una referencia al objeto, el alquiler expira y la memoria es liberada. Esta operaci�n de alquiler es transparente para el usuario, pero puede ser ajustada seleccionando el valor de la propiedad java.rmi.dgc.leaseValue en el servidor, en milisegundos cuando se arranca el servidor de esta forma:
java -Djava.rmi.dgc.leaseValue=120000 myAppServer
�RMI sobre Internet Inter-ORB Protocol (IIOP)
La ventaja de RMI sobre "Internet Inter-ORB Protocol " (IIOP), significa que el c�digo RMI existente puede referenciar y buscar un objeto con el servicio CosNaming de CORBA. Esto nos ofrece una gran interoperatividad entre arquitecturas con un peque�o cambio en nuestro c�digo RMI existente.
Nota: El compilador rmic proporciona la opci�n -iiop para generar el stub y las clases tie necesarias para RMI-IIOP.
�Servidor IIOP
El protocolo RMI-IIOP se implementa como un plug-in JNDI, por lo que como antes, necesitamos crear un InitialContext:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
La factor�a de nombres deber�a parecer familiar como el mismo servicio de nombres usado en la secci�n CORBA. La principal diferencia es la adicci�n de un valor URL especificando el servicio de nombres al que conectarse. El servicio de nombres usado aqu� es el programa tnameserv arrancado en el puerto 1091:
tnameserv -ORBInitialPort 1091
El otro cambio principal en el lado del servidor es reemplazar las llamadas a Naming.rebind para usar el m�todo rebind de JNDI en el ejemplar InitialContext. Por ejemplo:
Viejo c�digo RMI:
SellerHome shome=(SellerHome)Naming.lookup( "rmi://appserver:1090/seller");
Nuevo c�digo RMI:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
SellerHome shome=
(SellerHome)PortableRemoteObject.narrow(
ic.lookup("seller"), SellerHome)
�Ciente IIOP
En el lado del cliente, la b�squeda RMI se cambia para usar un ejemplar del InitialContext en lugar del Naming.lookup de RMI. El objeto devuelto es mapeado al objeto requerido usando el m�todo narrow de la clase javax.rmi.PortableRemoteObject. PortableRemoteObject reemplaza UnicastRemoteObject que estaba disponible anteriormente en c�digo de servidor RMI.
Viejo c�digo de b�squeda RMI:
SellerHome shome= new SellerHome("seller");
Naming.rebind("seller", shome);
Nuevo c�digo RMI:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
SellerHome shome= new SellerHome("seller");
ic.rebind("seller", shome);
El PortableRemoteObject reemplaza al UnicastRemoteObject disponible anteriormente en el c�digo del servidor RMI. El c�digo RMI deber�a extender UnicastRemoteObject o llamar al m�todo exportObject de la clase UnicastRemoteObject. PortableRemoteObject Tambi�n contiene un m�todo exportObject equivalente. En la implementaci�n actual, es mejor eliminar expl�citamente los objetos no utilizados mediante llamadas a PortableRemoteObject.unexportObject().
�Servicios de B�squeda JINI
�Aumentar el Rendimiento de la B�squeda
Cuando ejecutemos nuestra aplicaci�n, si encontramos que llevar el objeto a otro ordenador a trav�s de un diskette ser� m�s r�pido, es que tenemos un problema de configuraci�n de la red. La fuente del problema es c�mo se resuelven los nombres de host y las direcciones IP, y aqu� tenemos un atajo.
RMI y otros servicios de nombres usan la clase InetAddress para resolver los nombres de host y direcciones IP. InetAddress almacena los resultados para mejorar las llamadas subsecuentes, pero cuando se le pasa una nueva direcci�n IP o un nombre de servidor, realiza una referencia cruzada entre la direcci�n IP y el nombre del host. Si suministramos el nombre del host como una direcci�n IP, InetAddress todav�a intentar� verificar el nombre del host.
Para evitar este problema, incluimos el nombre del host y la direcci�n IP en un fichero host en el cliente.
Sistemas Unix: En Unix, el fichero host normalmente es /etc/hosts.
Windows: En windows 95 � 98, el fichero host es c:\windows\hosts, (el fichero hosts.sam es un fichero de ejemplo). En windows NT, el fichero host es c:\winnt\system32\drivers\etc\hosts.
Todo lo que necesitamos hacer es poner estas l�neas en nuestro ficheo host. Las entradas myserver1 y myserver2 son los host donde se ejecutan el servidor remoto y rmiregistry:
127.0.0.1 localhost 129.1.1.1 myserver1 129.1.1.2 myserver2
�Invocaci�n Remota de M�todos
El API de Invocaci�n Remota de M�todos (RMI) permite las comunicaciones entre cliente y servidor a trav�s de la red entre programas escritos en Java. El servidor de JavaBeans Enterprise implementa de forma transparente el c�digo RMI necesario para que el programa cliente pueda referenciar a los Beans Enterprise que se ejecutan en el servidor y acceder a ellos como si se estuvieran ejecutando localmente en el programa cliente.
El tener el RMi incluido internamente el servidor JavaBeans de Enterprise es muy conveniente y nos ahorra tiempo de codificaci�n, pero si necesitamos usar caracter�sticas avanzadas de RMI o integrar RMI con una aplicaci�n existente, necesitamos sobreescribir la implementaci�n por defecto RMI y escribir nuestro propio c�digo RMI.
El cap�tulo reemplaza el RegistrationBean manejado por contenedor del Cap�tulo 2: Beans de Entidad y de Sesi�n con un servidor de registro basado en RMI. El Bean SellerBean del cap�tulo 2, tambi�n se modifica para llamar al nuevo servidor de registro RMI usando una llamada a lookup de Java 2 RMI.
�Sobre RMI
El API RMI nos permite accede a un servidor de objetos remoto desde un programa cliente haciendo sencillas llamadas a m�todos del servidor de objetos. Mientras que otras arquitecturas distribuidas para acceder a servidores de objetos remotos como "Distributed Component Object Model" (DCOM) y "Common Object Request Broker Architecture" (CORBA) devuelven referencias al objeto remoto, el API RMI no s�lo devuelve referencias, si no que proporciona beneficios adicionales.
- El API RMI maneja referencias a objetos remotos (llamadas por referencia) y tambi�n devuelve una copia del objeto (llamada por valor).
- Si el programa cliente no tiene acceso local a la clase para la que se ejemplariz� un objeto remoto, los servicios RMI pueden descargar el fichero class.
�Serializaci�n y colocaci�n de Datos
Para transferir ojbjetos, el API RMI usa el API Serialization para empaquetar (colocar) y desempaquetar (descolocar) los objetos. Para colocar un objeto, el API Serialization convierte el objeto a un Stream de bytes, y para descolocar el objeto, el API Serialization convierte un stream de bytes en un objeto.
�RMI sobre IIOP
Una de las desventajas iniciales del RMI era que la �nica relaci�n con la plataforma Java para escribir interfaces hacen d�ficil la intregraci�n con sistemas legales existentes. Sin embargo, RMI sobre "Internet Inter-ORB Protocol" (IIOP) explicado en el Cap�tulo 4: Servicios de B�squeda permite a RMI comunicarse con cualquier sistema o lenguaje que soporte CORBA.
Si combinamos la integraci�n mejorada con la habilidad de RMI para trabajar a trav�s de firewalls usando proxies HTTP, podr�amos encontrar distribuciones para la l�gica de nuestro negocio usando RMI m�s f�ciles que una soluci�n basada en sockets.
Nota: La transferencia de c�digo y datos son partes clave de la especificaci�n JINI. De hecho, si a�adieramos un servicio de uniones a los servicios RMI crear�amos algo muy similar a los que obtenemos con la arquitectura JINI.
�RMI en la aplicaci�n de Subastas
El RegistrationServer basado en RMI tiene los siguientes m�todos nuevos:
- Un nuevo m�todo create para crear un nuevo usuario.
- Un nuevo m�todo find para buscar un usuario.
- Un nuevo m�todo search para la b�squeda personalizada de usuarios en la base de datos.
La nueva b�squeda personalizada sevuelve los resultados al cliente llamante mediante una llamada a un Callbak RMI. Est� b�squeda es similar a los m�todos finder usados en los Beans de ejemplos usados en los cap�tulos 2 y 3, excepto en que la versi�n RMI, puede tardar m�s tiempo en generar los resultados porque el sevidor de registros remoto llama al m�todo remoto exportado por el cliente SellerBean basado en RMI.
Si el cliente llamante est� escrito en Java, y no es, por ejemplo, una p�gina web, el servidor puede actualizar el cliente tan pronto como los resultados estuvieran listos. Pero, el protocolo HTTP usado en la mayor�a de los navegadores no permite que los resultados sean enviados sin que haya una petici�n. Esto significa que el resultado de una p�gina web no se crea hasta que los resultados est�n listos, lo que a�ade un peque�o retraso.
�Introducci�n a las Clases
Las dos clases principales en la implementaci�n de la subasta basada en RMI son SellerBean y el remoto RegistrationServer. SellerBean es llamado desde AuctionServlet para insertar un �tem para la subasta en la base de datos, y chequear balances negativos en las cuentas.
Los modelos de ejemplo de la arquitectura JavaBeans Enterprise en los que los detalles de registro del usuario se han separado del c�digo para crear y encontrar detalles de registro. Es decir, los detalles de registro de usuario proporcionados por la clase Registration.java se separan del c�digo para crear y encontrar un objeto Registration, que est� en la clase RegistrationHome.java.
La implementaci�n del interface remoto de RegistrationHome.java est� unida al rmiregistry. Cuando un programa cliente quiere manipular detalles del registro del usuario, primero tiene que buscar la referencia al objeto RegistrationHome.java en el rmiregistry.
�Sumario de Ficheros
Todo los fciheros de c�digo fuente para el ejemplo basado en RMI se describen en la siguiente lista.
- SellerBean.java: Programa cliente que llama a los m�todos remotos RegistrationServer.verifypasswd y RegistrationServer.findLowCreditAccounts. SellerBean tambi�n exporta su m�todo updateResults que llama a RegistrationServer cuando completa su b�squeda RegistrationServer.findLowCreditAccounts.
- RegistrationServer.java: Servidor de objetos remotos que implementa los interfaces remotos RegistrationHome y Registration.
- Registration.java: Interface remoto que declara los m�todos remotos getUser, verifypasswd, y otros m�todos para el manejo de los detalles de registro del usuario.
- RegistrationHome.java: Interface remoto que declara los m�todos remotos create, findByPrimaryKey, y findLowCreditAccounts que crean o devuelven ejemplares de detalles de registro.
- RegistrationImpl.java: El fichero fuente RegistrationServer.java incluye la implementaci�n para el interface remoto Registration como la clase RegistrationImpl.
- RegistrationPK.java: Clase que representa los detalles de registro de usuario usando s�lo la clave primaria del registro de la base de datos.
- ReturnResults.java: Interface remoto que declara el m�todo updateResults la clase SellerBean lo implementa como callback.
- AuctionServlet.java: Versi�n modificada de la clase original AuctionServlet conde las cuentas de registro se crean mediante llamadas directas al RegistrationServer de RMI. El servelt de subasta tambi�n llama al m�todo SellerBean.auditAccounts, que devuelve una lista de los usuarios con un bajo balance en la cuenta.
El m�todo auditAccounts es llamado con la siguiente URL, donde hace un simple chequeo para verificar que la petici�n viene del host local.
http://phoenix.eng.sun.com:7001/ AuctionServlet?action=auditAccounts
Tambi�n necesitaremos un fichero de polic�a java.policy para conceder los permisos necesarios para ejecutar el ejemplo en plataformas Java 2.
La mayor�a de las aplicaciones RMI necesitan dos permisos socket, para accesos a los socket y a HTTP para especificar los puertos. Los dos permisos de threads fueron listados en una pila cuando sea necesario por la clase RegistrationImpl para crear un thread interno.
En la plataforma Java 2, cuando un programa no tiene todos los permisos que necesita, la "M�quina Virtual Java" genera una pila de seguimiento que lista los permisos que necesitan ser a�adidos al fichero de polic�a de seguridad.
grant {
permission java.net.SocketPermission
"*:1024-65535", "connect,accept,resolve";
permission java.net.SocketPermission "*:80",
"connect";
permission java.lang.RuntimePermission
"modifyThreadGroup";
permission java.lang.RuntimePermission
"modifyThread";
};
�Compilar el Ejemplo
Antes de describir el c�digo basado en RMI de las clases anteriores, aqu� est� la secuencia de comandos para compilar el ejemplo en las plataformas Unix y Win32:
Unix: javac registration/Registration.java javac registration/RegistrationPK.java javac registration/RegistrationServer.java javac registration/ReturnResults.java javac seller/SellerBean.java rmic -d . registration.RegistrationServer rmic -d . registration.RegistrationImpl rmic -d . seller.SellerBean Win32: javac registration\Registration.java javac registration\RegistrationPK.java javac registration\RegistrationServer.java javac registration\ReturnResults.java javac seller\SellerBean.java rmic -d . registration.RegistrationServer rmic -d . registration.RegistrationImpl rmic -d . seller.SellerBean
�Arrancar el Registro RMI
Como estamos usando nuestro propio c�digo RMI, tenemos que arrancar expl�citamente el RMI Registry para que el objeto SellerBean pueda encontrar los Beans remotos de Enterprise. El RegistrationServer usa el registro RMI para registrar o unir los Beans enterprise que pueden ser llamados de forma remota. El cliente SellerBean contacta con el registro para buscar y obtener las referencias a los Beans AuctionItem y Registration.
Como RMI permite que el c�digo y los datos sean transferidos, debemos asegurarnos que el sistema classloader no carga clases extras que puedan ser enviadas err�neamente al cliente. En este ejemplo, las clases extras podr�an ser las clases Stub y Skel, y las clases RegistrationSever y RegistrationImpl, y para evitar que lo sean cuando arrancamos el registro RMI. Como el path actual podr�a ser incluido autom�ticamente, necesitamos arrancar el RMI Registry desde fuera del espacio de trabajo.
Los siguientes comandos evitan el env�o de clases extras, desconfigurando la variable CLASSPATH antes de arrancar el Registro RMI en el puerto 1099. Podemos especificar un puerto diferente a�adiendo el n�mero de puerto de esta forma: rmiregistry 4321 &. Si cambiamos el n�mero de puerto debemos poner el mismo n�mero en las llamadas al cliente lookup y al servidor rebind.
Unix: export CLASSPATH="" rmiregistry & Win32: unset CLASSPATH start rmiregistry
�Arrancar el Servidor Remoto
Una vez que rmiregistry se est� ejecutando, podemos arrancar el servidor remoto, RegistrationServer. El programa RegistrationServer registra el nombre registration2 con el servidor de nombres rmiregistry, y cualquier cliente puede usar este nombre para recuperar una referencia al objeto remoto, RegistrationHome.
Para ejecutar el ejemplo, copiamos las clasesRegistrationServer y RegistrationImpl y las clases stub asociadas a un �rea accesible de forma remota y arrancamos el programa servidor.
Unix: cp *_Stub.class /home/zelda/public_html/registration cp RegistrationImpl.class /home/zelda/public_html/registration cd /home/zelda/public_html/registration java -Djava.server.hostname= phoenix.eng.sun.com RegistrationServer Windows: copy *_Stub.class \home\zelda\public_html\registration copy RegistrationImpl.class \home\zelda\public_html\registration cd \home\zelda\public_html\registration java -Djava.server.hostname= phoenix.eng.sun.com RegistrationServer
Las siguientes propiedades se usan para configurar los clientes y servidores RMI. Estas propiedades pueden seleccionarse dentro del programa o suministrarlas como propiedades en la l�nea de comandos para la JVM.
- La propiedad java.rmi.server.codebase especifica d�nde se localizan las clases accesibles p�blicamente. En el servidor esto puede ser un simple fichero URL para apuntar al directorio o fichero JAR que contiene las clases. Si el URL apunta a un directorio, debe terminar con un car�cter separador de ficheros , "/".
Si no usamos un fichero URL, tampoco necesitaremos un servidor HTTP para descargar las clases remotas o tener que enviar manualmente el stub del cliente y las clases de interfaces remotos, por ejemplo, un fichero JAR.
- La propiedad java.rmi.server.hostname es el nombre completo del host del servidor donde residen las clases con acceso p�blico. Esto es s�lo necesario si el servidor tiene problemas para generar por s� mismo un nombre totalmente cualificado.
- La propiedad java.rmi.security.policy especifica el policy file con los permisos necesarios para ejecutar el objeto servidor remoto y para acceder a la descarga de las clases del servidor remoto.
�Establecer Comunicaciones Remotas
Los programas clientes se comunican unos con otros a trav�s del servidor. El programa servidor consiste en tres ficheros. Los ficheros de interfaces remotos Registration.java y RegistrationHome.java definen los m�todos que pueden ser llamados de forma remota, y el fichero RegistrationServer.java de clase define las clases RegistrationServer y RegistrationImpl que implementan los m�todos.
Para establecer comunicaciones remotas, tanto el programa cliente como el servidor necesitan acceder a las clases del interface remoto. El servidor necesita las clases del interface para generar la implementaci�n del interface, y el cliente usa el interface remoto para llamar a las implementaciones de los m�todos del servidor remoto.
Por ejemplo, SellerBean crea una referencia a el interface RegistrationHome, y no RegistrationServer, la implementaci�n, cuando necesita crear un regisro de usuario.
Junto con los interfaces del servidor y las clases, necesitamos las clases Stub y Skel para establecer comunicaciones remotas. Estas clases se generan cuando ejecutamos el comando del compilador rmic sobre las clases RegistrationServer y SellerBean.
Las clases SellerBean, SellerBean_Stub.class y SellerBean_Skel.class generadas son necesarias para la llamada desde el servidor hasta el cliente SellerBean. Es el fichero _Stub.class en el cliente que coloca y descoloca los datos desde el servidor, mientras que la clase _Skel.class hace los mismo en el servidor.
Nota: En la plataforma Java 2, el fichero del lado delservidor, _Skel.class ya no es necesario porque sus funciones han sido reemplazadas por las clases de la "Java Virtual Machine".
�Colocar Datos
Colocar y descolocar los datos significa que cuando llamamos al m�todo RegistrationHome.create desde SellerBean, esta llamada es reenviada al m�todo RegistrationServer_Stub.create. El m�todo RegistrationServer_Stub.create envuelve los argumentos del m�todo y los env�a a un stream serializado de bytes para el m�todo RegistrationServer_Skel.create.

Colocar y descolocar los datos tiene sus complicaciones. El primer problema son los objetos serializados que podr�an ser incompatiles entre versiones del JDK. Un objeto serializado tiene un identificador almacenado con el objeto que enlaza el objeto serializado con su versi�n. Si el cliente RMI y el servidor son incompativles con su ID de serie, podr�amos necesitar generar Stubs y Skels compatibles usando la opci�n -vcompat del compilador rmic.
Otro problema es que no todos los objetos son serializables por defecto. El objeto inicial RegistrationBean est� basado en la devoluci�n de un objeto Enumeration que contiene elementos Registration en un Vector. Devolver la lista desde el m�todo remoto, funciona bien, pero cuando intentamos env�ar un vector como un par�metro a un objeto remoto, obtendremos una excepci�n en tiempo de ejecuci�n en la plataforma Java 2.
Afortunadamente, en el API Collections, la plataforma Java ofrece alternativas a la descolocaci�n de objetos anterior. En este ejemplo, un ArrayList del API Collections reemplaza el Vector. Si el API Collections no es una opci�n, podemos crear una clase envoltura que extienda Serializable y proporcione implementaciones para los m�todos readObject y writeObject para convertir el objeto en un stream de bytes.
�La clase RegistrationServer
La clase RegistrationServer extiende java.rmi.server.UnicastRemoteObject e implementa los m�todos create, findByPrimaryKey y findLowCreditAccounts declarados en el interface RegistrationHome. El fichero fuente RegistrationServer.java tambi�n incluye la implementaci�n del interface remoto Registration como la clase RegistrationImpl. RegistrationImpl tambi�n extiende UnicastRemoteObject.
�Exportar un Objeto Remoto
Cualquier objeto que querramos que se accesible remotamente necesita extender el interface java.rmi.server.UnicastRemoteObject o usar el m�todo exportObject de la clase UnicastRemoteObject. Si extendemos UnicastRemoteObject, tambi�n obtendremos los m�todos equals, toString y hashCode para el objeto exportado.
�Pasar por Valor y por Referencia
Aunque la clase RegistrationImpl no est� unida al registro, todav�a est� referenciada remotamente porque est� asociada con los resultados devueltos por RegistrationHome. RegistrationImpl extiende UnicastRemoteObject, sus resultados son pasados por referencia, y s�lo una copia del Bean de registro del usuario existente en la Java VM a la vez.
En el caso de reportar resultados como en el m�todo RegistrationServer.findLowCreditAccounts, la clase RegistrationImpl se puede usar una copia del objeto remoto. Si no extendemos la clase UnicastRemoteObject en la definici�n de la clase RegistrationImpl, se devolver� un nuevo objeto Registration en cada petici�n. En efecto los valores son pasados pero no la referencia al objeto en el servidor.
�Recolecci�n de Basura Distribuida
Al usar referencias remotas a objetos en el servidor desde fuera del cliente el recolector de basura del servidor introduce algunos problemas potenciales con la memoria. �C�mo conoce el servidor cuando se mantiene una referencia a un objeto Registration que no est� siendo usado por ning�n cliente porque abort� o se cay� la conexi�n de red?
Para evitar bloqueos de memoria en el servidor desde los clientes, RMI usa un mecanismo de alquiler cuando ofrecen las referencias a los objetos exportados. Cuando se exporta un objeto, la JVM incrementa la cuenta del n�mero de referencias a este objeto y configura el tiempo de expiraci�n, o tiempo de pr�stamo, por el n�mero de referencias del objeto.
Cuando el alquiler expira, la cuenta de referencias de este objeto se decrementa y si alcanza 0, el objeto es seleccionado para la recolecci�n de basura por la JVM. Hay que configurar el cliente que mantiene un pico de referencia al objeto remoto a que renueve el alquiler si necesita el objeto m�s alla del tiempo de alquiler. Este pico de referencia es una forma de referirse a un objeto en la memoria sin mantenerlo lejos del recolector de basura.
Este tiempo de alquiler es una propiedad configurable medida en segundos. Si tenemos una red r�pida, podr�amos acortar el valor por defecto, y crear un gran n�mero de referencias a objetos transitorias.
El siguiente c�digo selecciona el tiempo de alquiler a 2 minutos.
Property prop = System.getProperties();
prop.put("java.rmi.dgc.leaseValue", 120000);
Los m�todos create y findByPrimaryKey son pr�cticamente id�nticos a las otras versiones del servidor Registration. La principal diferecia es que en el lado del servidor, el registro registration es referenciado como RegistrationImpl, que es la implementaci�n de Registration. En el lado del cliente, se usa Registration en su lugar.
El m�todo findLowCreditAccounts cosntruye un ArrayList de objetos RegistrationImpl serializables y llama al m�todo remoto en la clase SellerBean para pasar el resultado de vuelta. Los resultados on generado por una clase Thread interna porque el m�todo retorna antes de que el resultado est� completo. El objeto SellerBean espera a que sea llamado el m�todo updateAccounts antes de mostrar la p�gina HTML. En un cliente escrito en Java, no ser�a necesario esperar, podr�amos mostrar la actualizaci�n en tiempo real.
public class RegistrationServer
extends UnicastRemoteObject
implements RegistrationHome {
public registration.RegistrationPK
create(String theuser,
String password,
String emailaddress,
String creditcard)
throws registration.CreateException{
// code to insert database record
}
public registration.Registration
findByPrimaryKey(registration.RegistrationPK pk)
throws registration.FinderException {
if ((pk == null) || (pk.getUser() == null)) {
throw new FinderException ();
}
return(refresh(pk));
}
private Registration refresh(RegistrationPK pk)
throws FinderException {
if(pk == null) {
throw new FinderException ();
}
Connection con = null;
PreparedStatement ps = null;
try{
con=getConnection();
ps=con.prepareStatement("select password,
emailaddress,
creditcard,
balance from registration where theuser = ?");
ps.setString(1, pk.getUser());
ps.executeQuery();
ResultSet rs = ps.getResultSet();
if(rs.next()) {
RegistrationImpl reg=null;
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = pk.getUser();
reg.password = rs.getString(1);
reg.emailaddress = rs.getString(2);
reg.creditcard = rs.getString(3);
reg.balance = rs.getDouble(4);
return reg;
}else{
throw new FinderException ();
}
}catch (SQLException sqe) {
throw new FinderException();
}finally {
try{
ps.close();
con.close();
}catch (Exception ignore) {}
}
}
public void findLowCreditAccounts(
final ReturnResults client)
throws FinderException {
Runnable bgthread = new Runnable() {
public void run() {
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
ArrayList ar = new ArrayList();
try{
con=getConnection();
ps=con.prepareStatement("select theuser,
balance from registration
where balance < ?");
ps.setDouble(1, 3.00);
ps.executeQuery();
rs = ps.getResultSet();
RegistrationImpl reg=null;
while (rs.next()) {
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = rs.getString(1);
reg.balance = rs.getDouble(2);
ar.add(reg);
}
rs.close();
client.updateResults(ar);
}catch (Exception e) {
System.out.println("findLowCreditAccounts: "+e);
return;
}
finally {
try{
if(rs != null) {
rs.close();
}
if(ps != null) {
ps.close();
}
if(con != null) {
con.close();
}
}catch (Exception ignore) {}
}
} //run
};
Thread t = new Thread(bgthread);
t.start();
}
}
El m�todo main carga el driver JDBC. Esta versi�n usa la base de datos Postgres, instala el RMISecurityManager, y contacta con el registro RMI para unir el objeto remoto RegistrationHome al nombre registration2. No necesita unir el interface remoto, Registration porque la clase es cargada cuando es referenciada por RegistrationHome.
Por defecto, el servidor de nombres usa el puerto 1099. Si queremos usar un n�mero de puerto diferente, podemos a�adirlo con dos puntos de esta forma: kq6py:4321. Si cambiamos aqu� el n�mero de puerto, debemos arrancar el RMI Registry con el mismo n�mero de puerto.
El m�todo main tambi�n instala un RMIFailureHandler. Si el servidor falla al crear el socket servidor, el manejador de fallos devuelve true que instruye al servidor RMI para que reintente la operaci�n.
public static void main(String[] args){
try {
new pool.JDCConnectionDriver(
"postgresql.Driver",
"jdbc:postgresql:ejbdemo",
"postgres", "pass");
} catch (Exception e){
System.out.println(
"error in loading JDBC driver");
System.exit(1);
}
try {
Properties env=System.getProperties();
env.put("java.rmi.server.codebase",
"http://phoenix.eng.sun.com/registration");
RegistrationServer rs=
new RegistrationServer();
if (System.getSecurityManager() == null ) {
System.setSecurityManager(
new RMISecurityManager());
}
RMISocketFactory.setFailureHandler(
new RMIFailureHandlerImpl());
Naming.rebind("
//phoenix.eng.sun.com/registration2",rs);
}catch (Exception e) {
System.out.println("Exception thrown "+e);
}
}
}
class RMIFailureHandlerImpl
implements RMIFailureHandler {
public boolean failure(Exception ex ){
System.out.println("exception "+ex+" caught");
return true;
}
}
�Interface Registration
El interface Registration declara los m�todos implementados por RegistrationImpl en el fichero fuente RegistrationServer.java.
package registration;
import java.rmi.*;
import java.util.*;
public interface Registration extends Remote {
boolean verifyPassword(String password)
throws RemoteException;
String getEmailAddress() throws RemoteException;
String getUser() throws RemoteException;
int adjustAccount(double amount)
throws RemoteException;
double getBalance() throws RemoteException;
}
�Interface RegistrationHome
El interface RegistrationHome declara los m�todos implementados por la clase RegistrationServer. Estos m�todos reflejan el interface Home definido en el ejemplo JavaBeans de Enterprise. El m�todo findLowCreditAccounts toma un interface remoto como su �nico par�metro.
package registration;
import java.rmi.*;
import java.util.*;
public interface RegistrationHome extends Remote {
RegistrationPK create(String theuser,
String password,
String emailaddress,
String creditcard)
throws CreateException,
RemoteException;
Registration findByPrimaryKey(RegistrationPK theuser)
throws FinderException, RemoteException;
public void findLowCreditAccounts(ReturnResults rr)
throws FinderException, RemoteException;
}
�Interface ReturnResults
El interface ReturnResults declara el m�todo implementado por la clase SellerBean. El m�todo updateResults es llamado desde RegistrationServer.
package registration;
import java.rmi.*;
import java.util.*;
public interface ReturnResults extends Remote {
public void updateResults(ArrayList results)
throws FinderException, RemoteException;
}
�La Clase SellerBean
La clase SellerBean incluye la implementaci�n del m�todo callback y llama al objeto RegistrationServer usando RMI. El m�todo updateAccounts se hace accesible mediante una llamada a UnicastRemoteObject.exportObject(this);. El m�todo auditAccounts espera un objeto method Boolean.
El m�todo updateAccounts env�a una notificaci�n a todos los m�todos que esperan el objeto Boolean cuando ha sido llamado desde el servidor y recibe los resultados.
package seller;
import java.rmi.RemoteException;
import java.rmi.*;
import javax.ejb.*;
import java.util.*;
import java.text.NumberFormat;
import java.io.Serializable;
import javax.naming.*;
import auction.*;
import registration.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
public class SellerBean
implements SessionBean, ReturnResults {
protected SessionContext ctx;
javax.naming.Context ectx;
Hashtable env = new Hashtable();
AuctionServlet callee=null;
Boolean ready=new Boolean("false");
ArrayList returned;
public int insertItem(String seller,
String password,
String description,
int auctiondays,
double startprice,
String summary)
throws RemoteException {
try{
RegistrationHome regRef = (
RegistrationHome)Naming.lookup(
"//phoenix.eng.sun.com/registration2");
RegistrationPK rpk= new RegistrationPK();
rpk.setUser(seller);
Registration newseller = (
Registration)regRef.findByPrimaryKey(rpk);
if((newseller == null) ||
(!newseller.verifyPassword(password))) {
return(Auction.INVALjdcbook_USER);
}
AuctionItemHome home = (
AuctionItemHome) ectx.lookup(
"auctionitems");
AuctionItem ai= home.create(seller,
description,
auctiondays,
startprice,
summary);
if(ai == null) {
return Auction.INVALjdcbook_ITEM;
}else{
return(ai.getId());
}
}catch(Exception e){
System.out.println("insert problem="+e);
return Auction.INVALjdcbook_ITEM;
}
}
public void updateResults(java.util.ArrayList ar)
throws RemoteException {
returned=ar;
synchronized(ready) {
ready.notifyAll();
}
}
public ArrayList auditAccounts() {
this.callee=callee;
try {
RegistrationHome regRef = (
RegistrationHome)Naming.lookup(
"//phoenix.eng.sun.com/registration2");
regRef.findLowCreditAccounts(this);
synchronized(ready) {
try {
ready.wait();
} catch (InterruptedException e){}
}
return (returned);
}catch (Exception e) {
System.out.println("error in creditAudit "+e);
}
return null;
}
public void ejbCreate()
throws javax.ejb.CreateException,
RemoteException {
env.put(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.TengahInitialContextFactory");
try{
ectx = new InitialContext(env);
} catch (NamingException e) {
System.out.println(
"problem contacting EJB server");
throw new javax.ejb.CreateException();
}
Properties env=System.getProperties();
env.put("java.rmi.server.codebase",
"http://phoenix.eng.sun.com/registration");
env.put("java.security.policy","java.policy");
UnicastRemoteObject.exportObject(this);
}
public void setSessionContext(SessionContext ctx)
throws RemoteException {
this.ctx = ctx;
}
public void unsetSessionContext()
throws RemoteException {
ctx = null;
}
public void ejbRemove() {}
public void ejbActivate() throws RemoteException {
System.out.println("activating seller bean");
}
public void ejbPassivate() throws RemoteException {
System.out.println("passivating seller bean");
}
}
�Common Object Request Broker Architecture (CORBA)
Las implementaciones de RMI y de JavaBeans Enterprise de la aplicaci�n de subasta usan el lenguaje Java para implementar los distintos servicios de la subasta. Sin embargo, podr�amos necesitar intergrarlo con aplicaciones escritas en C, C++ u otros lenguajes y ejecutarlo en un millar de sistemas operativos y m�quinas distintas.
Una forma de integraci�n con otras aplicciones es transmitir datos en un formato com�n como caracteres de 8 bits sobre sockets TCP/IP. La desventaja es tener que gastar mucho tiempo en derivar un mensaje de protocolo y mapeado de varias estructuras de datos hacia y desde el formato de transmisi�n com�n para que los datos puedan ser enviados y recibidos sobre la conexi�n TCP/IP.
Aqu� es donde pueden ayudar el "Common Object Request Broker Architecture" (CORBA) y su "Interface Definition Language" (IDL). IDL proporciona un formato com�n para representar un objeto que puede ser distribuido a otras aplicaciones. Las otras aplicaciones podr�an incluso no entender de objetos, pero mientras puedan proporcionar un mapeado entre el formato com�n IDL y sus propia representaci�n de datos, la aplicaci�n podr� compartir los datos.
Este cap�tulo describe el esquema de mapeo de IDL a lenguaje Java, y c�mo reemplazar el original RegistrationBean basado en contenedor controlador por su equivalente servidor CORBA. Los programas SellerBean.java y AuctionServlet.java tambi�n se modifican para interoperar con el programa CORBA RegistrationServer.
�Esquema de Mapeo IDL
Muchos lenguajes de programaci�n proporcionan un mapeo entre sus tipos de datos y el formato com�n denominado IDL, y el lenguaje Java no es una excepci�n. El lenguaje Java puede enviar objetos definidos por IDL a otras aplicaciones distribuidas CORBA y recibir objetos definidos mediante IDL desde otras aplicaciones distribuidas CORBA.
Esta secci�n descrbie el esquema de mapedo de Java a IDL y, cuando sea necesario, presenta problemas que debemos tener en consideraci�n.
�Referencia R�pida
Aqu� tenemos una tabla de referencia r�pida de los tipos de datos del lenguaje Java y los de IDL CORBA, y las excepciones de tiempo de ejecuci�n que se pueden lanzar cuando la conversi�n falla. Los tipos de datos de esta tabla que necesitan explicaci�n se cubren m�s abajo.
| Tipo de Dato Java | Formato IDL | Exception |
|---|---|---|
| byte | octet | � |
| boolean | boolean | � |
| char | char | DATA_CONVERSION |
| char | wchar | � |
| double | double | � |
| float | float | � |
| int | long | � |
| int | unsigned long | � |
| long | long long | � |
| long | unsigned long long | � |
| short | short | � |
| short | unsigned short | � |
| java.lang.String | string | DATA_CONVERSION |
| java.lang.String | wstring | MARSHAL |
Valores sin Signo: Los tipos de datos Java: byte, short, int, y long est�n representados por entereros de complemento a dos de 8, 16, 32 y 64 bits. Esto significa que un valor short Java representa un rango desde -215 hasta 215 - 1 � desde -32768 hasta 32767 inclusives. El tipo con signo equivalente IDL para un short, short, es igual en el rango, pero el tipo short IDL sin signo usa el rango desde 0 hata 2 -15 � desde 0 hasta 65535.
Esto significa que en el caso de short, si un valor short sin signo mayor de 32767 es pasado a un programa escrito en Java, el valor short ser� representado como un n�mero negativo. Esto puede causar confusi�n en los l�mites de test para valores mayores que 32767 o menores que 0.
Tipos char IDL: El lenguaje Java usa un unicode de 16 Bits, pero los tipos char y string de IDL son carcateres de 8 bits. Podemos mapear un char Java a un char IDL de 8 bits para transmitir caracteres multi-byte si usamos un array para hacerlo. Sin embargo, el tipo de caracter ancho de IDL wchar est� especialmente dise�ado para lenguajes con caracteres multi-bytes y aloja el n�mero fijo de bytes que sea necesario para contener el conjunto del lenguaje para cada una de las letras.
Cuando se mapea desde el tipo char de Java al tipo char de IDL, se puede lanzar la excepci�n DATA_CONVERSION si el caracter no entra en los 8 bits.
Tipos string IDL: El tipo string IDL puede ser lanzado como una secuencia de tipos char IDL, tambi�n lanza la excepci�n DATA_CONVERSION. El tipo wstring IDL es equivalente a una secuencua de wchars terminada por un wchar NULL.
Un tipo string y un tipo wstring de IDL pueden tener un tama�o fijo o sin m�ximo definido. Si intentamos mapear un java.lang.String a un string IDL de tama�o fijo y el java.lang.String es demasidado largo, se lanzar� una excepci�n MARSHAL.
�Configurar el Mapeo IDL
El mapeo del lenguaje Java a IDL se sit�a en un fichero con extensi�n .idl. El fichero es compilado para que pueda ser accedido por los programas CORBA que necesitan enviar y recibir datos. Esta secci�n explica c�mo construir los mapeos para las sentencias de paquete y los tipos de datos Java. La siguiente secci�n en Implementaci�n CORBA de RegistrationServer describe c�mo usar esta informaci�n para configurar el fichero de mapeo IDL para el servidor Registration CORBA.
Paquetes e Interfaces Java: Las sentencias de paquete Java son equivalentes al tipo module de IDL. Este tipo puede ser anidado, lo que resulta en que las clases Java generadas se crean en subdirectorios anidados.
Por ejemplo, si un programa CORBA contiene esta sentencia de paquete:
package registration;
el fichero de mapeo deber�a tener este mapeo a m�dulo IDL para ella:
module registration {
};
Si un programa CORBA contiene una herencia de paquete como esta:
package registration.corba;
su mapeo IDL de m�dulo ser� este:
module registration {
module corba {
};
};
Las clases distribuidas est�n definidas como interfaces Java y se mapean al tipo interface de IDL. IDL no define accesos como public o private que podr�amos encontrar en el lenguaje Java, pero permite descender desde otros interfaces.
Este ejemplo a�ade el interface Java Registration a un registration module IDL.
module registration {
interface Registration {
};
}
Este ejemplo a�ade el interface Java Registration a un registration module IDL, e indica que el interface Registration desciende del interface User.
module registration {
interface Registration: User {
};
}
M�todos Java: Los m�todos Java se mapean a operaciones IDL. Las operaciones IDL son similares a los m�todos Java excepto en que no hay el concepto de control de acceso. Tambi�n tenemos que ayudar al compilador IDL especificando qu� par�metros son de entrada in, de entrada/salida inout o de salida out, definidos de esta forma:
- in - El par�metro se pasa dentro del m�todo pero no se modifica.
- inout - El par�metro se pasa al m�todo y se podr�a devolver modificado.
- out - El par�metro se podr�a devolver modificado.
Este mapeo IDL incluye los m�todos de los interfaces Registration y RegistrationHome a operaciones IDL usando un tipo m�dulo IDL.
module registration {
interface Registration {
boolean verifyPassword(in string password);
string getEmailAddress();
string getUser();
long adjustAccount(in double amount);
double getBalance();
};
interface RegistrationHome {
Registration findByPrimaryKey(
in RegistrationPK theuser)
raises (FinderException);
}
}
Arrays Java: Los Arrays Java son mapeados a los tipos array o sequence IDL usando una definici�n de tipo.
Este ejemplo mapea el array Java double balances[10] a un tipo array IDL del mismo tama�o.
typedef double balances[10];
Estos ejemplos mapean el array Java double balances[10] a un tipo sequence IDL. El primer typedef sequence es un ejemplo de secuencia sin l�mite, y el segundo tiene el mismo tama�o que el array.
typedef sequence<double> balances; typedef sequence<double,10> balances;
Excepciones Java: Las excepciones Java son mapeadas a excepciones IDL. Las operaciones usan exceptions IDL especific�ndolas como del tipo raises.
Este ejemplo mapea la CreateException desde la aplicaci�n subastas al tipo exception IDL, y le a�ade el tipo raises a la operaci�n. Las excepciones IDL siguen las sintaxis C++, por eso en lugar de lanzar una excepci�n (como se har�a en lenguaje Java), la operaci�n alcanza (raise) una excepci�n.
exception CreateException {
};
interface RegistrationHome {
RegistrationPK create(
in string theuser,
in string password,
in string emailaddress,
in string creditcard)
raises (CreateException);
}
�Otros Tipos IDL
Estos otros tipos b�sicos IDL no tienen un equivalente exacto en el lenguaje Java. Muchos de estos deber�an sernos familiares si hemos usado C � C++. El lenguaje Java proporciona mapeo para estos tipos porque los programas escritos en Java pueden recibir datos desde programas escritos en C � C++.
- attribute IDL
- enum IDL
- struct IDL
- union IDL
- Any IDL
- Principal IDL
- Object IDL
atributo IDL: El tipo attribute IDL es similiar a los m�todos get y set usados para acceder a los campos en el software de JavaBeans.
En el caso de un valor declarado como un atributo IDL, el compilador IDL genera dos m�todos con el mismo nombre que el atributo IDL. Un m�todo devuelve el campo y otro lo selecciona. Por ejemplo, este tipo attribute:
interface RegistrationPK {
attribute string theuser;
};
define estos m�todos:
//return user String theuser(); //set user void theuser(String arg);
enum IDL: El lenguaje Java tiene una clase Enumeration para representar una colecci�n de datos. El tipo enum IDL es diferente porque es declarado como un tipo de dato y no una colecci�n de datos.
El tipo enum IDL es una lista de valores que pueden se referenciados por un nombre en vez de por su posici�n en la lista. En el ejemplo, podemos ver que referirnos al c�digo de estado de un enum IDL por un nombre es mucho m�s legible que hacerlo por su n�mero. Esta l�nea mapea los valores static final int de la clase final LoginError. Podemos referirnos a estos valores como lo har�amos con un campo est�tico:LoginError.INVALjdcbook_USER.
enum LoginError {
INVALjdcbook_USER, WRONG_PASSWORD, TIMEOUT};
Aqu� hay una versi�n del tipo enum que incluye un subrayado anterior para que pueda ser usado en sentencias switch:
switch (problem) {
case LoginError._INVALjdcbook_USER:
System.out.println("please login again");
break;
}
struct IDL: Un tipo struct IDL puede ser comparado con una clase Java que s�lo tiene campos, que es c�mo lo mapea el compilador IDL.
Este ejemplo declara una struct IDL. Observamos que los tipos IDL pueden referenciar otros tipos IDL. En este ejemplo LoginError viene del tipo enum declarado arriba.
struct ErrorHandler {
LoginError errortype;
short retries;
};
union IDL: Una union IDL puede representar un tipo de una lista de tipos definidos para esa uni�n. La union mapea a una clase Java del mismo nombre con un m�todo discriminator usado para determinar el tipo de esa uni�n.
Este ejemplo mapea la uni�n GlobalErrors a una clase Java con el nombre GlobalErrors. Se podr�a a�adir un case por defecto case: DEFAULT para manejar cualquier elemento que podr�a estar en el tipo LoginErrors enum, y no est� especificado con una sentencia case aqu�.
union GlobalErrors switch (LoginErrors) {
case: INVALjdcbook_USER: string message;
case: WRONG_PASSWORD: long attempts;
case: TIMEOUT: long timeout;
};
En un programa escrito en lenguaje Java, la clase uni�n GlobalErrors se crea de esta forma:
GlobalErrors ge = new GlobalErrors();
ge.message("please login again");
El valor INVALjdcbook_USER se recupera de esta forma:
switch (ge.discriminator().value()) {
case: LoginError._INVALjdcbook_USER:
System.out.println(ge.message);
break;
}
Tipo Any: si no sabemos que tipo est� siento pasado o devuelto desde una operaci�n, podemos usar el tipo Any, que representa cualquier tipo IDL. La siguiente operaci�n retorna y pasa un tipo desconocido:
interface RegistrationHome {
Any customSearch(Any searchField, out count);
};
Para crear un tipo Any, se pide el tipo al "Object Request Broker" (ORB). Para seleccionar un valor de un tipo Any, usamos un m�todo insert_<type>. Para recuperar un valor, usamos el m�todo extract_<type>.
Este ejemplo pide un objeto del tipo Any, y usa el m�todo insert_type para seleccionar un valor.
Any sfield = orb.create_any(); sfield.insert_long(34);
El tipo Any tiene un valor TypeCode asignado que puede consultarse usando type().kind().value() sobre el objeto. El siguiente ejemplo muestra una prueba del TypeCode double. Este ejemplo incluye una referencia al TypeCode IDL encontrado que contiene el objeto Any. El TypeCode se usa para todos los objetos. Podemos analizar el tipo de un objeto CORBA usando los m�todos _type() o type().
public Any customSearch(Any searchField, IntHolder count){
if(searchField.type().kind().value() == TCKind._tk_double){
// return number of balances greater than supplied amount
double findBalance=searchField.extract_double();
Principal: El tipo Principal identifica al propietario de un objeto CORBA, por ejemplo, un nombre de usuario. El valor puede consultarse desde el campo request_principal de la clase RequestHeader para hacer la identificaci�n.
Object: El tipo Object es un objeto CORBA. Si necesitamos enviar objetos Java, tenemos que traducirlos a un tipo IDL o usar un mecanismo para serializarlos cuando sean transferidos.
�CORBA en la Aplicaci�n de Subasta
El RegistrationBean controlado por contenedor de la aplicaci�n subasta es totalmente reemplazado con un RegistrationServer solitario CORBA que implementa el servicio de registro. El RegistrationServer CORBA est� construido creando y compilando ficheros de mapeo IDL para que los programas clientes se puedan comunicar con el servidor de registros.
Los ficheros SellerBean.java y AuctionServlet.java se han actualizado para que busquen el servidor de registro CORBA.
�Implementaci�n del RegistrationServer CORBA
Esta secci�n describe el fichero Registration.idl, que mapea los interfaces remotos RegistrationHome y Registration desde la aplicaci�n de subastas de JavaBeans de Enterprise a sus equivalentes IDL y muestra como compilar el fichero Registration.idl en las clases del servidor de registos CORBA.
El servidor de registros CORBA implementa los m�todos create y findByPrimaryKey desdel el fichero RegistrationBean.java original, y lo ampl�a con los dos m�todos siguientes para ilustrar las retrollamadas CORBA, y como usar el tipo Any.
- findLowCreditAccounts(in ReturnResults rr), que usa una callback para devolver una lista de cuentas con bajo saldo.
- any customSearch(in any searchfield, out long count), que devuelve un resultado de b�squeda diferente dependiendo del tipo de campo enviado.
�Fichero de Mapeos IDL
Aqu� est� el fichero Registration.idl que mapea los tipos de datos y m�todos usados en los programas RegistrationHome y Registration a sus equivalentes IDL.
module registration {
interface Registration {
boolean verifyPassword(in string password);
string getEmailAddress();
string getUser();
long adjustAccount(in double amount);
double getBalance();
};
interface RegistrationPK {
attribute string theuser;
};
enum LoginError {INVALIDUSER, WRONGPASSWORD, TIMEOUT};
exception CreateException {
};
exception FinderException {
};
typedef sequence<Registration> IDLArrayList;
interface ReturnResults {
void updateResults(in IDLArrayList results)
raises (FinderException);
};
interface RegistrationHome {
RegistrationPK create(in string theuser,
in string password,
in string emailaddress,
in string creditcard)
raises (CreateException);
Registration findByPrimaryKey(
in RegistrationPK theuser)
raises (FinderException);
void findLowCreditAccounts(in ReturnResults rr)
raises (FinderException);
any customSearch(in any searchfield, out long count);
};
};
�Compilar el Fichero de Mapeos IDL
El fichero IDL tiene que ser convertido en clases Java que puedan ser usadas en una red distribuida CORBA. La plataforma Java 2 compila los ficheros .idl usando el programa idltojava. Este programa ser� reemplazado eventualmente con el comando idlj.
Los argumentos -fno-cpp indican que no hay compilador C++ instalado.
idltojava -fno-cpp Registration.idl
Otros compiladores Java IDL tambi�n deber�an funcionar, por ejemplo, jidl de ORBacus puede generar clases que pueden ser usadas por el ORB de Java 2.
�Stubs y Skeletons
Corba y RMI son similares en que la compilaci�n genera un fichero stub para el cliente y un fichero skeleton para el servidor. El stub (o proxy), y el skeleton (o sirviente) se usan para envolver o desenvolver datos entre el cliente y el servidor. El skeleton (o sirviente) est� implementado mediante el servidor. En este ejemplo, el interface RegistrationHome genera una clase _RegistrationHomeImplBase (la clase skeleton o sirviente) que extiende la clase RegistrationServer generada.

Cuando se solicita un objeto CORBA remoto o se llama a un m�todo remoto, la llamada del cliente pasa a trav�s de la clase stub antes de alcanzar el servidor. Este clase proxy invoca la petici�n CORBA para el programa cliente. El siguiente ejemplo es el c�digo generado autom�ticamente por la clase RegistrationHomeStub.java.
org.omg.CORBA.Request r = _request("create");
r.set_return_type(
registration.RegistrationPKHelper.type());
org.omg.CORBA.Any _theuser = r.add_in_arg();
�Object Request Broker
El centro de una red distribuida CORBA es el "Object Request Broker" o ORB. El ORB se encarga de empaquetar y desempaquetar los objetos entre el cliente y el servidor. Otros servicios como Servicios de Nombres y Servicios de Eventos funcionan con el ORB.
La plataforma Java 2 incluye un ORB en la distribuci�n llamado el IDL ORB. Este ORB es diferente de otros muchos ORBs porque no incluye un distintivo de "Basic Object Adapter" (BOA) o "Portable Object Adapter" (POA).
Una adaptador de objetos maneja la creacci�n y ciclo de vida de los objetos en un espacio distribuido CORBA. Esto puede ser comparado con el contenedor del servidor de JavaBeans Enterprise que maneja el ciclo de vida de los beans de entidad y de sesi�n.
Los programas AuctionServlet y SellerBean crean e inicializan un ORB de Java 2 de esta forma:
ORB orb = ORB.init(args, null);
En el programa RegistrationServer, el objeto servidor puede ser distribuido en uni�n con el ORB usando el m�todo connect:
RegistrationServer rs = new RegistrationServer(); orb.connect(rs);
Un objeto conectado a un ORB puede ser eleminado con el m�todo disconnect:
orb.disconnect(rs);
Una vez conectado a un objeto servidor CORBA, el ORB Java2 mantiene vivo el servidor y espera peticiones del cliente para el servidor CORBA.
java.lang.Object sync = new java.lang.Object();
synchronized(sync) {
sync.wait();
}
�Poner Disponible el Servidor CORBA
Aunque este objeto est� ahora siendo mapeado por el ORB, los clientes todav�a no tienen el mecanismo para encontrar el objeto remoto. Esto puede resolverse uniendo el objeto servidor CORBA a un servicio de nombres.
El servicio de nombres Java 2 llamado tnameserv, por defecto usa el puerto 900; sin embargo, este valor puede modificarse seleccionado el argumento -ORBInitialPort portnumber cuando se arranca tnameserv o seleccionando la propiedad org.omg.CORBA.ORBInitialPort cuando arrancamos los procesos cliente y servidor.
Las siguientes secciones describen el m�todo main de la clase RegistrationServer.
java.util.Properties props=System.getProperties();
props.put("org.omg.CORBA.ORBInitialPort", "1050");
System.setProperties(props);
ORB orb = ORB.init(args, props);
Las siguientes l�neas muestran que la referencia inicial de nombres es inicializada por la petici�n del servicio llamado NameService. El NamingContext es recuperado y el nombre construido y unido al servicio de nombres como elementos NameComponent. El nombre de este ejemplo tiene una ra�z llamada auction que es este objeto que se est� uniendo como RegistrationBean desde la ra�z auction. El nombre podr�a ser comparado por una clase mediante el nombre de auction.RegistrationBean.
org.omg.CORBA.Object nameServiceObj =
orb.resolve_initial_references("NameService") ;
NamingContext nctx =
NamingContextHelper.narrow(nameServiceObj);
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
NameComponent[] tempComponent = new NameComponent[1];
for(int i=0; i < fullname.length-1; i++ ) {
tempComponent[0]= fullname[i];
try{
nctx=nctx.bind_new_context(tempComponent);
}catch (Exception e){
System.out.println("bind new"+e);}
}
tempComponent[0]=fullname[fullname.length-1];
try{
nctx.rebind(tempComponent, rs);
}catch (Exception e){
System.out.println("rebind"+e);
}
�Conectar un nuevo ORB
El ORB IDL de Java 2 realmente no incluye ninguno de los servicios disponibles en muchos otros ORBs comerciales como los servicios de seguridad o eventos (notificaci�n). Podemos usar otro ORB en el runtime de Java 2 configurando dos propiedades e incluyendo cualquier codigo de objeto adaptador que sea necesario.
Usar un nuevo ORB en el servidor de registros requiere que las propiedades org.omg.CORBA.ORBClass y org.omg.CORBA.ORBSingletonClass apunten a las clases ORB apropiadas. En este ejemplo se usa el ORB ORBacus en lugar del ORB IDL de Java 2. Para usar otro ORB, el c�digo de abajo deber�a conectarse dentro del m�todo RegistrationServer.main.
En el c�digo de ejemplo, se usa un ORB SingletonClass. Este ORB no es un ORB completo, y su uso primario es como factor�a para TypeCodes. La llamada a ORB.init() en la �ltima l�nea crea el ORB Singleton.
Properties props= System.getProperties();
props.put("org.omg.CORBA.ORBClass",
"com.ooc.CORBA.ORB");
props.put("org.omg.CORBA.ORBSingletonClass",
"com.ooc.CORBA.ORBSingleton");
System.setProperties(props);
ORB orb = ORB.init(args, props) ;
En el IDL de Java 2, no hay un objeto adaptador distinto. Como se muestra en el segmento de c�digo inferior, usar el "Basic Object Adapter" desde ORBacus requiere un conversi�n expl�cita al ORB ORBacus, El "Broker Object Architecture" (BOA) es notificado de que el objeto ya est� distribuido llamando al m�todo impl_is_ready(null).
BOA boa = ((com.ooc.CORBA.ORB)orb).BOA_init(
args, props);
...
boa.impl_is_ready(null);
Aunque los dos ORBs ORBSingletonClass y ORBClass construyen el nombre del objeto usando NameComponent, tenemos que usar un diferente servicio de nombres ORBacus. El servicio CosNaming.Server se arranca de la siguiente forma, donde el par�metro -OAhost es opcional:
java com.ooc.CosNaming.Server -OAhost localhost -OAport 1060
Una vez arrancado el servicio de nombres, los programas cliente y servidor pueden encontrar el servicio de nombres usando el protocolo IIOP hacia el host y el puerto nombrados cuando se arranc� el servicio de nombrado:
java registration.RegistrationServer -ORBservice NameService iiop://localhost:1060/DefaultNamingContext
�Acceso al Servicio de Nombres por los Clientes CORBA
Los cliente CORBA acceden al servicio de nombres de una forma similar a como lo hace el servidor, excepto que en lugar de unir un nombre, el cliente resuelve el nombre construido desde el NameComponents.
Las clases AuctionServlet y SellerBean usan el siguiente c�digo para buscar el servidor CORBA:
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
RegistrationHome regRef =
RegistrationHomeHelper.narrow(
nctx.resolve(fullname));
En el caso del ORB ORBacus, los clientes tambi�n necesitan un "Basic Object Adapter" si se usan retrollamadas en el m�todo SellerBean.auditAccounts. El contexto de nombrado tambi�n se configura de forma diferente para el servidor ORBacus arrancado anteriormente:
Object obj = ((com.ooc.CORBA.ORB)orb).get_inet_object ( "localhost", 1060, "DefaultNamingContext"); NamingContext nctx = NamingContextHelper.narrow(obj);
�Clases Helper y Holder
Las referencias a objetos remotos en CORBA usan una clase Helper para recuperar un valor desde ese objeto. Un m�todo usado comunmente es el m�todo Helper, que asegura que el objeto est� encastado correctamente.
Las clases Holder contienen valores devueltos cuando se usan par�metros inout o out en un m�todo. El llamador primero ejemplariza la clase Holder apropiada para ese tipo y recupera el valor desde la clase cuando la llamada retorna. En el siguiente ejemplo, el valor del contador customSearch se configura y recupera despu�s de que se haya llamado a customSearch. En el lado del servidor el valor del contador se selecciona llamando a count.value=newvalue.
IntHolder count= new IntHolder();
sfield=regRef.customSearch(sfield,count);
System.out.println("count now set to "+count.value);
�Recolecci�n de Basura
Al contrario que RMI, CORBA no tiene un mecanismo de recolecci�n de basura distribuido. Las referencias a un objeto son locales al proxy del cliente y al sirviente del servidor. Esto significa que cada M�quina Virtual Java (JVM) es libre de reclamar un objeto y recoger la basura si no tiene m�s referencias sobre �l. Si un objeto no es necesario en el servidor, necesitamos llamar a orb.disconnect(object) para permitir que el objeto sea recolectado.
�Retrollamadas (Callbacks) CORBA
El nuevo m�todo findLowCreditAccounts es llamado desde el AuctionServlet usando la URL http://localhost:7001/AuctionServlet?action=auditAccounts.
El m�todo AuctionServlet.auditAccounts llama al m�todo SellerBean.auditAccounts, que devuelve un ArrayList de registros de Registration.
//AuctionServlet.java
private void auditAccounts(ServletOutputStream out,
HttpServletRequest request) throws IOException{
// ...
SellerHome home = (SellerHome) ctx.lookup("seller");
Seller si= home.create();
if(si != null) {
ArrayList ar=si.auditAccounts();
for(Iterator i=ar.iterator(); i.hasNext();) {
Registration user=(Registration)(i.next());
addLine("<TD>"+user.getUser() +
"<TD><TD>"+user.getBalance()+
"<TD><TR>", out);
}
addLine("<TABLE>", out);
}
El objeto SellerBean llama al m�todo CORBA RegistrationHome.findLowCreditAccounts implementado en el fichero RegistrationServer.java, y se pasa una referencia a s� mismo. La referencia es pasada siempre que la clase SellerBean implemente el interface ReturnResults declarado en el Registration.idl.
//SellerBean.java
public ArrayList auditAccounts() {
try{
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
RegistrationHome regRef =
RegistrationHomeHelper.narrow(
nctx.resolve(fullname));
regRef.findLowCreditAccounts(this);
synchronized(ready) {
try{
ready.wait();
}catch (InterruptedException e){}
}
return (returned);
}catch (Exception e) {
System.out.println("error in auditAccounts "+e);
}
return null;
}
El m�todo RegistrationServer.findLowCreditAccounts recupera los registros de usuario desde la tabla Registration de la base de datos que tengan un valor de cr�dito menor de tres. Entonces devuelve la lista de registros Registration en un ArrayList llamando al m�todo SellerBean.updateResults que tiene una referencia a ella.
//RegistrationServer.java
public void findLowCreditAccounts(
final ReturnResults client)
throws Finder Exception {
Runnable bgthread = new Runnable() {
public void run() {
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
ArrayList ar = new ArrayList();
try{
con=getConnection();
ps=con.prepareStatement(
"select theuser,
balance from registration
where balance < ?");
ps.setDouble(1, 3.00);
ps.executeQuery();
rs = ps.getResultSet();
RegistrationImpl reg=null;
while (rs.next()) {
try{
reg= new RegistrationImpl();
}catch (Exception e) {
System.out.println("creating reg"+e);
}
reg.theuser = rs.getString(1);
reg.balance = rs.getDouble(2);
ar.add(reg);
}
rs.close();
RegistrationImpl[] regarray =
(RegistrationImpl [])ar.toArray(
new RegistrationImpl[0]);
client.updateResults(regarray);
}catch (Exception e) {
System.out.println(
"findLowCreditAccounts: "+e);
return;
}
finally {
try{
if(rs != null) {
rs.close();
}
if(ps != null) {
ps.close();
}
if(con != null) {
con.close();
}
}catch (Exception ignore) {}
}
}//run
};
Thread t = new Thread(bgthread);
t.start();
}
El m�todo SellerBean.updateResults actualiza el ArrayList global de registros de Registration devuelto por el objeto RegistrationServer y notifica al m�todo SellerBean/auditAccounts que puede devolver este ArrayList de registros Registration al AuctionServlet.
public void updateResults(Registration[] ar)
throws registration.FinderException {
if(ar == null) {
throw new registration.FinderException();
}
try{
for(int i=0; i< ar.length; i++) {
returned.add(ar[i]);
}
}catch (Exception e) {
System.out.println("updateResults="+e);
throw new registration.FinderException();
}
synchronized(ready) {
ready.notifyAll();
}
}
�Usar el Tipo Any
El m�todo RegistrationServer.customSearch usa el tipo Any de IDL para pasar y devolver resultados. El customSearch es llamado por el AuctionServlet de esta forma:
http://localhost.eng.sun.com:7001/
AuctionServlet?action=customSearch&searchfield=2
El par�metro searchfield puede ser seleccionado como un n�mero o un string. El m�todo AuctionServlet.customFind pasa el campo de b�squeda directamente al m�todo SellerBean.customFind que recupera un String que luego es mostrado al usuario:
private void customSearch(ServletOutputStream out,
HttpServletRequest request)
throws IOException{
String text = "Custom Search";
String searchField=request.getParameter(
"searchfield");
setTitle(out, "Custom Search");
if(searchField == null ) {
addLine("Error: SearchField was empty", out);
out.flush();
return;
}
try{
addLine("<BR>"+text, out);
SellerHome home = (SellerHome)
ctx.lookup("seller");
Seller si= home.create();
if(si != null) {
String displayMessage=si.customFind(
searchField);
if(displayMessage != null ) {
addLine(displayMessage+"<BR>", out);
}
}
}catch (Exception e) {
addLine("AuctionServlet customFind error",out);
System.out.println("AuctionServlet " +
"<customFind>:"+e);
}
out.flush();
}
El m�todo SellerBean.customFind llama al objeto RegistrationHome implementado en la clase RegistrationServer.java, y dependiendo de si el searchField puede ser convertido a un double o a un string, inserta este valor dentro de un objeto del tipo Any. El objeto Any se crea mediante una llamada al ORB, orb.create_any();
El m�todo customFind tambi�n usa un par�metro out, count, del tipo int que devuelve el n�mero de registros encontrados. El valor de count se recupera usando count.value cuando la llamada retorna:
//SellerBean.java
public String customFind(String searchField)
throws javax.ejb.FinderException,
RemoteException{
int total=-1;
IntHolder count= new IntHolder();
try{
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
RegistrationHome regRef =
RegistrationHomeHelper.narrow(
nctx.resolve(fullname));
if(regRef == null ) {
System.out.println(
"cannot contact RegistrationHome");
throw new javax.ejb.FinderException();
}
Any sfield=orb.create_any();
Double balance;
try{
balance=Double.valueOf(searchField);
try {
sfield.insert_double(balance.doubleValue());
}catch (Exception e) {
return("Problem with search value"+balance);
}
sfield=regRef.customSearch(sfield,count);
if(sfield != null ) {
total=sfield.extract_long();
}
return(total+"
accounts are below optimal level from" +
count.value+" records");
}catch (NumberFormatException e) {
sfield.insert_string(searchField);
Registration reg;
if((reg=RegistrationHelper.extract(
regRef.customSearch(
sfield,count)))
!= null ) {
return("Found user "+reg.getUser() +"
who has email address "+
reg.getEmailAddress());
}else {
return("No users found who have email address " +
searchField);
}
}
}catch(Exception e){
System.out.println("customFind problem="+e);
throw new javax.ejb.FinderException();
}
}
El valor devuelto desde la llamada a customFind se extrae dentro de un objeto del tipo Any y se construye un String con la salida mostrada al usuario. Para los tipos sencillos se puede usar el m�todo extract_<type> de Any. Sin embargo, para el tipo Registration, se usa la clase RegistrationHelper.
Registration reg =
RegistrationHelper.extract(
regRef.customSearch(sfield,count))
El m�todo RegistrationServer.customSearch determina el tipo del objeto que est� siendo pasado en el par�metro searchField chequeando el .type().kind().value() del objeto Any.
if(searchField.type().kind().value() ==
TCKind._tk_double)
Finalmente, como el m�todo customSearch devuelve un objeto del tipo Any, se requiere una llamada a orb.create_any(). Para tipos sencillos como double, se usa el m�todo insert_<type>. Para el tipo Registration , se usa la clase RegistrationHelper: RegistrationHelper.insert(returnResults, regarray[0]).
//RegistrationServer.java
public Any customSearch(Any searchField,
IntHolder count){
Any returnResults= orb.create_any();
int tmpcount=count.value;
if(searchField.type().kind().value() ==
TCKind._tk_double){
// return number of balances greater
// than supplied amount
double findBalance=searchField.extract_double();
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
try{
con=getConnection();
ps=con.prepareStatement("select count(*) from
registration where balance < ?");
ps.setDouble(1, findBalance);
ps.executeQuery();
rs = ps.getResultSet();
if(rs.next()) {
tmpcount = rs.getInt(1);
}
count.value=tmpcount;
rs.close();
}catch (Exception e) {
System.out.println("custom search: "+e);
returnResults.insert_long(-1);
return(returnResults);
}
finally {
try{
if(rs != null) { rs.close(); }
if(ps != null) { ps.close(); }
if(con != null) { con.close(); }
} catch (Exception ignore) {}
}
returnResults.insert_long(tmpcount);
return(returnResults);
}else if(searchField.type().kind().value() ==
TCKind._tk_string) {
// return email addresses that match supplied address
String findEmail=searchField.extract_string();
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
ArrayList ar = new ArrayList();
RegistrationImpl reg=null;
try{
con=getConnection();
ps=con.prepareStatement("select theuser,
emailaddress from registration
where emailaddress like ?");
ps.setString(1, findEmail);
ps.executeQuery();
rs = ps.getResultSet();
while (rs.next()) {
reg= new RegistrationImpl();
reg.theuser = rs.getString(1);
reg.emailaddress = rs.getString(2);
ar.add(reg);
}
rs.close();
RegistrationImpl[] regarray =
(RegistrationImpl [])ar.toArray(
new RegistrationImpl[0]);
RegistrationHelper.insert(
returnResults,
regarray[0]);
return(returnResults);
}catch (Exception e) {
System.out.println("custom search: "+e);
return(returnResults);
}
finally {
try{
if(rs != null) { rs.close(); }
if(ps != null) { ps.close(); }
if(con != null) { con.close(); }
} catch (Exception ignore) {}
}
}
return(returnResults);
}
�Conclusi�n
Como hemos podido ver, convertir una aplicaci�n para que use RMI o CORBA requiere muy pocos cambios en el coraz�n del programa. La principal diferencia ha sido la inicializaci�n y el servicio de nombres. Mediante la abstracci�n de estas dos �reas en nuestra aplicaci�n fuera de la l�gica del negocio podemos migrar f�cilmente entre diferentes arquitecturas de objetos distribuidos.
�JDBC
La aplicaci�n de subasta con JavaBeans Enterpise y con sus dos variantes de "Remote Method Invocation" (RMI) y "Common Object Request Broker" (CORBA) han usado llamadas sencillas de JDBC JDBC para actualizar y consultar informaci�n desde una base de datps usando una conexi�n JDBC. Por defecto, el acceso a bases de datos JDBC implica abrir una conexi�n con la base de datos, ejecutar comandos SQL en un sentencia, procesar los datos devueltos y cerrar la conexi�n con la base de datos.
En conjunto, la aproximaci�n por defecto funciona bien para bajos vol�menes de acceso a la base de datos, pero �c�mo podemos manejar un gran n�mero de peticiones que actualizan muchas tablas relacionadas a la vez y a�n as� asegurar la integridad de los datos? Esta secci�n explica c�mo hacerlo con los siguientes t�picos:
�Drivers JDBC
La conexi�n con la base de datos est� manejada por la clase Driver JDBC. El SDK de Java contiene s�lo un driver JDBC, un puente jdbc-odbc que comunica con un driver "Open DataBase Conectivity" (ODBC) existente. Otras bases de datos necesitan un driver JDBC esp�cifico para esa base de datos.
Para obtener un idea general de lo que hacer un driver JDBC, podemos examinar el fichero JDCConnectionDriver.java. La clase JDCConnectionDriver implemta la clase java.sql.Driver y act�a como un driver "pass-through" re-enviando peticiones JDBC al driver JDBC real de la base de datos. La clase driver JDBC se carga con un llamada a Class.forName(drivername).
Estas l�neas de c�digo muestran c�mo cargar tres clases diferentes de drivers JDBC:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Class.forName("postgresql.Driver");
Class.forName("oracle.jdbc.driver.OracleDriver");
Cada driver JDBC est� configurado para entender una URL espec�fica, por eso se pueden cargar varios drivers JDBC a la vez. Cuando especificamos una URL en el momento de la conexi�n, se selecciona el primer driver JDBC que corresponda.
El puente jdbc-odbc acepta URLS que empiecen con jdbc:odbc: y usa el siguiente campo de esa URL para especificar el nombre de la fuente de los datos. Este nombre identifica el esquema de la base de datos particular a la que queremos acceder. La URL tambi�n puede incluir m�s detalles sobre c�mo contactyar con la base de datos e introducir la cuenta.
//access the ejbdemo tables String url = "jdbc:odbc:ejbdemo";
El siguiente ejemplo contiene la informaci�n de Oracle SQL*net sobre una base de datos particular llamada ejbdemo en la m�quina dbmachine:
String url = "jdbc:oracle:thin:user/password@( description=(address_list=( address=(protocol=tcp) (host=dbmachine)(port=1521)))(source_route=yes) (connect_data=(sid=ejbdemo)))";
Este siguiente ejemplo usa mysql para conectar con la base de datos ejbdemo en la m�quina local. Tambi�n se incluyen los detalles del nombre de usuario y la password para el login.
String url =
"jdbc:mysql://localhost/ejbdemo?user=user;
password=pass";
Los drivers JDBC se dividen en cuatro tipos. Tambi�n se pueden categorizar como puro java o drivers peque�os para indicar si son usados por aplicaciones clientes (drivers puro java) o por applets (drivers peque�os).
�Drivers del Tipo 1
Los drivers JDBC del tipo 1 son drivers puente como el puente jdbc.odbc. Estos drivers utilizan un intermediario como el ODBC para transferir las llamadas SQL a la base de datos. Los drivers puente cuentan con c�digo nativo, aunque la librer�a de c�digo nativo del puente jdbc-odbc forma parte de la M�quina Virtual Java 2.
�Drivers del Tipo 2
Los drivers del tipo 2 usan el API existente de la base de datos para comunicarla con el cliente. Aunque los drivers del tipo 2 son m�s r�pidos que los del tipo 1, los del tipo 2 usan c�digo nativo y requieren permisos adicionales para funcionar en un applet.
Un driver del tipo 2 podr�a necesitar c�digo de base de datos en el lado del cliente para conectar a trav�s de la red.
�Drivers del Tipo 3
Los Drivers del tipo 3 llaman al API de la base de datos en el servidor. Las peticiones JDBC desde el cliente son primero comprobadas por el Driver JDBC en el servidor para ejecutarse. Los drivers del tipo 3 y 4 pueden usarse en clientes applets ya que no necesitan c�digo nativo.
�Drivers del Tipo 4
El nivel m�s alto de drivers reimplementa el API de red para base de datos en el lenguaje Java. Los Drivers del tipo 4 tambi�n pueden usarse en clientes applets porque no necesitan c�digo nativo.
�Conexiones a Bases de Datos
Una conexi�n con una base de datso puede establecerese con un llamada al m�todo DriverManager.getConnection. La llamada toma una URL que identifica la base de datos, y opcionalmente el nombre de usuario y la password para la base de datos.
Connection con = DriverManager.getConnection(url); Connection con = DriverManager.getConnection(url, "user", "password");
Despu�s de establecer la conexi�n, se puede ejecutar una sentencia contra la base de datos. Los resultados de la sentencias pueden recuperarse y cerrarse la conexi�n.
Una caracter�sticas �til de la clase DriverManager es el m�todo setLogStream. Podemos usar este m�todo para generar informaci�n de seguimiento para ayudarnos a dignosticar problemas de conexi�n que normalmente no ser�an visibles. Para generar la informaci�n de seguimiento, s�lo tenemos que llamar al m�todo de esta forma:
DriverManager.setLogStream(System.out);
La secci�n Connection Pooling en el cap�tulo 8 muestra c�mo podemos mejorar las conexi�n JDBC sin cerrrar la conexi�n una vez completada la sentencia. Cada conexi�n JDBC a una base de datos provoca una sobrecarga al abrir un nuevo socket y usar el nombre de usuario y la password para login en la base de datos. La reutilizaci�n de las conexiones reduce la sobrecarga. Las colas de Conexiones mantienen una lista de conexiones abiertas y limpia cualquier conexi�n que no pueda ser reutilizada.
�Sentencias
Hay tres tipos b�sicos de sentencias SQL usadas en el API JDBC: CallabelStatement, Statement, y PreparedStatement. Cuando se env�a una sentencias Statement o PreparedStatement a la base de datos, el driver la traduce a un formato que la base de datos pueda reconocer.
�Sentencias Callable
Una vez que hemos establecido una conexi�n con una base de datos, podemos usar el m�todo Connection.prepareCall para crear una sentencia callable. Estas sentencias nos permite ejecutar prodecimientos almacenados SQL.
El siguiente ejemplo crea un objeto CallableStatement con tres par�metros para almacenar informaci�n de la cuenta de login:
CallableStatement cs =
con.prepareCall("{call accountlogin(?,?,?)}");
cs.setString(1,theuser);
cs.setString(2,password);
cs.registerOutParameter(3,Types.DATE);
cs.executeQuery();
Date lastLogin = cs.getDate(3);
�Statements
El interface Statement nos permite ejecutar una simple sentencias SQL sin par�metros. Las instrucciones SQL son insertadas dentro del objeto Statement cuando se llama al m�todo Statement.executeXXX method.
Sentencias Query: Este segmento de c�digo crea un objeto Statement y llama al m�todo Statement.executeQuery para seleccionar texto desde la base de datos dba. El resultado de la consulta se devuelve en un objeto ResultSet. C�mo recuperar los resultados desde este objeto ResultSet se explica m�s abajo en la Hoja de Resultados
Statement stmt = con.createStatement();
ResultSet results = stmt.executeQuery(
"SELECT TEXT FROM dba ");
Sentencias Update: Este segmento de c�digo crea un objeto Statement y llama al m�todo Statement.executeUpdate para a�adir una direcci�n de email a una tabla de la base de datos dba:
String updateString =
"INSERT INTO dba VALUES (some text)";
int count = stmt.executeUpdate(updateString);
�Setencias Prepared
El interface PreparedStatement desciende del interface Statement y usa una plantilla para crear peticiones SQL. Se usa una PreparedStatement para enviar sentencias SQL precompiladas con uno o m�s par�metros.
Query PreparedStatement: Creamos un objeto PreparedStatement especificando la definici�n de plantilla y la situaci�n de los par�metros. Los datos de los par�metros se insertan dentro del objeto PreparedStatement llamando a sus m�todos setXXX y especificando el par�metro y su dato. Las instrucciones SQL y los par�metros son enviados a la base de datos cuando se llama al m�todo executeXXX.
Este segmento de c�digo crea un objeto PreparedStatement para seleccionar datos de usuarios basados en la direcci�n email del usuario. El interrogante ("?") indica que este sentencia tiene un par�metro:
PreparedStatement pstmt = con.prepareStatement("
select theuser from
registration where
emailaddress like ?");
//Initialize first parameter with email address
pstmt.setString(1, emailAddress);
ResultSet results = ps.executeQuery();
Una vez que se ha inicializado la plantilla PreparedStatement s�lo se insertan los valores modificados para cada llamadas:
pstmt.setString(1, anotherEmailAddress);
Nota: No todos los drivers de bases de datos compilan sentencias preparadas.
Update PreparedStatement: Este segmento de c�digo crea un objeto PreparedStatement para actualizar el registro de un vendedor. La plantilla tiene cinco par�metros, que se seleccionan con cinco llamadas a los m�todos PreparedStatement.setXXX apropiados.
PreparedStatement ps = con.prepareStatement(
"insert into registration(theuser, password,
emailaddress, creditcard,
balance) values (
?, ?, ?, ?, ?)");
ps.setString(1, theuser);
ps.setString(2, password);
ps.setString(3, emailaddress);
ps.setString(4, creditcard);
ps.setDouble(5, balance);
ps.executeUpdate();
�Cachear los Resultados de la Base de Datos
El concepto PreparedStatement de reutilizar peticiones puede extenderse al cacheo de resultados de una llamada JDBC. Por ejemplo, una descripci�n de un �tem de la subastas permanece igual hasta que el vendedor lo cambia. Si el �tem recibe cientos de peticiones, el resultado de la sentencia: query "select description from auctionitems where item_id='4000343'" podr�a ser almacenado de forma m�s eficiente en un tabla hash.
Almacenar resultados en una tbal hash requiere que la llamada JDBC sea interceptada antes de crear una sentencia real que devuelva los resultados cacheados, y la entrada del cach� debe limpiarse si hay una actualizaci�n correspondiente con ese item_id.
�Hoja de Resultados
El interface ResultSet maneja accesos a datos devueltos por una consulta. Los datos devueltos son igual a una l�nea de la base de la tabla de la base de datos. Algunas consultas devuelven una l�nea, mientras que muchas consultas devuelven m�ltiples l�neas de datos.
Se utilizan los m�todos getType para recuperar datos desde columnas espec�ficas para cada fila devuelta en la consulta. Este ejemplo recupera la columna TEXT de todas las tablas con una columna TEXT en la base de datosdba. El m�todo results.next mueve hasta la siguiente fila recuperada hasta que se hayan procesado todas las filas devueltas:
Statement stmt = con.createStatement();
ResultSet results = stmt.executeQuery(
"SELECT TEXT FROM dba ");
while(results.next()){
String s = results.getString("TEXT");
displayText.append(s + "\n");
}
stmt.close();
�Hoja de Resultados Scrollable
Antes del JDBC 2.0, los dirvers JDBC devolv�an hojas de resultado de s�lo lectura con cursores que s�lo se mov�an en una direcci�n, hacia adelante. Cada elemento era recuperado mediante una llamada al m�todo next de la hoja de resultados.
JDBC 2.0 introduce las hojas de resultados scrollables cuyos valores pueden ser le�dos y actualizados si as� lo permite la base de datos original. Con las hojas de resultados scrollables, cualquier fila puede ser seleccionada de forma aleatorio, y nos podemos mover por la hoja de resultados hacia adelante y hacia atr�s.
Una ventaja de la nueva hoja de resultados es que podemos actualizar un conjunto de filas correspondientes son tener que enviar una llamada adicional a executeUpdate. Las actualizaciones se hacen llamando a JDBC y no se necesitan comandos SQL personalinzados. Esto aumenta la portabilidad del c�digo de la base de datos que creemos.
Tanto Statements como PreparedStatements tienen un constructor adicional que acepta un par�metro tipo scroll y otro tipo update. El valor del tipo scroll puede ser uno de los siguientes valores:
- ResultSet.TYPE_FORWARD_ONLY
Comportamiento por defecto en JDBC 1.0, la aplicaci�n s�lo puede llamar a next() sobre la hoja de resultados.
- ResultSet.SCRol>L_SENSITIVE
La hoja de resultados es totalmente navegable y las actualizaciones son reflejadas en la hoja de resultados cuando ocurren.
- ResultSet.SCRol>L_INSENSITIVE
La hoja de resultados es totalmente navegable pero las actualizaciones son s�lo visibles cuando se cierra la hoja de resultados. Necesitamos crear una nueva hoja de resultados para verlos.
El par�metro del tipo update puede ser uno de estos dos valores:
- ResultSet.CONCUR_READ_ONLY
La hoja de resultados es de s�lo lectura.
- ResultSet.CONCUR_UPDATABLE
La hoja de resultados puede ser actualizada.
Podemos verificar que nuestra base de datos soporta estos tipos llamando al m�todo con.getMetaData().supportsResultSetConcurrency() como se ve aqu�:
Connection con = getConnection();
if(con.getMetaData().supportsResultSetConcurrency(
ResultSet.SCRol>L_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE)) {
PreparedStatement pstmt = con.prepareStatement(
"select password, emailaddress,
creditcard, balance from
registration where theuser = ?",
ResultSet.SCRol>L_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
}
�Navegar por la Hoja de Resultados
La hoja de resultados totalmente scrollable devuelve un cursor que puede moverse usando comandos sencillos. Por defecto el cursor de la hoja de resultados apunta a la fila antes de la primera fila en la hoja de resultados. Una llamada a next() recupera la primera fila de la hoja de resultados. el cursor puede tambien moverse llamando a uno de los siguientes m�todos de ResultSet:
- beforeFirst(): Posici�n por defecto. Pone el cursor antes de la primera fila de la hoja de resultados.
- first(): Pone el cursor en la primera fila de la hoja de resultados.
- last(): Pone el cursor antes de la �ltima fila de la hoja de resultados.
- afterLast() Pone el cursor m�s all� de la �ltima fila de la hoja de resultados. Se llama a previous para movernos hacia atr�s en la hoja de resultados.
- absolute(pos): Pone el cursor en el n�mero de fila indicado donde absolute(1) es la primera fila y absolute(-1) es la �ltima fila.
- relative(pos): Pone el cursor en una l�nea relativa a la posici�n actual donde relative(1) mueve el cursor una fila hacia adelante.
�Actualizar la Hoja de Resultados
Podemos actualizar un valor en la hoja de resultados llamando al m�todo ResultSet.update<type> sobre la fula donde est� posicionado el cursor. El valor del tipo aqu� es el midmo usando cuando se recupera un valor de una hoja de resultados, por ejemplo, updateString actualiza un valor String en la hoja de resultados.
El siguiente c�digo actualiza el balance de un usuario desde la hoja de resultados creada anteriormente. La actualizaci�n s�lo se aplica a la hoja de resultados hasta que se llama a rs.updateRow(), que actualiza la base de datos original. Cerrando la hoja de resultados antes de llamar a updateRow se perder� cualquier edici�n aplicada en la hoja de resultados.
rs.first();
updateDouble("balance",
rs.getDouble("balance") - 5.00);
Insertar una nueva fila usa los mismos m�todos update<type>. La �nica diferencia es que se llama al m�todo rs.moveToInsertRow de que los datos hayan sido inicializados y despu�s se llama a rs.insertRow(). Podemos borrar la fila actual con una llamada a rs.deleteRow().
�Trabajos Batch
Por defecto, cada sentencia JDBC se env�a individualmente a la base de datos. Aparte de las peticiones de red adicionales, este proceso provoca retrasos adicionales si la transaci�n expande varias sentencias. JDBC 2.0 nos permite env�ar varias sentencias a la vez con el m�todo addBatch.
El siguiente m�todo muestra c�mo usar la sentencia addBatch. Las llamadas a stmt.addBatch a�aden sentencias a la Statement original, y la llamada a executeBatch env�a la sentencia completa con todos los ap�ndices a la base de datos.
Statement stmt = con.createStatement();
stmt.addBatch(
"update registration set balance=balance-5.00
where theuser="+theuser);
stmt.addBatch(
"insert into auctionitems(
description, startprice)
values("+description+","+startprice+")");
int[] results = stmt.executeBatch();
La hoja de resultados del m�todo addBatch es un array de cuentas de filas afectadas por cada sentencia ejecutada en el trabajo batch. Si ocurre un problema se lanzar� una java.sql.BatchUpdateException. Se puede obteener un array incompleto de contador de fila de BatchUpdateException llamando a su m�todo getUpdateCounts.
�Almacenar Clases, Im�genes y otros Objetos Grandes
Muchas bases de datos pueden almacenar datos binarios como parte de una fila si el campo es asignado como long raw, longvarbinary, u otro tipo similar. Esto campos pueden ocupar hasta 2 Gigabytes de datos. Esto significa que podemos convertir los datos en un stram binario o un array de bytes, puede ser almacenado o recuperado desde una base de datos como lo ser�a un string o un double.
Esta t�cnica peude usarse para almacenar y recuperar im�genes y objetos Java.
Almacenar y recuperar una imagen: Es muy f�cil almacenar un objeto que puede ser serializado o convertido en un array de bytes. Desafortunadamente java.awt.Image no es Serializable. Sin embargo, como se ve en el siguiente ejemplo de c�digo, podemos almacenar los datos de la imagen en un fichero y almacenar la informaci�n del fichero como bytes en un campo binario de la base de datos.
int itemnumber=400456;
File file = new File(itemnumber+".jpg");
FileInputStream fis = new FileInputStream(file);
PreparedStatement pstmt = con.prepareStatement(
"update auctionitems
set theimage=? where id= ?");
pstmt.setBinaryStream(1, fis, (int)file.length()):
pstmt.setInt(2, itemnumber);
pstmt.executeUpdate();
pstmt.close();
fis.close();
Para recuperar esta imagen y crear un array de bytes que pueda ser pasado a createImage, hacemos los siguiente:
int itemnumber=400456;
byte[] imageBytes;
PreparedStatement pstmt = con.prepareStatement(
"select theimage from auctionitems where id= ?");
pstmt.setInt(1, itemnumber);
ResultSet rs=pstmt.executeQuery();
if(rs.next()) {
imageBytes = rs.getBytes(1);
}
pstmt.close();
rs.close();
Image auctionimage =
Toolkit.getDefaultToolkit().createImage(
imageBytes);
Almacenar y Recuperar un Objeto: Una clase puede ser serializada a un campo binario de la base de datos de la misma forma que se hizo con la imagen en el ejemplo anterior. En este ejemplo, la clase RegistrationImpl se ha modificado para soportar la serializaci�n por defecto a�adiendole implements Serializable a la declaraci�n de la clase.
Luego, se crea un array ByteArrayInputStream para pasarlo como un Stream Binario a JDBC. Para crear el ByteArrayInputStream, RegistrationImpl primero pasa a trav�s de un ObjectOutputStream hacia el ByteArrayInputStream con una llamada a RegistrationImpl.writeObject. Luego el ByteArrayInputStream es convertido a un array de bytes, que puede ser utilizado para crear el ByteArrayInputStream. El m�todo create en RegistrationServer.java se ha modificado de esta forma:
public registration.RegistrationPK create(
String theuser,
String password,
String emailaddress,
String creditcard)
throws registration.CreateException{
double balance=0;
Connection con = null;
PreparedStatement ps = null;;
try {
con=getConnection();
RegistrationImpl reg= new RegistrationImpl();
reg.theuser = theuser;
reg.password = password;
reg.emailaddress = emailaddress;
reg.creditcard = creditcard;
reg.balance = balance;
ByteArrayOutputStream regStore =
new ByteArrayOutputStream();
ObjectOutputStream regObjectStream =
new ObjectOutputStream(regStore);
regObjectStream.writeObject(reg);
byte[] regBytes=regStore.toByteArray();
regObjectStream.close();
regStore.close();
ByteArrayInputStream regArrayStream =
new ByteArrayInputStream(regBytes);
ps=con.prepareStatement(
"insert into registration (
theuser, theclass) values (?, ?)");
ps.setString(1, theuser);
ps.setBinaryStream(2, regArrayStream,
regBytes.length);
if (ps.executeUpdate() != 1) {
throw new CreateException ();
}
RegistrationPK primaryKey =
new RegistrationPKImpl();
primaryKey.theuser(theuser);
return primaryKey;
} catch (IOException ioe) {
throw new CreateException ();
} catch (CreateException ce) {
throw ce;
} catch (SQLException sqe) {
System.out.println("sqe="+sqe);
throw new CreateException ();
} finally {
try {
ps.close();
con.close();
} catch (Exception ignore) {
}
}
}
El objeto es recuperado y resconstruido extrayendo los bytes desde la base de datos, creando un ByteArrayInputStream desde aquellos bytes le�dos desde un ObjectInputStream, y llamando a readObject para crear de nuevo el ejemplar.
El siguiente ejemplo muestra los cambios necesarios en el m�todo RegistrationServer.refresh para recuperar el ejemplar Registration desde la base de datos.
private Registration refresh(RegistrationPK pk)
throws FinderException {
if (pk == null) {
throw new FinderException ();
}
Connection con = null;
PreparedStatement ps = null;
try {
con=getConnection();
ps=con.prepareStatement("
select theclass from
registration where theuser = ?");
ps.setString(1, pk.theuser());
ps.executeQuery();
ResultSet rs = ps.getResultSet();
if(rs.next()){
byte[] regBytes = rs.getBytes(1);
ByteArrayInputStream regArrayStream =
new ByteArrayInputStream(regBytes);
ObjectInputStream regObjectStream =
new ObjectInputStream(
regArrayStream);
RegistrationImpl reg=
(RegistrationImpl)
regObjectStream.readObject();
return reg;
}
else {
throw new FinderException ();
}
} catch (Exception sqe) {
System.out.println("exception "+sqe);
throw new FinderException ();
}
finally {
try {
rs.close();
ps.close();
con.close();
}
catch (Exception ignore) {}
}
}
BLOBs y CLOBs: Almacenar grandes campos en un tabla con otros datos no es necesariamente el lugar �ptimo especialmente si los datos tienen un tama�o variable. una forma de manejar objetos de tama�o grande y variable es con el tipo "Large Objects" (LOBs). Este tipo usa un localizador, esencialmente un puntero, en el registro de la base de datos que apunta al campo real en la base de datos.
Hay dos tipos de LOBs: "Binary Large Objects" (BLOBs) y "Character Large Objects" (CLOBs). Cuando accedemos a BLOB o CLOB, los datos no se copian en el cliente. Para recuperar los datos reales desde una hoja de resultados, tenemos que recuperar el puntero con una llamada a BLOB blob=getBlob(1) o CLOB clob=getClob(1), y luego recuperar los datos con una llamada a blob.getBinaryStream() o clob.getBinaryStream().
�Controlar Transaciones
Por defecto, las sentencias JDBC son procesadas en el modo full auto-commit. Este modo funciona bien para una sola consulta a la base de datos, pero si la operaci�n depende de varias sentencias de la base de datos que todas deben completarse con �xito o toda la operaci�n ser� cancelada, se necesita una transaci�n m�s adecuada.
Una descripci�n de los niveles de aislamiento en la transaci�nse cubre con m�s detalles en el Cap�tulo 3: Maneja�o de Datos y Transaciones. Para usar control de transaciones en la plataforma JDBC, primero necesitamos desactivar el moco "full auto-commit" llamando a:
Connection con= getConnection(); con.setAutoCommit(false);
En este punto, podemos enviar cualquier siguiente sentencia JDBC o deshacer cualquier actualizaci�n llamando al m�todo Connection.rollback. La llamada rollback se sit�a normalmente en el manejador de excepciones, aunque puede situarse en cualquier lugar en le flujo de la transaci�n.
El siguiente ejemplo inserta un �tem en la subasta y decrementa el balance del usuario. Si el balance es menor de cero, se deshace la transaci�n complera y el �tem de susbasta es eliminado.
public int insertItem(String seller,
String password,
String description,
int auctiondays,
double startprice,
String summary) {
Connection con = null;
int count=0;
double balance=0;
java.sql.Date enddate, startdate;
Statement stmt=null;
PreparedStatement ps = null;
try {
con=getConnection();
con.setAutoCommit(false);
stmt= con.createStatement();
stmt.executeQuery(
"select counter from auctionitems");
ResultSet rs = stmt.getResultSet();
if(rs.next()) {
count=rs.getInt(1);
}
Calendar currenttime=Calendar.getInstance();
java.util.Date currentdate=currenttime.getTime();
startdate=new java.sql.Date(
currentdate.getTime());
currenttime.add(Calendar.DATE, auctiondays);
enddate=new java.sql.Date((
currenttime.getTime()).getTime());
ps=con.prepareStatement(
"insert into auctionitems(
id, description, startdate, enddate,
startprice, summary)
values (?,?,?,?,?,?)");
ps.setInt(1, count);
ps.setString(2, description);
ps.setDate(3, startdate);
ps.setDate(4, enddate);
ps.setDouble(5, startprice);
ps.setString(6, summary);
ps.executeUpdate();
ps.close();
ps=con.prepareStatement(
"update registration
set balance=balance -0.50
where theuser= ?");
ps.setString(1, seller);
ps.close();
stmt= con.createStatement();
stmt.executeQuery(
"select balance from registration
where theuser='"+seller+"'");
rs = stmt.getResultSet();
if(rs.next()) {
balance=rs.getDouble(1);
}
stmt.close();
if(balance <0) {
con.rollback();
con.close();
return (-1);
}
stmt= con.createStatement();
stmt.executeUpdate(
"update auctionitems set
counter=counter+1");
stmt.close();
con.commit();
con.close();
return(0);
} catch(SQLException e) {
try {
con.rollback();
con.close();
stmt.close();
ps.close();
}catch (Exception ignore){}
}
return (0);
}
�Caracteres de Escape
El API JDBC proporciona la palabr clave escape para que podamos especificar el caracter que querramos usar como caracter de escape. Por ejemplo, si queremos usar el signo de tanto por ciento (%) como el s�mbolo de tanto por ciento que que no se interprete como un comod�n SQL usando en consultas SQL LIKE, tenemos que escaparlo con el caracter de escape que especifiquemos con la palabra clave escape.
La siguiente sentencia muestra c�mo podemos usar la palabra clave escape para buscar por el valor 10%:
stmt.executeQuery(
"select tax from sales where tax like
'10\%' {escape '\'}");
Si nuestro programa almacena nombres y direcciones en la base de datos introducidos desde la l�nea de comandos o desde un interface de usuario, el s�mbolo de comilla simple (') podr�a aparecer en los datos. Pasar una comilla simple directamente a un string SQL causa problemas cuando la sentencia es analizada porque SQL le da a este s�mbolo otro significado a menos que se le escape.
Para resolver este problem, el siguiente m�todo escapa cualquier s�mbolo ' encontrado en la l�nea de entrada. Este m�todo puede ser extendido para escapar cualquier otro caracter como las comas , que la base de datos o su driver podr�an interpretar de otra forma:
static public String escapeLine(String s) {
String retvalue = s;
if (s.indexOf ("'") != -1 ) {
StringBuffer hold = new StringBuffer();
char c;
for(int i=0; i < s.length(); i++ ) {
if ((c=s.charAt(i)) == '\'' ) {
hold.append ("''");
}else {
hold.append(c);
}
}
retvalue = hold.toString();
}
return retvalue;
}
Sin embargo, si usamos un PreparedStatement en lugar de una simple Statement, muchos de estos problemas de escape desaparecen. Por ejemplo, en lugar de esta l�nea con la secuencia de escape:
stmt.executeQuery(
"select tax from sales where tax like
'10\%' {escape '\'}");
Podr�amos usar esta l�nea:
preparedstmt = C.prepareStatement(
"update tax set tax = ?");
�Mapear Tipos de Base de Datos
Aparte de unos pocos tipos como INTEGER que son representados como INTEGER en las bases de datos m�s populares, podr�amos encontrar que el tipo JDBC de una columna de la tabla no corresponde con el tipo representado en la base de datos. Esto significa que lllamar a ResultSet.getObject, PreparedStatement.setObject y CallableStatement.getObject() fallar� bastantes veces.
Nuestro programa puede determinar los tipos de las columnas de la base de datos desde los datos meta de la base de datos y usar esta informaci�n para chequear el valor antes de recuperarlo. Este c�digo chequea que el valor es del tipo INTEGER antes de recuperarlo.
int count=0;
Connection con=getConnection();
Statement stmt= con.createStatement();
stmt.executeQuery(
"select counter from auctionitems");
ResultSet rs = stmt.getResultSet();
if(rs.next()) {
if(rs.getMetaData().getColumnType(1) ==
Types.INTEGER) {
Integer i=(Integer)rs.getObject(1);
count=i.intValue();
}
}
rs.close();
�Mapeo de Tipos Date
El tipo DATE es donde ocurren m�s errores. Es porque la clase java.util.Date representa tanto la Fecha como la Hora, pero SQL tiene estos tres tipos para representar informaci�nde fecha y hora:
- Un tipo DATE que representa s�lo fechas (03/23/99).
- Un tipo TIME que espec�fica s�lo la hora (12:03:59).
- Un tipo TIMESTAMP que representa el valor de la hora en nanosegundos.
Estos tres tipos adiciones los proporciona el paquete java.sql como java.sql.Date, java.sql.Time y java.sql.Timestamp y son todos suclases de java.util.Date. Esto significa que podemos usar valores java.util.Date convertidos al tipo necesario para que sean compatibles con el tipo de la base de datos.
Nota: la clase Timestamp pierde precisi�n cuando se convierte a java.util.Date porque java.util.Date no contiene un campo de nanosegundos, es mejro no convertir un ejemplar Timestamp si el valor va a ser escrito de vuelta en la base de datos.
Este ejemplo usa la clase java.sql.Date para convertir el valor java.util.Date devuelto por la llamada a Calendar.getTime hacia java.sql.Date.
Calendar currenttime=Calendar.getInstance();
java.sql.Date startdate=
new java.sql.Date((
currenttime.getTime()).getTime());
Tambi�n podemo usar la clase java.text.SimpleDateFormat para hacer la conversi�n. Este ejemplo usa la clase java.text.SimpleDateFormat para convertir un objeto java.util.Date a un objeto java.sql.Date:
SimpleDateFormat template =
new SimpleDateFormat("yyyy-MM-dd");
java.util.Date enddate =
new java.util.Date("10/31/99");
java.sql.Date sqlDate =
java.sql.Date.valueOf(
template.format(enddate));
Si encontramos que una representaci�n de fecha de una base de datos no puede ser mapeada a un tipo Java con una llamada a getObject o getDate, recuperamos el valor con una llamada a getString y formateamos el string como un valor Date usando la clase SimpleDateFormat mostrada arriba.
�Servlets
Un servelt es un programa del lado del servidor escrito en lenguaje Java que interact�a con clientes y que normalmente est� unido a unservidor de "HyperText Transfer Protocol" (HTTP). Uno uso com�n para un servlet es ampliar un servidor web proporcionando contenidos web din�micos.
Los servelts tienen la ventaja sobre otras tecnolog�as que de est�n compilados, tienen capacidad de threads interna, y proporcionan un entorno de programaci�n seguro. Incluso las sites web que antes no proporcionaban soporte para servlets, pueden hacerlo ahora usando programas como JRun o el m�dulo Java para el servidor Web Apache.
La aplicaci�n subastas basada en web usa un servelt para aceptar y procesar entradas del comprador y vendedor a trav�s del navegador y devuelve din�micamente informaci�n sobre el �tem de la subasta hacia el navegador. El programa AuctionServlet se creo extendiendo la clase HttpServlet. Esta clase proporciona un marco de trabajo para manejar peticiones y respuestas HTTP.
Esta secci�n examina el AuctionServlet e incluye informaci�n sobre c�mo usar objetos Cookie y Session en un servlet.
�HttpServlet
La clase AuctionServlet extiende la clase HttpServlet, que es una clase abastracta.
public class AuctionServlet extends HttpServlet {
Un servlet puede cargarse cuando se arranca el servidor web o cuando se solicita una petici�n HTTP a una URL que especifica el servlet. El servlet normalmente es cargado mediante un cargador de clases separado en el sevidor web porque esto permite que el servlet sea recargado descargando el cargador de clases que cargo la clase servlet. Sin embargo, si el servlet depende de otras clases y una de estas clases cambia, necesitaremos actualiza el sello de la fecha del servlet para recargarlo.
Despu�s de cargar un servlet, el primer estado en su ciclo de vida es la llamada a su m�todo init por parte del servidor web. Una vez cargado e inicializado, el siguiente estado en el ciclo de vida del servlet es para servir peticiones. El servlet sirve peticiones a trav�s de las implementaciones de su m�todos service, doGet, o doPost.
El servlet puede opcionalmente implementar un m�todo destroy para realizar operaciones de limpieza antes de que el servidor web descarge el servlet.
�El m�todo init
El m�todo init s�lo se llama una vez por el servidor web cuando se arranca el servlet por primera vez. A este m�todo se le pasa un objeto ServletConfig que contiene la informaci�n de inicializaci�n perteniente al servidor web donde se est� ejecutando la aplicaci�n.
El objeto ServletConfig es usado para acceder a la informaci�n mantenida por el servidor web incluyendo valores del par�metro initArgs en el fichero de propiedades del servlet. El c�digo del m�todo init usa el objeto ServletConfig para recuperar los valores de initArgs llamando al m�todo config.getInitParameter("parameter").
El m�todo AuctionServlet.init tambi�n contacta con el servidor de JavaBeans Enterprise para crear un objeto contexto (ctx). Este objeto e susado en el m�todo service para establecer una conexi�n con el servidor de JavaBeans Enterprise.
Context ctx=null;
private String detailsTemplate;
public void init(ServletConfig config)
throws ServletException{
super.init(config);
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
try {
detailsTemplate=readFile(
config.getInitParameter("detailstemplate"));
} catch(IOException e) {
System.err.println(
"Error in AuctionServlet <init>"+e);
}
}
�El m�todo destroy
El m�todo destroy es un m�todo de ciclo de vida implementado por servlets que necesitan grabar su estado entre cargas y descargas del servlet. Por ejemplo, el m�todo destroy podr�a gabar el estado actual del servlet, y la siguiente vez que el servlet sea cargado, el estado grabado podr�a ser recuperado por el m�todo init. Deber�amos tener cuidado con que no se podr�a haber llamado al m�todo destroy si la m�quina servidor se bloquea.
public void destroy() {
saveServletState();
}
�El m�todo service
El AuctionServlet es un servlet HTTP que maneja peticiones de clientes y genera respuestas a trav�s de su m�todo service. Acepta como par�metros los objetos de petici�n y respuesta HttpServletRequest y HttpServletResponse.
- HttpServletRequest conteine las cabeceras y los streams de entrada desde el cliente hacia el servidor.
- HttpServletResponse es el stream de salida que se utiliza para enviar informaci�n de vuelta desde el servidor hacia el cliente.
El m�todo service maneja peticiones HTTP est�ndars del cliente recibidas mediante su par�metro HttpServletRequest y delengando la petici�n a uno de los siguientes m�todos designados para manejar peticiones. Los diferentes tipos de peticiones se describen en la secci�n Peticiones HTTP.
- doGet para GET, GET condicional, y peticiones HEAD.
- doPost para peticiones POST.
- doPut para peticiones PUT.
- doDelete para peticiones DELETE.
- doOptions para peticiones OPTIONS.
- doTrace para peticiones TRACE.
El programa AuctionServlet proporciona su propia implementaci�n del m�todo service que llama a uno de los siguiente m�todos bas�ndose en el valor devuelto por la llamada a cmd=request.getParameter("action"). Estas implementaciones de m�todos corresponden a las implementacione por defecto proporcionadas por los m�todos doGet y doPost llamadas por el m�todo service, pero a�ade algunas funcionalidades espec�ficas de la aplicaci�n subasta para buscar Beans Enterprise.
- listAllItems(out)
- listAllNewItems(out)
- listClosingItems(out)
- insertItem(out, request)
- itemDetails(out, request)
- itemBid(out, request)
- registerUser(out, request)
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
String cmd;
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
if (ctx == null ) {
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
}
cmd=request.getParameter("action");
if(cmd !=null) {
if(cmd.equals("list")) {
listAllItems(out);
}else
if(cmd.equals("newlist")) {
listAllNewItems(out);
}else if(cmd.equals("search")) {
searchItems(out, request);
}else if(cmd.equals("close")) {
listClosingItems(out);
}else if(cmd.equals("insert")) {
insertItem(out, request);
}else if (cmd.equals("details")) {
itemDetails(out, request );
}else if (cmd.equals("bid")) {
itemBid(out, request) ;
}else if (cmd.equals("register")) {
registerUser(out, request);
}
}else{
// no command set
setTitle(out, "error");
}
setFooter(out);
out.flush();
}
�Peticiones HTTP
Una petici�n es un mensaje enviado desde un programa cliente como un navegador a un programa servidor. La primera l�nea del mensaje de petici�n contiene un m�todo que indica la acci�n a realizar sobre la URL que viene despu�s. Los dos mecanismos m�s comunes para enviar informaci�n al servidor son POST y GET.
- Las peticiones GET podr�an pasar par�metros a una URL a�adi�ndolas a la URL. Estas peticiones pueden ser guardadas en el bookmark o enviadas por correro e incluyen la informaci�n de la URL de respuesta.
- Las peticiones POST podr�an pasar datos adicionales a la URL envi�ndolas directamente al servidor de forma separada a la URL. Estas peticiones no pueden ser almacenadas en el bookmark ni enviadas por email y no cambiar la URL de la respuesta.
Las peticiones PUT son la inversa de la peticiones GET. En lugar de leer la p�gina, las peticiones PUT escriben (o almacenan) la p�gina.
Las peticiones DELETE son para eliminar p�ginas Web.
Las peticiones OPTIONS son para obtener informaci�n sobre las opciones de comunicaci�n disponibles en la cadena petici�n/respuesta.
Las peticiones TRACE son para realizar pruebas de diagn�stico porque permite que el cliente vea lo que se est� recibiendo al orto final de la cadena de petici�n.
�Usar Cookies en servlets
LAs cookies HTTP son exencialmente cabeceras HTTP personalizadas que son pasadas entre el cliente y el servidor. Aunque las cookies no son muy populares, permiten que el estado sea compartido entre dos m�quinas. Por ejemplo, cuando un usuario hace login en una site, una cookie puede mantener una referencia verificando que el usuario ha pasado el chequeo de password y puede usar esta referencia para identificar al mismo usuario en futuras visitas.
Las cookies normalmente est�n asociadas con un servidor. Si configuramos el dominio a .java.sun.com, entonces la cookies est� asociada con ese dominio. Si no se configura nign�n dominio, la cookie s�lo est� asociada con el servidor que cre� la cookie.
�Configurar una Cookie
El API Servlet de Java incluye una clase Cookie que podemos usar para configurar o recuperar la cookie desde la cabecera HTTP. Las cookies HTTP incluyen un nombre y una pareja de valores.
El m�todo startSession mostrado aqu� est� en el programa LoginServlet. En este m�todo, el nombre en la pareja nombre valor usado para crea el Cookie es JDCAUCTION, y un identificador �nico generado por el servidor es el valor.
protected Session startSession(String theuser,
String password,
HttpServletResponse response) {
Session session = null;
if ( verifyPassword(theuser, password) ) {
// Create a session
session = new Session (theuser);
session.setExpires (sessionTimeout + i
System.currentTimeMillis());
sessionCache.put (session);
// Create a client cookie
Cookie c = new Cookie("JDCAUCTION",
String.valueOf(session.getId()));
c.setPath ("/");
c.setMaxAge (-1);
c.setDomain (domain);
response.addCookie (c);
}
return session;
}
Versiones posteriores del API Servlet incluye un API Session, para crear una sesi�n usando el API Servelt en el ejemplo anterior podemos usar el m�todo getSession.
HttpSession session = new Session (true);
El m�todo startSession es llamado mediante peticiones de acci�n login desde un POST al LoginServlet de esta forma:
<FORM ACTION="/LoginServlet" METHOD="POST"> <TABLE> <INPUT TYPE="HIDDEN" NAME="action" VALUE="login"> <TR> <TD>Enter your user id:</TD> <TD><INPUT TYPE="TEXT" SIZE=20 NAME="theuser"></TD> </TR> <TR> <TD>Enter your password:<TD> <TD><INPUT TYPE="PASSWORD" SIZE=20 NAME="password"></TD> </TR> </TABLE> <INPUT TYPE="SUBMIT" VALUE="Login" NAME="Enter"> </FORM>
La cookie es creada con un edad m�xima de -1, lo que significa que el cookie es almacenado pero permanece vivo miesntras el navegador se est� ejecutando. El valor se selecciona en segunod, aunque cuando s eusan valores menores que unos pocos segundos necesitamos tener cuidado con que los tiempos de las m�quinas pudieran estar ligeramente desincronizados.
El valor de path puede ser usado para especificar que el cookie s�lo se aplica a directorios y ficheros bajo el path seleccionado en esa m�quina. En este ejemplo, el path ra�z / significa que el cookie es aplicable a todos los directorios.
El valor del dominio en este ejemplo es le�do desde los par�metros de inicializaci�n del servlet. Si el dominio es null, el cookie es s�lo aplicado a esa m�quina de domino.
�Recuperar un Cookie
El cookie es recuperado desde las cabeceras HTTP con una llamada al m�todo getCookies para solicitarlo:
Cookie c[] = request.getCookies();
Posteriormente podemos recuperar la pareja de selecciones nombre y valor llamando al m�todo Cookie.getName para recuperar el nombre y al m�todo Cookie.getValue para recuperar el valor.
LoginServlet tiene un m�todo validateSession que chequea las cookies del usuario para encontrar un cookie JDCAUCTION que fu� enviada en este dominio:
private Session validateSession
(HttpServletRequest request,
HttpServletResponse response) {
Cookie c[] = request.getCookies();
Session session = null;
if( c != null ) {
Hashtable sessionTable = new Hashtable();
for (int i=0; i < c.length &&
session == null; i++ ) {
if(c[i].getName().equals("JDCAUCTION")) {
String key = String.valueOf (c[i].getValue());
session=sessionCache.get(key);
}
}
}
return session;
}
Si usamos el API Servlet podemos usar el siguiente m�todo, observamos que el par�metro es false para especificar que el valor de sesi�n es devuelto y que no se cree una nueva sesi�n:
HttpSession session = request.getSession(false);
�Generar Sesiones
El m�todo LoginServlet.validateSession devuelve un objeto Session representado por la clase Session. Esta clase usa un generado desde una secuencia num�rica. Esta identificador de sesi�n numerada es la parte del valor de la pareja de nombe y valor almacenadas en el cookie.
La �nica forma de referenciar el nombre del usuario en el servidor es con este identificador de sesi�n, que est� almacenado en un sencillo cach� de memoria con los otros identificadores de sesi�n. Cuando un usuario termina una sesi�n, se llama a la acci�n LoginServlet de esta forma:
http://localhost:7001/LoginServlet?action=logout
El cach� de sesi�n implementado en el programa SessionCache.java incluye un thread para eliminar sesiones m�s viejas que el tiempo preseleccionado. Este tiempo podr�a medise en horas o d�as, dependiendo del tr�fico de la web site.
�Evitar el Cach� de P�ginas
El m�todo LoginServlet.setNoCache selecciona los valores Cache-Control o Pragma (dependiendo de la versi�n del protocolo HTTP que estemos usando) en la cabecera de respuesta a no-cache. La cabecera de expiraci�n Expires tambi�n se selecciona a 0, alternativamente podemos seleccionar la hora para que se la hora actual del sistema. Incluso si el cliente no cachea la p�gina, frecuentemente hay servidores proxys en una red corporativa que si lo har�an. S�lo las p�ginas que usan Secure Socket Layer (SSL) no son cacheadas por defecto.
private void setNoCache (HttpServletRequest request,
HttpServletResponse response) {
if(request.getProtocol().compareTo ("HTTP/1.0") == 0) {
response.setHeader ("Pragma", "no-cache");
} else if (request.getProtocol().compareTo
("HTTP/1.1") == 0) {
response.setHeader ("Cache-Control", "no-cache");
}
response.setDateHeader ("Expires", 0);
}
�Restringir Accesos y Redireccionamientos
Si instalamos el LoginServlet como el servlet por defecto o el servler a ejecutar cuando se sirva cualquier p�gina bajo el documento raiz, odemos usar cookies para restringir los usuarios a ciertas secciones de la site. Por ejemplo, podemos permitir que los usuarios que tengan cookies con el estado de que han introducido su passweord acceder a secciones de la site que requieren un login y mantener a los otros fuera.
El programa LoginServlet chequea un directorio restringido en este m�todo init. El m�todo init mostrado abajo configura la variable protectedDir a true si la variable config pasada a �l especifica un directorio protegido. El fichero de configuraci�n del servidor Web proporciona las configuraciones pasadas a un servlet en la variable config.
public void init(ServletConfig config)
throws ServletException {
super.init(config);
domain = config.getInitParameter("domain");
restricted = config.getInitParameter("restricted");
if(restricted != null) {
protectedDir=true;
}
M�s tarde en los m�todos validateSession y service, la variable protectedDir es comprobada y se llama al m�todo HttpResponse.sendRedirect para viar al usuario a la p�gina correcta bas�ndose en sus estados de login y sesi�n
if(protectedDir) {
response.sendRedirect (restricted+"/index.html");
}else{
response.sendRedirect (defaultPage);
}
El m�todo init tambi�n recupera el contexto del servlet para el servlet FileServlet para que los m�todos puedan ser llamados sobre FileServlet en el m�todo validateSession. La ventaja de llamar a los m�todos sobre el servlet FileServlet para servir los ficheros desde dentro del servlet LoginServlet, es que obtenemos todas las ventajas de la funcionalidades a�adidas dentro del servlet FileServlet como el mepeo de memoria o el chach� de ficheros. La parte negativa es que el c�digo podr�a no ser portable a otros servidores que no tengan un servlet FileServlet. Este c�digo recupera el contexto FileServlet:
FileServlet fileServlet=(FileServlet)
config.getServletContext().getServlet("file");
El m�todo validateSession evita que los usuarios sin login de sesi�n accedan a los directorios restringidos.
�C�digos de Error HTTP
Podemos devolver un c�digo de error HTTP usando el m�todo sendError. Por ejemplo, el c�digo de error HTTP 500 indica un error interno en el seridor, y el c�digo de error 404 indica p�gina no encontrada. Este segmento de c�digo devuelve el c�digo de error HTTP 500:
protected void service (HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
response.sendError (500);
}
�Leer Valores GET y POST
El API Servlet tiene un m�todo getParameter en la clase HttpServletRequest que devuelve el valor GET o POST para el nombre suministrado.
- La petici�n HTTP GET maneja parejas de nombre/valor como parte de la URL. El m�todo getParameter analiza la URL pasada, recupera las parejas name=value determinadas por el caracter (&), y devuelve el valor.
- La petici�n HTTP POST lle el nombre de las parejas nombre/valor desde el stream de entrada desde el cliente. El m�todo getParameter analiza en el stream de entrada las parejas de nombre/valor.
El m�todo getParameter funciona vien para servlet sencillos, pero si necesitamos recuperar los par�metros POST en el orden enque fueron situados en la p�gina wev o manejar posts multi-parte, podemos escribir nuestro propio para analizar el stram de entrada.
El siguiente ejemplo devuelve los par�metros POST en el orden en que fueron recibidos desde la p�gina Web. Normalmento, los par�metros son almacenados en un Hashtable que no mantiene el orden de secuencia de los elementos almacenados. El ejemplo mantiene una referencia a cada pareja nombre/valoren un vector que puede ser ser analizado para devolver valores en el orden en que fueron recibidos por el servidor.
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostServlet extends HttpServlet {
private Vector paramOrder;
private Hashtable parameters;
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if(request.getMethod().equals("POST")
&& request.getContentType().equals(
"application/x-www-form-urlencoded")) {
parameters=parsePostData(
request.getContentLength(),
request.getInputStream());
}
for(int i=0;i<paramOrder.size();i++) {
String name=(String)paramOrder.elementAt(i);
String value=getParameter((
String)paramOrder.elementAt(i));
out.println("name="+name+" value="+value);
}
out.println("</body></html>");
out.close();
}
private Hashtable parsePostData(int length,
ServletInputStream instream) {
String valArray[] = null;
int inputLen, offset;
byte[] postedBytes = null;
boolean dataRemaining=true;
String postedBody;
Hashtable ht = new Hashtable();
paramOrder= new Vector(10);
StringBuffer sb = new StringBuffer();
if (length <=0) {
return null;
}
postedBytes = new byte[length];
try {
offset = 0;
while(dataRemaining) {
inputLen = instream.read (postedBytes,
offset,
length - offset);
if (inputLen <= 0) {
throw new IOException ("read error");
}
offset += inputLen;
if((length-offset) ==0) {
dataRemaining=false;
}
}
} catch (IOException e) {
System.out.println("Exception ="+e);
return null;
}
postedBody = new String (postedBytes);
StringTokenizer st =
new StringTokenizer(postedBody, "&");
String key=null;
String val=null;
while (st.hasMoreTokens()) {
String pair = (String)st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) {
throw new IllegalArgumentException();
}
try {
key = java.net.URLDecoder.decode(
pair.substring(0, pos));
val = java.net.URLDecoder.decode(
pair.substring(pos+1,
pair.length()));
} catch (Exception e) {
throw new IllegalArgumentException();
}
if (ht.containsKey(key)) {
String oldVals[] = (String []) ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++) {
valArray[i] = oldVals[i];
}
valArray[oldVals.length] = val;
} else {
valArray = new String[1];
valArray[0] = val;
}
ht.put(key, valArray);
paramOrder.addElement(key);
}
return ht;
}
public String getParameter(String name) {
String vals[] = (String []) parameters.get(name);
if (vals == null) {
return null;
}
String vallist = vals[0];
for (int i = 1; i < vals.length; i++) {
vallist = vallist + "," + vals[i];
}
return vallist;
}
}
Para saber si una petici�n es POST o GET, llamados al m�todo getMethod de la clase HttpServletRequest. Para determinar el formato de los datos que est�n siendo posteados, llamamos al m�todo getContentType de la clase HttpServletRequest. Para sencillas p�ginas HTML, el tipo devuelto por est� llamada ser� application/x-www-form-urlencoded.
Si necesitamos crear un post con m�s de una parte como el creado por el siguiente formulario HTML, el servler necesitar� ller el stream de entrada desde el post para alcanzar las secciones individuales. Cada secci�n se dstingue por un l�mite definido en la cabecera post.
<FORM ACTION="/PostMultiServlet" METHOD="POST" ENCTYPE="multipart/form-data"> <INPUT TYPE="TEXT" NAME="desc" value=""> <INPUT TYPE="FILE" NAME="filecontents" value=""> <INPUT TYPE="SUBMIT" VALUE="Submit" NAME="Submit"> </FORM>
El siguiente ejemplo extrae una descripci�n y un fichero desde los navegadores del cliente. Lee el stream de entrada buscando una l�nea que corresponda con un string de l�mite, lee el contenido de la l�nea y lueo lee los datos asociados con esa parte. El fichero suvido se muestra simplemente, pero tambi�n puede ser escrito en disco:
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostMultiServlet extends HttpServlet {
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if (request.getMethod().equals("POST")
&& request.getContentType().startsWith(
"multipart/form-data")) {
int index = request.getContentType().indexOf(
"boundary=");
if (index < 0) {
System.out.println("can not find boundary type");
return;
}
String boundary =
request.getContentType().substring(
index+9);
ServletInputStream instream =
request.getInputStream();
byte[] tmpbuffer = new byte[8192];
int length=0;
String inputLine=null;
boolean moreData=true;
//Skip until form data is reached
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine !=null)
System.out.println("input="+inputLine);
if(length<0) {
moreData=false;
}
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("desc") >=0) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
System.out.println("desc="+inputLine);
}
}
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("filename") >=0) {
int startindex=inputLine.indexOf(
"filename");
System.out.println("file name="+
inputLine.substring(
startindex+10,
inputLine.indexOf("\"",
startindex+10)));
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
}
byte fileBytes[]=new byte[50000];
int offset=0;
if (moreData) {
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0, length);
if(length>0 && (
inputLine.indexOf(boundary) <0)) {
System.arraycopy(
tmpbuffer,
0,
fileBytes,
offset,
length);
offset+=length;
} else {
moreData=false;
}
}
}
// trim last two newline/return characters
// before using data
for(int i=0;i<offset-2;i++) {
System.out.print((char)fileBytes[i]);
}
}
out.println("</body></html>");
out.close();
}
}
�Threads
Un servlet debe ser capaz de manejar m�ltipels peticiones concurrentes. Cualquier n�mero de usuarios puede en un momento dado invocar al servlet, y mientras que el m�todo init ejecuta siempre un �nico trehad, el m�todo service es multi-thread para manejar m�ltiples peticiones.
Esto significa que cualquier campo est�tico o p�blico accedido por el m�todo service deber�an est�r restringidos a accesos de un thread. el ejemplo de abajo usa la palabra clave synchronized para restringir el acceso a un contador para que s�lo pueda ser actualizado por un thread a la vez:
int counter
Boolean lock = new Boolean(true);
synchronized(lock){
counter++;
}
�HTTPS
Muchos servidores, navegadores, y el java Plug-In tiene la posibilidad de soportar el protocolo HTTP seguro llamado HTTPS. Este similar al HTTP excepto en que los datos on tramitidos a trav�s de una capa de socket seguro (SSL) en lugar de una conexi�n de socket normal. Los navegadores web escuchan peticiones HTTP en un puerto mientras escuchan las peticiones HTTPS en otro puerto.
Los datos encriptados que son enviados a trav�s de la red incluyen chequeos para verificar si los dato se han modificado en el tr�nsito. SSL tambi�n autentifica el servidor web a sus clientes proporcionando un certificado de clave p�blica. en el SSL 3.0 el cliente tambi�n puede autentificarse a s� mismo con el servidor, usxando de nuevo un certificado de clave p�blica.
La clave p�blica criptogr�fica (tambi�n llamada clave de encriptaci�n asim�trerica) usa una pareja de claves p�blica y privada. Cualquier mensaje encriptado (hecho ininteligible) con la clave privada de la pareja s�lo puede ser desencriptado con la correspondiente clave p�blica. Los certificados son sentencias firmadas digitalmente generadas por un tercera parte conocidad como "Autoridad de Certificaci�n" Certificate Authority. Esta Autorizar necesita asegurarse de que nosotros somos quien decimos ser porque los clientes se creeran el certificado que reciban. Si es as�, este certificado puede contener la clave p�blica de la pareja de clave p�blica/privada. El certificado est� firmado por la clave privada de la Autoridad de Certificaci�n, y muchos navegadores conocen las claves p�blicas la mayor�a de las Autoridades de Certificaci�n.
Mientras que la encriptaci�nde clavep�blica es buena para prop�sitos de autentificaci�n, no es tan r�pida como la encriptaci�n asim�trica y por eso el protocolo SSL usa ambos tipos de claves en el ciclo de vida de una conexi�n SSL. El cliente y el servidor empiezan una transaci�n HTTPS con una inicializaci�n de conexi�n o fase de estrechamiento de manos.
Es en ese momento en el que el servidor es autentificado usando el certificado que el cliente ha recibido. El cliente usa la clave p�blica del servidor para encriptar los mensajes enviados al servidor. Despu�s de que el cliente haya sido autentificado y el algoritmo de encriptaci�n se ha puesto de acuerdo entre las dos partes, se usan unas nuevas claves de sesi�n sim�trica para encriptar y desencriptar las comunicaciones posteriores.
El algoritmo de encriptaci�n puede ser uno de los m�s populares algoritmos como "Rivest Shamir and Adleman" (RSA) o "Data Encryption Standard" (DES). Cuando mayor sea el n�mero de bits usados para crear la clave, mayores dificultades para poder romper las claves mediante la fuerza bruta.
HTTPS usando criptograf�a de clave p�blica y certificados nos permite proporcionar una gran privacidad a las aplicacioens que necesitan transaciones seguras. Los servidores, navegadores y Java Plug-In tienen sus propias configuraciones para permitir usar Comunicaciones SSL. En general, estos pasos requieren:
- Obtener una clave privada y un certificado firmado digitalmente con la clave p�blica correspondente.
- Instalar el certificado en una localizaci�n especificada por el software que estamos usando (servidor, navegador o Java Plug-In).
- Activar las caracter�sticas SSL y especificar nuestros ficheros de certificado y de clave privada como se explica en nuestra documentaci�n.
Siempre que activemos las caracter�sticas SSL de acuerdo con los requerimientos de la aplicaci�n dependiendo del nivel de seguridad de necesitemos. Por ejemplo no necesitamos verificar la autenticidad de los clientes para navegar por los �tems de la subasta, pero s� querremos ecriptar la informaci�n de la tarjeta de cr�dido y otras informaciones suministradas cuando los compradores y vendedores se registran para participar.
HTTPS puede ser usado para cualquier dato, no s�lo ara p�ginas web HTTP. Los programas escritos en lenguaje Java pueden ser descaradoa a trrav�s de conexiones HTTPS, y podemos abrir una conexi�n con un servidor HTTPS en el Java Plug-In. Para escribir un programa en Java que use SSL, este necesita una librer�a SSL y un conecimiento detallado del proceso de negociaci�n HTTPS. Nuestra librer�a SSL podr�a cubir los pasos necesarios ya que est� informaci�n es restringida por el control de exportaci�n de seguridad.