## 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 Chapter 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)
- Servicio de Nombres de la Arquitectura Common Object Request Broker (CORBA)
- Interoperable Object References (IOR)
- Remote Method Invocation (RMI)
- RMI Over Internet Inter-ORB Protocol (IIOP)
- Servicios de Búsqueda JINI
- Aumentar el Rendimiento de la Búsqueda

### 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.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.

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

(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.

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

Ozito