Skip to content

10. Creación de aplicaciones distribuidas CORBA

10.1. Introducción

En el capítulo anterior, vimos cómo crear aplicaciones distribuidas en Java con el paquete RMI. Aquí abordamos el mismo problema, pero en esta ocasión con la arquitectura CORBA. CORBA (Common Object Request Broker Architecture) es una especificación definida por el OMG (Object Management Group), que agrupa a numerosas empresas del sector informático. CORBA define un «bus de software» accesible para aplicaciones escritas en diferentes lenguajes:

Image

Veremos que la creación de una aplicación distribuida con CORBA es similar al método empleado con Java RMI: los conceptos son parecidos. CORBA presenta la ventaja de la interoperabilidad con aplicaciones escritas en otros lenguajes.

10.2. Proceso de desarrollo de una aplicación CORBA

10.2.1. Introducción

Para desarrollar una aplicación cliente-servidor CORBA, seguiremos los siguientes pasos:

  1. escritura de la interfaz del servidor con IDL (Interface Definition Language)
  2. generación de las clases «esqueleto» y «stub» del servidor
  3. escritura del servidor
  4. escritura del cliente
  5. compilación de todas las clases
  6. Inicio de un directorio de servicios CORBA
  7. Inicio del servidor
  8. inicio del cliente

Tomaremos como primer ejemplo el del servidor de eco ya utilizado en el contexto RMI. De este modo, el lector podrá ver las diferencias entre ambos métodos.

La aplicación se ha probado con el jdk1.2.

10.2.2. Escritura de la interfaz del servidor

Al igual que con Java RMI, desde el punto de vista del cliente, el servidor se define por su interfaz. Si bien las clases que implementan el servidor no son necesarias para el cliente, sí lo son las de su interfaz. Mientras que Java RMI utilizaba una interfaz Java que daba lugar a las clases «esqueleto» y «stub» del servidor, la arquitectura Java CORBA requiere que la interfaz se describa en un lenguaje distinto de Java. Esta interfaz dará lugar a varias clases, algunas de las cuales serán utilizadas por el cliente y otras por el servidor.

La descripción de la interfaz de eco será la siguiente:

module echo{
    interface iSrvEcho{
        string echo(in string msg);
    };
};

La descripción de la interfaz se almacenará en un archivo echo.idl. Está escrita en el lenguaje IDL (Interface Definition Language) de OMG. Para que sea utilizable, debe ser analizada por un programa que creará archivos fuente en el lenguaje utilizado para desarrollar la aplicación CORBA. En este caso, utilizaremos el programa idltojava.exe, que, a partir de la interfaz anterior, creará los archivos fuente .java necesarios para la aplicación. El programa idltojava.exe no se incluye con el JDK. Se puede descargar de la página web de Sun: http://java.sun.com.

Analicemos las pocas líneas de la interfaz anterior de idl:

module echo

es equivalente al paquete «echo» de Java. La compilación de la interfaz dará lugar al paquete Java echo c.a.d, un directorio que contiene clases Java.

interface iSrvEcho

es equivalente a la interfaz iSrvEcho de Java. Dará lugar a una interfaz de Java.

string echo(in string msg)

es equivalente a la instrucción Java String echo(String msg). Los tipos del lenguaje IDL no se corresponden exactamente con los del lenguaje Java. Las correspondencias se pueden consultar más adelante en este capítulo. En el lenguaje IDL, los parámetros de una función pueden ser de entrada (in), de salida (out) o de entrada-salida (inout). En este caso, el método echo recibe un parámetro de entrada msg, que es una cadena de caracteres, y devuelve una cadena de caracteres como resultado.

La interfaz anterior es la de nuestro servidor de eco. Recordemos que una interfaz remota describe los métodos del objeto servidor a los que pueden acceder los clientes. En este caso, solo el método echo estará disponible para los clientes.

10.2.3. Compilación de la interfaz IDL del servidor

Una vez definida la interfaz del servidor, se generan los archivos Java correspondientes.

E:\data\java\corba\ECHO>dir *.idl

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL

E:\data\java\corba\ECHO>d:\javaidl\idltojava.exe -fno-cpp echo.idl

La opción -fno-cpp sirve para indicar que no hay que utilizar un preprocesador (se utiliza sobre todo con C/C++). La compilación del archivo echo.idl genera un subdirectorio echo que contiene los siguientes archivos:

E:\data\java\corba\ECHO>dir echo

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java

El archivo iSrvEcho.java es el archivo Java que describe la interfaz del servidor:

/*  * Archivo: ./ECHO/ISRVECHO.JAVA  * Procedencia: ECHO.IDL  * Fecha: lunes, 15 de marzo, 13:56:08, 1999  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package echo;
public interface iSrvEcho
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String echo(String msg)
;
}

Se puede observar que es prácticamente una traducción palabra por palabra de la interfaz IDL. Si tenemos curiosidad por echar un vistazo al contenido de los demás archivos .java, encontraremos cosas más complejas. Esto es lo que dice la documentación sobre la función de estos distintos archivos:

iSrvEcho.java

la interfaz del servidor

_iSrvEchoImplbase.java

implementa la interfaz anterior iSrvEcho. Se trata de una clase abstracta, el «esqueleto» del servidor, que proporciona al servidor las funcionalidades CORBA necesarias para la aplicación distribuida.

_iSrvEchoStub.java

Es la imagen («stub») del servidor que utilizará el cliente. Proporciona al cliente las funcionalidades CORBA para conectarse al servidor.

iSrvEchoHelper.java

Proporciona los métodos necesarios para la gestión de las referencias de objetos CORBA

iSrvEchoHolder.java

Proporciona los métodos necesarios para gestionar los parámetros de entrada y salida de los métodos de la interfaz.

10.2.4. Compilación de las clases generadas a partir de la interfaz IDL

Es recomendable compilar las clases anteriores. En otro ejemplo veremos que aquí se pueden detectar errores debidos a un funcionamiento incorrecto del generador idltojava. En este caso todo va bien y, tras la compilación, en el directorio del paquete echo tenemos los siguientes archivos:

E:\data\java\corba\ECHO\echo>dir

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java
_ISRVE~1 CLA         2 275  18/03/99  11:25 _iSrvEchoImplBase.class
_ISRVE~2 CLA         1 383  18/03/99  11:25 _iSrvEchoStub.class
ISRVEC~1 CLA           251  18/03/99  11:25 iSrvEcho.class
ISRVEC~2 CLA         2 078  18/03/99  11:25 iSrvEchoHelper.class
ISRVEC~3 CLA           858  18/03/99  11:25 iSrvEchoHolder.class

10.2.5. Entrada del servidor

10.2.5.1. Implementación de la interfaz iSrvEcho

Anteriormente hemos definido la interfaz iSrvEcho. Ahora vamos a escribir la clase que implementa dicha interfaz. Derivará de la clase _iSrvEchoImplbase.java, que, como se ha indicado anteriormente, ya implementa la interfaz iSrvEcho.

// paquetes importados
import echo.*;

// clase que implementa el eco remoto
public class srvEcho extends _iSrvEchoImplBase{
    // método que realiza el eco
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fin del eco
}// fin de la clase

El código se entiende por sí solo. Esta clase se guarda en el archivo srvEcho.java, en el directorio principal del paquete de la interfaz iSrvEcho.

Se puede compilar para comprobarlo:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac srvEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.5.2. Registro de la clase de creación del servidor

Al igual que en el caso de una aplicación cliente-servidor RMI, un servidor CORBA debe registrarse en un directorio para que los clientes puedan acceder a él. Es este procedimiento de registro el que, a nivel de desarrollo, difiere según se trate de una aplicación CORBA o RMI. A continuación se muestra el del servidor CORBA de eco registrado en el archivo serveurEcho.java:

// paquetes importados
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- clase serveurEcho
public class serveurEcho{
    // ------- main: inicia el servidor de eco
    // sintaxis pg machineAnnuaire portAnnuaire nomService
    // máquina: máquina que aloja el directorio CORBA
    // puerto: puerto del directorio CORBA
    // nomService: nombre del servicio que se va a registrar

  public static void main(String arg[]){
    // ¿Están ahí los argumentos?
    if(arg.length!=3){
        System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
        System.exit(1);
    }
    // se recuperan los argumentos
    String machine=arg[0];
    String port=arg[1];
    String nomService=arg[2];

    try{

    // Necesitamos un objeto CORBA para trabajar
    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
      ORB orb=ORB.init(initORB,null);
    // se añade el servicio al directorio de servicios
    // se llamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // Creamos el servidor y lo asociamos al servicio srvEcho
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // seguimiento
    System.out.println("Serveur d'écho prêt");
    // espera de las solicitudes de los clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
    // se ha producido un error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// en espera
}// serveurEcho

A continuación explicamos las líneas generales de la puesta en marcha del servidor sin entrar en detalles que, a primera vista, pueden resultar complejos. Hay que recordar de ejemplo anterior sus líneas generales, que se repetirán en cualquier servidor CORBA.

10.2.5.2.1. Los parámetros del servidor

Un servidor CORBA debe registrarse en un servicio de directorio que opere en una máquina y un puerto determinados. Nuestra aplicación recibirá estos dos datos como parámetros. El servicio así registrado debe tener un nombre, que será el tercer parámetro.

10.2.5.2.2. Crear el objeto de acceso al servicio de directorio CORBA

Para acceder al servicio de directorio y registrar nuestro servidor de eco, necesitamos un objeto denominado ORB (Object Request Broker), que se obtiene mediante el siguiente método de clase:

ORB ORB.init(String [] args, Properties prop)

Args :     tableau de paires de chaînes de caractères, chaque paire étant de la forme (paramètre,valeur)

Prop :     propriétés de l’application

El ejemplo utiliza la siguiente secuencia para obtener el objeto ORB:


    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
       ORB orb=ORB.init(initORB,null);

Los pares (parámetro, valor) utilizados son los siguientes:


("-ORBInitialHost",machine) : ce couple précise la machine ou opère l’annuaire des services CORBA, ici la machine passée en paramètre au serveur.

("-ORBInitialPort",port ) : ce couple précise le port ou opère l’annuaire des services CORBA, ici le port passé en paramètre au serveur.

El segundo parámetro del método init se deja en null. Si también se hubiera dejado el primer parámetro en null, el par (máquina, puerto) utilizado habría sido el predeterminado (localhost,900).

10.2.5.2.3. Registrar el servidor en el directorio de servicios CORBA

El registro del servidor en el directorio se realiza mediante las siguientes operaciones:

    // se incluye el servicio en el directorio de servicios
    // se llamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // Se crea el servidor y se asocia al servicio srvEcho
    srvEcho serveurEcho=new srvEcho();
          ncRef.rebind(path,serveurEcho);
            orb.connect(serveurEcho);

La primera parte del código consiste en preparar el nombre del servicio. Este nombre se representa en el código mediante la variable path. El nombre de un servicio consta de varios componentes:

  • un componente inicial objRef, un objeto genérico que debe convertirse a un tipo NamingContext, en este caso ncRef.
  • el nombre del servicio, en este caso nomService, que se ha pasado como parámetro al servidor

Estos componentes del nombre (NameComponent) se recogen en una matriz, en este caso path. Es esta matriz la que «nombra» de forma precisa el servicio creado. Una vez creado el nombre, queda

  • asociarlo a una instancia del servidor (la clase srvEcho creada anteriormente)

srvEcho serveurEcho=new srvEcho();
  • y registrarlo en el directorio
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilación de la clase de inicio del servidor

Se compila la clase anterior:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac serveurEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.6. Texto del cliente

10.2.6.1. El código

Escribimos un cliente para probar nuestro servicio de eco. Le pasaremos al cliente los mismos tres parámetros que al servidor:

Máquina: máquina en la que se encuentra el directorio de servicios CORBA

Puerto: puerto en el que opera este directorio

nomService: nombre del servicio de eco

El cliente se conecta al servicio de eco y, a continuación, solicita al usuario que escriba mensajes con el teclado. Estos se envían al servidor de eco, que los devuelve. El diálogo se muestra en pantalla.

El cliente CORBA del servicio de eco se parece mucho al cliente RMI ya escrito. Una vez más, el cliente debe conectarse a un servicio de directorio para obtener una referencia del objeto-servidor al que desea conectarse. La diferencia entre ambos clientes radica ahí y únicamente ahí. Este es el código del cliente de eco CORBA:


    

10.2.6.2. La conexión del cliente al servidor

El cliente CORBA anterior se conecta al servidor mediante la instrucción:

        // se establece la conexión con el servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

Una vez finalizada esta operación, el cliente dispone de una referencia del servidor de eco. A partir de ahí, un cliente CORBA no difiere de un cliente RMI. El método privado que garantiza la conexión con el servidor es el siguiente:

// paquetes importados
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

// ---------- clase cltEcho
public class cltEcho {

    public static void main(String arg[]){
         // sintaxis: cltEcho machineAnnuaire portAnnuaire nombre del servicio
         // máquina: máquina en la que opera el directorio de servicios CORBA
         // puerto: puerto en el que opera el directorio de servicios
         // nomService: nombre del servicio de eco

         // verificación de los argumentos
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
            System.exit(1);
        }

         // se recuperan los parámetros
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

         // se establece la conexión con el servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

         // diálogo cliente-servidor
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        iSrvEcho serveur=null;

        try{
             // apertura del flujo del teclado
            in=new BufferedReader(new InputStreamReader(System.in));
             // bucle de lectura de los mensajes que se van a enviar al servidor de eco
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                 // envío del mensaje al servidor y recepción de la respuesta
                reponse=serveurEcho.echo(msg);
                 // seguimiento
                System.out.println("Réponse serveur : " + reponse);
                 // mensaje siguiente
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
             // se ha terminado
            System.exit(0);
         // gestión de errores         
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// main

     // ---------------------- getServeurEcho
    private static iSrvEcho getServeurEcho(String machine, String port, 
            String nomService){

         // solicita una referencia del servidor de eco
         // seguimiento
        System.out.println("--> Connexion au serveur CORBA en cours...");
         // la referencia del servidor de eco
        iSrvEcho serveurEcho=null;
        try{
             // se solicita un objeto CORBA para trabajar
            String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
          ORB orb=ORB.init(initORB,null);
             // se utiliza el servicio de directorio para localizar el servidor de eco
          org.omg.CORBA.Object objRef=
                orb.resolve_initial_references("NameService");
          NamingContext ncRef=NamingContextHelper.narrow(objRef);
             // el servicio buscado se llama srvEcho; se solicita
          NameComponent nc= new NameComponent(nomService,"");
          NameComponent path[]={nc};
          serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur d'écho ("
                + e + ")");
            System.exit(10);
        }// try-catch
         // se devuelve la referencia al servidor
        return serveurEcho;
    }// getServeurEcho

}// clase

Encontramos las mismas secuencias de código que en el servidor:

  • creamos un objeto ORB que nos permitirá conectarnos al directorio de servicios CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
      ORB orb=ORB.init(initORB,null);
  • se definen los distintos componentes del nombre del servicio de eco
        org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
  • Se solicita al servicio de directorio una referencia del servicio de eco (aquí es donde nos diferenciamos del servidor)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

10.2.6.3. Compilation

E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java

E:\data\java\corba\ECHO>dir

CLTECH~1 CLA         2 599  18/03/99  13:51 cltEcho.class
CLTECH~1 JAV         2 907  16/03/99  16:15 cltEcho.java
ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.7. Pruebas

10.2.7.1. Inicio del servicio de directorio

En un equipo con Windows, iniciamos el servicio de directorio de la siguiente manera:

E:\data\java\corba\ECHO>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

lo que hace que se inicie el servicio de directorio en el puerto 1000 del equipo.

El servicio de directorio tnameserv muestra un mensaje en pantalla similar al siguiente:

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

No se lee muy bien, pero nos quedaremos con la última línea: el servicio está activo en el puerto 1000.

10.2.7.2. Puesta en marcha del servidor de eco

El servicio de eco se inicia con tres parámetros:


E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho

El servidor muestra:

    Serveur d’écho prêt

10.2.7.3. Inicio del cliente en el mismo equipo que el del servidor


E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho localhost 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin

10.2.7.4. Inicio del cliente en un equipo Windows distinto al del servidor

E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Réponse serveur : [abcd]
Message : efgh
Réponse serveur : [efgh]
Message : fin

10.3. Ejemplo 2: un servidor SQL

10.3.1. Introducción

Retomamos aquí la descripción del servidor SQL, ya analizado en el contexto de Java RMI, con el objetivo de poner de relieve los puntos en común entre ambos métodos, así como sus diferencias. Recordemos la función de este servidor SQL: se encuentra en un equipo con Windows y permite a los clientes remotos acceder a las bases de datos públicas ODBC de ese equipo con Windows.

Image

El cliente CORBA podría realizar tres operaciones:

  • conectarse a la base de datos que elija
  • enviar consultas SQL
  • cerrar la conexión

El servidor ejecuta las consultas SQL del cliente y le envía los resultados. Esa es su función principal y por eso lo llamamos servidor SQL. Aplicamos los distintos pasos vistos anteriormente con el servidor de eco.

10.3.2. Escritura de la interfaz IDL del servidor

Recordemos, a modo de resumen, la interfaz RMI que habíamos utilizado para el servidor:

import java.rmi.*;

// la interfaz remota
public interface interSQL extends Remote{
    public String connect(String pilote, String url, String id, String mdp)
        throws java.rmi.RemoteException;
    public String[] executeSQL(String requete, String separateur) 
        throws java.rmi.RemoteException;
    public String close()
        throws java.rmi.RemoteException;
}

La función de los distintos métodos era la siguiente:

Connect: el cliente se conecta a una base de datos remota, para lo cual proporciona el controlador, la URL JDBC, así como su identificador (id) y su contraseña (mdp) para acceder a dicha base. El servidor le devuelve una cadena de caracteres que indica el resultado de la conexión:

    200 - Connexion réussie
    500 - Echec de la connexion

executeSQL: el cliente solicita la ejecución de una consulta SQL en la base de datos a la que está conectado. Indica el carácter que debe separar los campos en los resultados que se le devuelven. El servidor devuelve una matriz de cadenas:

    100 n

para una consulta de actualización de la base de datos, siendo n el número de filas actualizadas

    500 msg d’erreur

si la consulta ha generado un error

    501 Pas de résultats

si la consulta no ha generado ningún resultado

    101 ligne1
    101 ligne2
    101 ...

si la consulta ha generado resultados. Las líneas devueltas por el servidor son los resultados de la consulta.

Close: el cliente cierra su conexión con la base de datos remota. El servidor devuelve una cadena que indica el resultado de este cierre:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

La interfaz IDL del servidor será la siguiente:

module srvSQL{

    typedef sequence<string> resultats;

    interface interSQL{
        string connect(in string pilote, in string urlBase, in string id, in string mdp);
        resultats executeSQL(in string requete, in string separateur);
        string close();
    };// interfaz
};// módulo

La única novedad con respecto a lo que hemos visto con la interfaz IDL del servidor de eco es el uso de la palabra clave «sequence». Esta palabra clave permite definir una matriz unidimensional. La definición se realiza en dos pasos:

  • definición de un tipo para designar la matriz, en este caso «resultados»:
typedef sequence<string> resultats;

La palabra clave typedef es bien conocida por los programadores de C/C++: permite definir un nuevo tipo. En este caso, el tipo resultats se define como equivalente al tipo sequence<string>, c.a.d, es decir, una matriz dinámica (sin tamaño fijo) de cadenas de caracteres.

  • Uso del nuevo tipo donde sea necesario
resultats executeSQL(in string requete, in string separateur);

Por lo tanto, el método executeSQL devuelve un array de cadenas de caracteres.

10.3.3. Compilación de la interfaz IDL del servidor

La interfaz IDL anterior se guarda en el archivo srvSQL.idl. Se compila este archivo:

E:\data\java\corba\sql>d:\javaidl\idltojava -fno-cpp srvSQL.idl

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL

Se observa que la compilación ha dado lugar a un directorio con el nombre del módulo de la interfaz IDL (srvSQL). Veamos el contenido de este directorio:

E:\data\java\corba\sql>dir srvSQl

RESULT~1 JAV           833  19/03/99  10:00 resultatsHolder.java
RESULT~2 JAV         1 883  19/03/99  10:00 resultatsHelper.java
_INTER~1 JAV         2 474  19/03/99  10:00 _interSQLStub.java
INTERS~1 JAV           448  19/03/99  10:00 interSQL.java
INTERS~2 JAV           841  19/03/99  10:00 interSQLHolder.java
INTERS~3 JAV         1 855  19/03/99  10:00 interSQLHelper.java
_INTER~2 JAV         4 535  19/03/99  10:00 _interSQLImplBase.java

Cabe recordar que los archivos Helper y Holder son clases relacionadas con los parámetros de entrada-salida y los resultados de los métodos de la interfaz remota. El directorio srvSQL contiene todos los archivos .java relacionados con la interfaz interSQL definida en el archivo .idl. También contiene archivos relacionados con el tipo resultats creado en la interfaz IDL.

El archivo interSQL.java es el archivo Java de la interfaz de nuestro servidor. Es importante comprobar que lo que se ha generado automáticamente se ajusta a lo que esperábamos. El archivo interSQL.java generado es el siguiente:

/*  * Archivo: ./SRVSQL/INTERSQL.JAVA  * De: SRVSQL.IDL  * Fecha: viernes, 19 de marzo de 1999, 09:59:48  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package srvSQL;
public interface interSQL
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String connect(String pilote, String urlBase, String id, String mdp)
;
    String[] executeSQL(String requete, String separateur)
;
    String close()
;
}

Se observa que tenemos la misma interfaz que la utilizada para el cliente-servidor RMI. Por lo tanto, podemos continuar. Compilemos todos estos archivos .java:

E:\data\java\corba\sql\srvSQL>j:\jdk12\bin\javac *.java

Note: _interSQLImplBase.java uses or overrides a deprecated API.  Recompile with
 "-deprecation" for details.
1 warning

E:\data\java\corba\sql\srvSQL>dir *.class

_INTER~1 CLA         3 094  19/03/99  10:01 _interSQLImplBase.class
_INTER~2 CLA         1 953  19/03/99  10:01 _interSQLStub.class
INTERS~1 CLA           430  19/03/99  10:01 interSQL.class
INTERS~2 CLA         2 096  19/03/99  10:01 interSQLHelper.class
INTERS~3 CLA           870  19/03/99  10:01 interSQLHolder.class
RESULT~1 CLA         2 047  19/03/99  10:01 resultatsHelper.class
RESULT~2 CLA           881  19/03/99  10:01 resultatsHolder.class

10.3.4. Codificación del servidor SQL

Ahora escribimos el código del servidor SQL. Recordemos que esta clase debe derivarse de la clase abstracta _nomInterfaceImplBase generada al compilar el archivo IDL. Aparte de esta particularidad y salvo las secuencias de código relacionadas con el registro del servicio en un directorio, el código del servidor CORBA es idéntico al del servidor RMI:

// paquetes importados
import java.sql.*;
import java.util.*;
import srvSQL.*;

// clase SQLServant
public class SQLServant extends _interSQLImplBase{

    // datos globales de la clase
    private Connection DB;

    // --------------- conexión
    public String connect(String pilote, String url, String id,
        String mdp){

        // conexión a la base de datos url mediante el controlador
        // identificación con el ID y la contraseña

        String resultat=null;            // resultado del método
        try{
            // carga del controlador
            Class.forName(pilote);
            // solicitud de conexión
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // error
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // fin
        return resultat;
    }            

    // ------------- executeSQL
    public String[] executeSQL(String requete, String separateur){

        // ejecuta una consulta SQL en la base de datos DB
        // y almacena los resultados en una matriz de cadenas

        // datos necesarios para la ejecución de la consulta
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // creación del contenedor de la consulta
            S=DB.createStatement();
            // ejecución de la consulta
            if (! S.execute(requete)){
                // consulta de actualización
                // se devuelve el número de líneas actualizadas
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // era una consulta
            // se recuperan los resultados
            RS=S.getResultSet();
            // número de campos del conjunto de resultados
            int nbChamps=RS.getMetaData().getColumnCount();
            // se procesan
            while(RS.next()){
                // creación de la línea de resultados
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // se añade al vector de resultados
                resultats.addElement(ligne);
            }// while
            // fin del procesamiento de los resultados
            // se liberan los recursos
            RS.close();
            S.close();
            // se devuelven los resultados
            int nbLignes=resultats.size();
            if (nbLignes==0){
                lignes=new String[1];
                lignes[0]="501 Pas de résultats";
            } else {
                lignes=new String[resultats.size()];
                for(int i=0;i<lignes.length;i++)
                    lignes[i]=(String) resultats.elementAt(i);
            }//si
            return lignes;
        } catch (Exception e){
            // error
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- cerrar
    public String close(){
        // cierra la conexión con la base de datos
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // devuelve el resultado
        return resultat;
    }
}// clase SQLServant

Esta clase se encuentra en el archivo SQLServant.java que estamos compilando:

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL
SQLSER~1 JAV         2 941  15/03/99   9:09 SQLServant.java

E:\data\java\corba\sql>j:\jdk12\bin\javac SQLServant.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class

10.3.5. Codificación del programa de inicio del servidor SQL

La clase anterior representa el servidor SQL una vez iniciado. Previamente, debe registrarse en un directorio de servicios CORBA. Al igual que con el servicio de eco, lo haremos con una clase especial a la que pasaremos, en el momento de la ejecución, tres parámetros:

Máquina: máquina en la que se encuentra el directorio de servicios CORBA

Puerto: puerto en el que opera este directorio

nomService: nombre del servicio SQL

El código de esta clase es prácticamente idéntico al de la clase que realizaba la misma tarea para el servicio de eco. Hemos resaltado en negrita la línea que difiere entre ambas clases: no crea el mismo objeto de servidor. Así pues, vemos que seguimos teniendo el mismo mecanismo de inicio del servidor. Si aislamos este mecanismo en una clase, tal y como se ha hecho aquí, se vuelve prácticamente transparente para el desarrollador.

// paquetes importados
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- clase serveurSQL
public class serveurSQL{
    // ------- main: inicia el servidor SQL
  public static void main(String arg[]){
        // serveurSQL máquina, puerto, servicio

        //¿Tenemos el número correcto de argumentos?
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
            System.exit(1);
        }
        // se recogen los argumentos
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
    try{
            // se necesita un objeto CORBA para trabajar
      ORB orb=ORB.init(initORB,null);
            // se añade el servicio al directorio de servicios
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
        // creamos el servidor y lo asociamos al servicio srvSQL
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // seguimiento
        System.out.println("Serveur SQL prêt");
        // espera de las solicitudes de los clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
            // se ha producido un error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// en espera
}// srvSQL

Compilamos esta nueva clase:

E:\data\java\corba\sql>j:\jdk12\bin\javac serveurSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class

10.3.6. Escritura del cliente

El cliente del servidor CORBA se invoca con los siguientes parámetros:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

máquina: máquina en la que se encuentra el directorio de servicios CORBA

puerto: puerto en el que opera este directorio

nomService: nombre del servicio SQL

controlador: controlador que debe utilizar el servidor SQL para gestionar la base de datos deseada

urlBase: URL JDBC de la base de datos que se va a gestionar

id: identidad del cliente o «null» si no hay identidad

mdp: contraseña del cliente o «null» si no hay contraseña

separador: carácter que debe utilizar el servidor SQL para separar los campos de las líneas de resultados de una consulta

A continuación se muestra un ejemplo de los parámetros posibles:

    localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

donde:

máquina: máquina en la que se encuentra el directorio de servicios CORBA

puerto: puerto en el que opera este directorio

srvSQL: srvSQL, nombre CORBA del servidor SQL

controlador: sun.jdbc.odbc.JdbcOdbcDriver, el controlador habitual para bases de datos con interfaz ODBC

urlBase: jdbc:odbc:articles, para utilizar una base de datos «articles» declarada en la lista de bases de datos públicas ODBC del equipo Windows

id: null, sin identidad

contraseña: null, sin contraseña

separador: , los campos de los resultados estarán separados por una coma

Una vez iniciado con los parámetros anteriores, el cliente sigue los siguientes pasos:

  • se conecta al equipo «machine» en el puerto «port» para solicitar el servicio CORBA srvSQL
  • solicita la conexión a la base de datos de artículos
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • solicita al usuario que introduzca una consulta SQL mediante el teclado
  • la envía al servidor SQL
executeSQL(requete, ’’,’’);
  • muestra en pantalla los resultados devueltos por el servidor
  • vuelve a pedir al usuario que introduzca una consulta SQL con el teclado. Se detendrá cuando la consulta haya finalizado.

A continuación se muestra el código Java del cliente. Los comentarios deberían bastar para entenderlo. Se observará que:

  • el código es idéntico al del cliente RMI que ya hemos estudiado. Se diferencia de este en el proceso de solicitud del servicio al directorio, proceso que se encuentra aislado en el método getServeurSQL.
  • El método getServeurSQL es idéntico al escrito para el cliente de eco

Por lo tanto, vemos que:

  • un cliente CORBA solo se diferencia de un cliente RMI en la forma de conectarse al servidor
  • esta forma es idéntica para todos los clientes CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;

public class clientSQL {

    // datos globales de la clase
    private static String syntaxe =
        "syntaxe : cltSQL machine port service pilote urlBase id mdp separateur";
    private static BufferedReader in=null;
    private static interSQL serveurSQL=null;

    public static void main(String arg[]){
        // sintaxis: cltSQL máquina puerto separador controlador URL ID contraseña
        // máquina y puerto: máquina y puerto del directorio de servicios CORBA con el que hay que ponerse en contacto
        // servicio: nombre del servicio
        // controlador: controlador que se debe utilizar para la base de datos que se va a utilizar
        // urlBase: URL JDBC de la base de datos que se va a utilizar
        // id: identificador del usuario
        // mdp: su contraseña
        // separador: cadena que separa los campos en los resultados de una consulta

        // verificación del número de argumentos
        if(arg.length!=8)
            erreur(syntaxe,1);

        // inicialización de los parámetros de conexión a la base de datos
        String machine=arg[0];
        String port=arg[1];
        String service=arg[2];        
        String pilote=arg[3];
        String urlBase=arg[4];
        String id, mdp, separateur;
        if(arg[5].equals("null")) id=""; else id=arg[5];
        if(arg[6].equals("null")) mdp=""; else mdp=arg[6];        
        if(arg[7].equals("null")) separateur=" "; else separateur=arg[7];        

        // parámetros del servicio de directorio CORBA
        String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
        // cliente CORBA: se solicita una referencia del servidor SQL
        interSQL serveurSQL=getServeurSQL(machine,port,service);

        // diálogo cliente-servidor
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // apertura del flujo del teclado
            in=new BufferedReader(new InputStreamReader(System.in));
            // seguimiento
            System.out.println("--> Connexion à la base de données en cours");
            // solicitud de conexión inicial a la base de datos
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // seguimiento
            System.out.println("<-- "+reponse);
            // análisis de la respuesta
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // bucle de lectura de las consultas que se van a enviar al servidor SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // envío de la consulta al servidor y recepción de la respuesta
                lignes=serveurSQL.executeSQL(requete,separateur);
                // seguimiento
                afficheLignes(lignes);
                // siguiente solicitud
                System.out.print("--> Requête : ");                
                requete=in.readLine().toLowerCase().trim();
            }// while
            // seguimiento
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // se cierra la conexión
            reponse=serveurSQL.close();
            // seguimiento
            System.out.println("<-- " + reponse);
            // fin
            System.exit(0);
        // gestión de errores        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// intentar
    }// main

    // ----------- AfficheLignes
    private static void afficheLignes(String[] lignes){
        for (int i=0;i<lignes.length;i++)
            System.out.println("<-- " + lignes[i]);
    }// afficheLignes

    // ------------ error
    private static void erreur(String msg, int exitCode){
        // visualización del mensaje de error
        System.err.println(msg);
        // posible liberación de recursos
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // salida
        System.exit(exitCode);
    }// error

    // ---------------------- getServeurSQL
    private static interSQL getServeurSQL(String machine, String port, String service){
        // solicita una referencia del servidor SQL
        // máquina: máquina del directorio de servicios CORBA
        // puerto: puerto del directorio de servicios CORBA
        // servicio: nombre del servicio CORBA que se va a solicitar

        // seguimiento
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // la referencia del servidor SQL
        interSQL serveurSQL=null;
        // parámetros del servicio de directorio CORBA
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
                try{
            // se solicita un objeto CORBA para trabajar; para ello, se utiliza el puerto 
            // de escucha del directorio de servicios CORBA
      ORB orb=ORB.init(initORB,null);
            // se utiliza el servicio de directorio para localizar el servidor SQL
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // el servicio buscado se llama srvSQL; lo solicitamos
      NameComponent nc= new NameComponent(service,"");
      NameComponent path[]={nc};
      serveurSQL=interSQLHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur SQL ("
                + e + ")");
            System.exit(10);
        }// try-catch
        // se devuelve la referencia al servidor
        return serveurSQL;
    }// getServeurSQL

}// clase

Compilemos la clase del cliente:

E:\data\java\corba\sql>j:\jdk12\bin\javac clientSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class
CLIENT~1 CLA         3 774  19/03/99  10:45 clientSQL.class

Estamos listos para las pruebas.

10.3.7. Pruebas

10.3.7.1. Pré-requis

Supongamos que una base de datos ACCESS llamada «Artículos» está disponible públicamente en el servidor Windows SQL:

Image

Esta base de datos tiene la siguiente estructura:

nombre
tipo
code
código del artículo de 4 caracteres
nom
su nombre (cadena de caracteres)
prix
su precio (real)
stock_actu
su stock actual (número entero)
stock_mini
el stock mínimo (en número entero) por debajo del cual hay que reponer el artículo

10.3.7.2. Lanzamiento del servicio de directorio

E:\data\java\corba\sql>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

Le service d’annuaire est lancé sur le port 1000. Il affiche dans une fenêtre DOS quelque chose du genre :

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

10.3.7.3. Inicio del servidor SQL

Se inicia el servidor SQL:

E:\data\java\corba\sql>start j:\jdk12\bin\java serveurSQL localhost 1000 srvSQL

Se muestra en una ventana DOS:

    Serveur SQL prêt

10.3.7.4. Inicio de un cliente en el mismo equipo que el servidor

Estos son los resultados obtenidos con un cliente en el mismo equipo que el servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select nombre, stock_actu, stock_mini from artículos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 cachalot,6,6
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Consulta: delete from artículos where stock_mini<7
<-- 100 1
--> Consulta: SELECT nombre, stock_actu, stock_mini FROM artículos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.3.7.5. Inicio de un cliente en un equipo distinto al del servidor

Estos son los resultados obtenidos con un cliente en un equipo distinto al del servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL tahe.istia.univ-angers.fr 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select * from artículos
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoé,1502,7,7
<-- 101 x123,fusil,3000,9,8
<-- 101 s345,skis nautiques,1800,13,8
<-- 101 f450,essai3,3,13,9
<-- 101 z400,léopard,500000,7,7
<-- 101 g457,panthère,800000,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.4. Correspondencias entre IDL y JAVA

A continuación se indican las correspondencias entre los tipos simples IDL y JAVA:

tipo IDL
tipo Java
boolean
booleano
char
char
wchar
char
byte
byte
cadena
java.lang.String
wstring
java.lang.String
short
short
short sin signo
short
long
int
long sin signo
int
long long
long
unsigned long long
long
float
float
double
double

Recordemos que para definir una matriz de elementos de tipo T en la interfaz IDL, se utiliza la instrucción:

    typedef   sequence<T> nomType;

y que, a continuación, se utiliza nomType para hacer referencia al tipo del array. Así, en la interfaz del servidor SQL se ha utilizado la declaración:

    typedef sequence<string> resultats;

para que resultats haga referencia a una matriz String[] en Java.