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.
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.
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.
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.
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.
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:
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:
Viejo código RMI:
Viejo código de búsqueda RMI:
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
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.
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.
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:
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:
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
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:
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);
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
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:
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:
java registration.RegistrationServer
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.
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);
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.INVALID_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.
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:
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.
String[] orbargs= {"-ORBInitialPort 1050"};
ORB orb = ORB.init(orbargs, null);
RegistrationServer rs = new RegistrationServer();
//Add this line
String ref = orb.object_to_string(rs);
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:
El nombre sustituto del servidor puede mantener registros persistentes IOR que pueden sobrevivir a paradas si es necesario.
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);
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.
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.
SellerHome shome =
(SellerHome)Naming.lookup(
"rmi://appserver:1090/seller");
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:
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:
tnameserv -ORBInitialPort 1091
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.
Nuevo código RMI:
SellerHome shome= new SellerHome("seller");
Naming.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().
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);
Servicios de Búsqueda JINI
(Para hacerlo más tarde)
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.
127.0.0.1 localhost
129.1.1.1 myserver1
129.1.1.2 myserver2