Skip to content

10. Construction d’applications distribuées CORBA

10.1. Introduction

Dans le chapitre précédent, nous avons vu comment créer des applications distribuées en Java avec le package RMI. Nous abordons ici le même problème avec cette fois-ci l’architecture CORBA. CORBA (Common Object Request Broker Architecture) est une spécification définie par l’OMG (Object Management Group) qui regroupe de nombreuses entreprises du monde informatique. CORBA définit un « bus logiciel » accessible à des applications écrites en différents langages :

Image

Nous allons voir que la construction avec CORBA d’une application distribuée est proche de la méthode employée avec Java RMI : les concepts se ressemblent. CORBA présente l’avantage de l’interopérabilité avec des applications écrites dans d’autres langages.

10.2. Processus de développement d’une application CORBA

10.2.1. Introduction

Pour développer une application client-serveur CORBA, nous suivrons les étapes suivantes :

  1. écriture de l’interface du serveur avec IDL (Interface Definition Language)
  2. génération des classes « squelette » et « stub » du serveur
  3. écriture du serveur
  4. écriture du client
  5. compilation de l’ensemble des classes
  6. lancement d’un annuaire des services CORBA
  7. lancement du serveur
  8. lancement du client

Nous prenons comme premier exemple, celui du serveur d’écho déjà utilisé dans le contexte RMI. Le lecteur pourra ainsi voir les différences entre les deux méthodes.

L’application a été testée avec le jdk1.2.

10.2.2. Écriture de l’interface du serveur

Comme avec Java RMI, vis à vis du client, le serveur est défini par son interface. Si les classes implémentant le serveur ne sont pas nécessaires au client, celles de son interface le sont. Là où Java RMI utilisait une interface Java donnant naissance aux classes « squelette » et « stub » du serveur, l’architecture Java CORBA nécessite la description de l’interface avec un langage autre que Java. Cette interface donnera naissance à plusieurs classes utilisées pour certaines par le client, pour d’autres par le serveur.

La description de l’interface d’écho sera la suivante :

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

La description de l’interface sera stockée dans un fichier echo.idl. Elle est écrite dans le langage IDL (Interface Definition Language) de l’OMG. Pour être exploitable, elle doit être analysée par un programme qui va créer des fichiers source dans le langage utilisé pour développer l’application CORBA. Ici, nous utiliserons le programme idltojava.exe qui à partir de l’interface précédente va créer les fichiers source .java nécessaires à l’application. Le programme idltojava.exe n’est pas livré avec le JDK. On peut se le procurer sur le site de Sun http://java.sun.com.

Analysons les quelques lignes de l’interface idl précédente :

module echo

est équivalent à package echo de Java. La compilation de l’interface va donner naissance au package java echo c.a.d. un répertoire contenant des classes Java.

interface iSrvEcho

est équivalent à interface iSrvEcho de Java. Va donner naissance à une interface Java.

string echo(in string msg)

est équivalent à l’instruction Java String echo(String msg). Les types du langage IDL ne correspondent pas exactement à ceux du langage Java. On trouvera les correspondances un peu plus loin dans ce chapitre. Dans le langage IDL, les paramètres d’une fonction peuvent être des paramètres d’entrée (in), de sortie (out), d’entrée-sortie (inout). Ici, la méthode echo reçoit un paramètre d’entrée msg qui est une chaîne de caractères et renvoie une chaîne de caractères comme résultat.

L’interface précédente est celle de notre serveur d’écho. On rappelle qu’une interface distante décrit les méthodes de l’objet serveur accessibles aux clients. Ici, seule la méthode echo sera disponible pour les clients.

10.2.3. Compilation de l’interface IDL du serveur

Une fois l’interface du serveur définie, on produit les fichiers Java correspondants.

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

L’option -fno-cpp sert à indiquer qu’il n’y a pas à utiliser de pré-processeur (utilisé avec le C/C++ le plus souvent). La compilation du fichier echo.idl produit un sous-répertoire echo avec dedans les fichiers suivants :

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

Le fichier iSrvEcho.java est le fichier Java décrivant l’interface du serveur :

/*
 * File: ./ECHO/ISRVECHO.JAVA
 * From: ECHO.IDL
 * Date: Mon Mar 15 13:56:08 1999
 *   By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

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

On voit que c’est quasiment la traduction mot à mot de l’interface IDL. Si on a la curiosité de regarder le contenu des autres fichiers .java, on trouvera des choses plus complexes. Voici ce que dit la documentation sur le rôle de ces différents fichiers :

iSrvEcho.java

l’interface du serveur

_iSrvEchoImplbase.java

implémente l’interface iSrvEcho précédente. C’est une classe abstraite, « squelette » du serveur fournissant au serveur les fonctionnalités CORBA nécessaires à l’application distribuée.

_iSrvEchoStub.java

C’est l’image (« stub ») du serveur dont usera le client. Elle fournit au client les fonctionnalités CORBA pour atteindre le serveur.

iSrvEchoHelper.java

Fournit les méthodes nécessaires à la gestion des références d’objets CORBA

iSrvEchoHolder.java

Fournit les méthodes nécessaires à la gestion des paramètres d’entrée-sortie des méthodes de l’interface.

10.2.4. Compilation des classes générées à partir de l’interface IDL

Une bonne idée est de compiler les classes précédentes. On verra dans un autre exemple qu’on peut ici découvrir des erreurs dûes à un fonctionnement incorrect du générateur idltojava. Ici cela se passe bien, et après compilation, on a dans le répertoire du package echo les fichiers suivants :

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. Écriture du serveur

10.2.5.1. Implémentation de l’interface iSrvEcho

Nous avons défini plus haut l’interface iSrvEcho. Nous écrivons maintenant la classe implémentant cette interface. Elle sera dérivée de la classe _iSrvEchoImplbase.java qui comme indiqué ci-dessus implémente déjà l’interface iSrvEcho.

// packages importés
import echo.*;

// classe implémentant l’écho distant
public class srvEcho extends _iSrvEchoImplBase{
    // méthode réalisant l’écho
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fin écho
}// fin classe

Le code se comprend de lui-même. Cette classe est enregistrée dans le fichier srvEcho.java dans le répertoire parent de celui du package de l’interface iSrvEcho.

On peut compiler pour vérifier :

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. Écriture de la classe de création du serveur

Comme pour une application client-serveur RMI, un serveur CORBA doit être enregistré dans un annuaire pour être accessible par des clients. C’est cette procédure d’enregistrement qui, au niveau développement, diffère selon qu’on a une application CORBA ou RMI. Voici celle du serveur CORBA d’écho enregistrée dans le fichier serveurEcho.java :

// packages importés
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- classe serveurEcho
public class serveurEcho{
    // ------- main : lance le serveur d'écho
    // syntaxe pg machineAnnuaire portAnnuaire nomService
    // machine : machine supportant l'annuaire CORBA
    // port : port de l'annuaire CORBA
    // nomService : nom du service à enregistrer

  public static void main(String arg[]){
    // les arguments sont-ils là ?
    if(arg.length!=3){
        System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
        System.exit(1);
    }
    // on récupère les arguments
    String machine=arg[0];
    String port=arg[1];
    String nomService=arg[2];

    try{

    // il nout faut un objet CORBA pour travailler
    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
      ORB orb=ORB.init(initORB,null);
    // on met le service dans l'annuaire des services
    // il s'appellera srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // on crée le serveur et on l'associe au service srvEcho
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // suivi
    System.out.println("Serveur d'écho prêt");
    // attente des demandes des clients
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
    // il y a eu une erreur
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// main
}// serveurEcho

Nous expliquons ci-après les grandes lignes du lancement du serveur sans entrer dans les détails qui sont complexes au premier abord. Il faut retenir de l’exemple précédent ses grandes lignes qui vont revenir dans tout serveur CORBA.

10.2.5.2.1. Les paramètres du serveur

Un serveur CORBA doit s’enregistrer auprès d’un service d’annuaire opérant sur une machine et un port donnés. Notre application recevra ces deux données en paramètres. Le service ainsi enregistré doit porter un nom, ce sera le troisième paramètre.

10.2.5.2.2. Créer l’objet d’accès au service d’annuaire CORBA

Pour atteindre le service d’annuaire et enregistrer notre serveur d’écho, nous avons besoin d’un objet appelé ORB (Object Request Broker) obtenu avec la méthode de classe suivante :

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

L’exemple utilise la séquence suivante pour obtenir l’objet ORB :


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

Les couples (paramètre, valeur) utilisés sont les suivants :


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

Le second paramètre de la méthode init est laissé à null. Si on avait laissé le premier paramètre également à null, le couple (machine,port) utilisé aurait été par défaut (localhost,900).

10.2.5.2.3. Enregistrer le serveur dans l’annuaire des services CORBA

L’enregistrement du serveur dans l’annuaire se fait avec les opérations suivantes :

    // on met le service dans l'annuaire des services
    // il s'appellera srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // on crée le serveur et on l'associe au service srvEcho
    srvEcho serveurEcho=new srvEcho();
        ncRef.rebind(path,serveurEcho);
            orb.connect(serveurEcho);

La première partie du code consiste à préparer le nom du service. Ce nom est symbolisé dans le code par la variable path. Le nom d’un service comprend plusieurs composantes :

  • une composante initiale objRef, objet générique qui doit être ramené à un type NamingContext, ici ncRef.
  • le nom du service, ici nomService qui a été passé en paramètre au serveur

Ces composantes du nom (NameComponent) sont rassemblées dans un tableau, ici path. C’est ce tableau qui « nomme » de façon précise le service créé. Une fois le nom créé, il reste

  • à l’associer à un exemplaire du serveur (la classe srvEcho construite précédemment)

srvEcho serveurEcho=new srvEcho();
  • à l’enregistrer dans l’annuaire
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilation de la classe de lancement serveur

On compile la classe précédente :

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. Écriture du client

10.2.6.1. Le code

Nous écrivons un client afin de tester notre service d’écho. On passera au client les trois mêmes paramètres qu’au serveur :

Machine : machine où se trouve l’annuaire des services CORBA

Port : port sur lequel opère cet annuaire

nomService : nom du service d’écho

Le client se connecte au service d’écho puis demande à l’utilisateur de taper des messages au clavier. Ceux-ci sont envoyés au serveur d’écho qui les renvoie. Un suivi de ce dialogue est fait à l’écran.

Le client CORBA du service d’écho ressemble beaucoup au client RMI déjà écrit. Là encore, le client doit se connecter à un service d’annuaire pour obtenir une référence de l’objet-serveur auquel il veut se connecter. La différence entre les deux clients réside là et uniquement là. Voici le code du client CORBA d’écho :


 

10.2.6.2. La connexion du client au serveur

Le client CORBA ci-dessus se connecte au serveur par l’instruction :

        // on fait la liaison avec le serveur d'écho
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

A l’issue de cette opération, le client détient une référence du serveur d’écho. Ensuite un client CORBA ne diffère pas d’un client RMI. La méthode privée qui assure la connexion au serveur est la suivante :

// packages importés
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

// ---------- classe cltEcho
public class cltEcho {

    public static void main(String arg[]){
        // syntaxe : cltEcho machineAnnuaire portAnnuaire nomservice
        // machine : machine où opère l'annuaire des services CORBA
        // port : port où opère l'annuaire des services
        // nomService : nom du service d'écho

        // vérification des arguments
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
            System.exit(1);
        }

        // on récupère les paramètres
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

        // on fait la liaison avec le serveur d'écho
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

        // dialogue client-serveur
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        iSrvEcho serveur=null;

        try{
            // ouverture du flux clavier
            in=new BufferedReader(new InputStreamReader(System.in));
            // boucle de lecture des msg à envoyer au serveur d'écho
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                // envoi du msg au serveur et réception de la réponse
                reponse=serveurEcho.echo(msg);
                // suivi
                System.out.println("Réponse serveur : " + reponse);
                // msg suivant
                System.out.print("Message : ");             
                msg=in.readLine().toLowerCase().trim();
            }// while
            // c'est fini
            System.exit(0);
        // gestion des erreurs      
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// main

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

        // demande une référence du serveur d'écho
        // suivi
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // la référence du serveur d'écho
        iSrvEcho serveurEcho=null;
        try{
            // on demande un objet CORBA pour travailler
            String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
        ORB orb=ORB.init(initORB,null);
            // on utilise le service d'annuaire pour localiser le serveur d'écho
        org.omg.CORBA.Object objRef=
                orb.resolve_initial_references("NameService");
        NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // le service recherché s'appelle srvEcho - on le demande
        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
        // on rend la référence au serveur
        return serveurEcho;
    }// getServeurEcho

}// classe

On retrouve les mêmes séquences de code que dans le serveur :

  • on crée un objet ORB qui nous permettra de contacter l’annuaire des services CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
      ORB orb=ORB.init(initORB,null);
  • on définit les différentes composantes du nom du service d’écho
        org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
  • on demande au service d’annuaire une référence du service d’écho (c’est là qu’on diffère du serveur)
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. Tests

10.2.7.1. Lancement du service d’annuaire

Sur une machine Windows, nous lançons le service d’annuaire de la façon suivante :

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

ce qui a pour effet de lancer le service d’annuaire sur le port 1000 de la machine.

Le service d’annuaire tnameserv produit un affichage d’écran qui ressemble à ce qui suit :

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

C’est peu lisible, mais on retiendra la dernière ligne : le service est actif sur le port 1000.

10.2.7.2. Lancement du serveur d’écho

Le service d’écho est lancé avec trois paramètres :


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

Le serveur affiche :

    Serveur d’écho prêt

10.2.7.3. Lancement du client sur le même poste que celui du serveur


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. Lancement du client sur poste Windows autre que celui du serveur

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. Exemple 2 : un serveur SQL

10.3.1. Introduction

Nous reprenons ici l’écriture du serveur SQL déjà étudié dans le contexte de Java RMI, toujours pour mettre en évidence les points communs aux deux méthodes ainsi que leurs différences. Nous rappelons le rôle de ce serveur SQL : il se trouve sur une machine Windows, et permet aux clients distants d’exploiter les bases ODBC publiques de ce poste Windows.

Image

Le client CORBA pourrait faire 3 opérations :

  • se connecter à la base de son choix
  • émettre des requêtes SQL
  • fermer la connexion

Le serveur exécute les requêtes SQL du client et lui envoie les résultats. C’est son travail essentiel et c’est pourquoi nous l’appellons un serveur SQL. Nous appliquons les différentes étapes vues précédemment avec le serveur d’écho.

10.3.2. Écriture de l’interface IDL du serveur

Rappelons pour mémoire l’interface RMI que nous avions utilisée pour le serveur :

import java.rmi.*;

// l'interface distante
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;
}

Le rôle des différentes méthodes était le suivant :

Connect : le client se connecte à une base de données distante dont il donne le pilote, l’url JDBC ainsi que son identité id et son mot de passe mdp pour accéder à cette base. Le serveur lui rend une chaîne de caractères indiquant le résultat de la connexion :

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

executeSQL : le client demande l’exécution d’une requête SQL sur la base à laquelle il est connecté. Il indique le caractère qui doit séparer les champs dans les résultats qui lui sont renvoyés. Le serveur renvoie un tableau de chaînes :

    100 n

pour une requête de mise à jour de la base, n étant le nombre de lignes mises à jour

    500 msg d’erreur

    si la requête a généré une erreur

    501 Pas de résultats

    si la requête n’a généré aucun résultat

    101 ligne1
    101 ligne2
    101 ...

si la requête a généré des résultats. Les lignes ainsi renvoyées par le serveur sont les lignes résultats de la requête.

Close : le client ferme sa connexion avec la base distante. Le serveur renvoie une chaîne indiquant le résultat de cette fermeture :

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

L’interface IDL du serveur sera la suivante :

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();
    };// interface
};// module

La seule nouveauté par rapport à ce qu’on a vu avec l’interface IDL du serveur d’écho est l’utilisation du mot clé sequence. Ce mot clé permet de définir un tableau à une dimension. La définition se fait en deux étapes :

  • définition d’un type pour désigner le tableau, ici resultats :
typedef sequence<string> resultats;

Le mot clé typedef est bien connu des programmeurs C/C++ : il permet de définir un nouveau type. Ici, le type resultats est défini comme équivalent au type sequence<string>, c.a.d. un tableau dynamique (non dimensionné) de chaînes de caractères.

  • utilisation du nouveau type là où on en a besoin
resultats executeSQL(in string requete, in string separateur);

La méthode executeSQL rend donc un tableau de chaînes de caractères.

10.3.3. Compilation de l’interface IDL du serveur

L’interface IDL précédente est mise dans le fichier srvSQL.idl. On compile ce fichier :

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

On constate que la compilation a donné naissance à un répertoire portant le nom du module de l’interface IDL(srvSQL). Regardons le contenu de ce répertoire :

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

On rappelle que les fichiers Helper et Holder sont des classes liées aux paramètres d’entré-sortie et résultats des méthodes de l’interface distante. Le répertoire srvSQL contient tous les fichiers .java liés à l’interface interSQL définie dans le fichier .idl. In contient également des fichiers liés au type resultats créé dans l’interface IDL.

Le fichier interSQL.java est le fichier Java de l’interface de notre serveur. Il est important de vérifier que ce qui a été produit automatiquement correspond à notre attente. Le fichier interSQL.java généré est ici le suivant :

/*
 * File: ./SRVSQL/INTERSQL.JAVA
 * From: SRVSQL.IDL
 * Date: Fri Mar 19 09:59:48 1999
 *   By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 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()
;
}

On constate qu’on a la même interface que celle utilisée pour le client-serveur RMI. On peut donc continuer. Compilons tous ces fichiers .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. Écriture du serveur SQL

Nous écrivons maintenant le code du serveur SQL. Rappelons que cette classe doit dériver de la classe abstraite _nomInterfaceImplBase produite par la compilation du fichier IDL. Hormis cette particularité et si on excepte les séquences de code liées à l’enregistrement du service dans un annuaire, le code du serveur CORBA est identique à celui du serveur RMI :

// packages importés
import java.sql.*;
import java.util.*;
import srvSQL.*;

// classe SQLServant
public class SQLServant extends _interSQLImplBase{

    // données globales de la classe
    private Connection DB;

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

        // connexion à la base url grâce au driver pilote
        // identification avec identité id et mot de passe mdp

        String resultat=null;           // résultat de la méthode
        try{
            // chargement du pilote
            Class.forName(pilote);
            // demande de connexion
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // erreur
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // fin
        return resultat;
    }           

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

        // exécute une requête SQL sur la base DB
        // et met les résultats dans un tableau de chaînes

        // données nécessaires à l'exécution de la requête
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // création du conteneur de la requête
            S=DB.createStatement();
            // exécution requête
            if (! S.execute(requete)){
                // requête de mise à jour
                // on renvoie le nombre de lignes mises à jour
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // c'était une requête d'interrogation
            // on récupère les résultats
            RS=S.getResultSet();
            // nombre de champs du Resultset
            int nbChamps=RS.getMetaData().getColumnCount();
            // on les exploite
            while(RS.next()){
                // création de la ligne des résultats
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // ajout au vecteur des résultats
                resultats.addElement(ligne);
            }// while
            // fin de l'exploitation des résultats
            // on libère les ressources
            RS.close();
            S.close();
            // on rend les résultats
            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);
            }//if
            return lignes;
        } catch (Exception e){
            // erreur
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- close
    public String close(){
        // ferme la connexion à la base de données
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // renvoi du résultat
        return resultat;
    }
}// classe SQLServant

Cette classe est placée dans le fichier SQLServant.java que nous compilons :

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. Écriture du programme de lancement du serveur SQL

La classe précédente représente le serveur SQL une fois lancé. Auparavant, il doit être enregistré dans un annuaire des services CORBA. Comme pour le service d’écho, nous le ferons avec une classe spéciale à qui nous passerons, au moment de l’exécution, trois paramètres :

Machine : machine où se trouve l’annuaire des services CORBA

Port : port sur lequel opère cet annuaire

nomService : nom du service SQL

Le code de cette classe est quasi identique à celui de la classe qui faisait la même chose pour le service d’écho. Nous avons mis en gros caractères la ligne qui diffère entre les deux classes : elle ne crée pas le même objet-serveur. On voit donc qu’on a toujours la même mécanique de lancement du serveur. Si on isole cette mécanique dans une classe, comme il a été fait ici, elle devient quasi transparente au développeur.

// packages importés
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- classe serveurSQL
public class serveurSQL{
    // ------- main : lance le serveur SQL
  public static void main(String arg[]){
        // serveurSQL machine port service

        //a-t-on le bon nombre d'arguments
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
            System.exit(1);
        }
        // on récupère les arguments
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
    try{
            // il nout faut un objet CORBA pour travailler
      ORB orb=ORB.init(initORB,null);
            // on met le service dans l'annuaire des services
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
        // on crée le serveur et on l'associe au service srvSQL
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // suivi
        System.out.println("Serveur SQL prêt");
        // attente des demandes des clients
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
            // il y a eu une erreur
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// main
}// srvSQL

Nous compilons cette nouvelle classe :

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. Écriture du client

Le client du serveur CORBA est appelé avec les paramètres suivants :

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

machine : machine où se trouve l’annuaire des services CORBA

port : port sur lequel opère cet annuaire

nomService : nom du service SQL

pilote : pilote que doit utiliser le serveur SQL pour gérer la base de données désirée

urlBase : url JDBC de la base de données à gérer

id : identité du client ou null si pas d’identité

mdp : mot de passe du client ou null si pas de mot de passe

separateur : caractère que le serveur SQL doit utiliser pour séparer les champs des lignes résultats d’une requête

Voici un exemple de paramètres possibles :

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

avec ici :

machine : machine sur laquelle se trouve l’annuaire des services CORBA

port : port sur lequel opère cet annuaire

srvSQL : srvSQL, nom CORBA du serveur SQL

pilote : sun.jdbc.odbc.JdbcOdbcDriver, le pilote usuel des bases avec interface odbc

urlBase : jdbc:odbc:articles, pour utiliser une base articles déclarée dans la liste des bases publiques ODBC de la machine Windows

id : null, pas d’identité

mdp : null, pas de mot de passe

separateur : , les champs des résultats seront séparés par une virgule

Une fois lancé avec les paramètres précédents, le client suit les étapes suivantes :

  • il se connecte à la machine machine sur le port port pour demander le service CORBA srvSQL
  • il demande la connexion à la base de données articles
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • il demande à l’utilisateur de taper une requête SQL au clavier
  • il l’envoie au serveur SQL
executeSQL(requete, ’’,’’);
  • il affiche à l’écran les résultats renvoyés par le serveur
  • il redemande à l’utilisateur de taper une requête SQL au clavier. Il s’arrêtera lorsque la requête est fin.

Le texte Java du client suit. Les commentaires devraient suffire à sa compréhension. On constatera que :

  • le code est identique à celui du client RMI déjà étudié. Il en diffère par le processus de demande du service à l’annuaire, processus isolé dans la méthode getServeurSQL.
  • la méthode getServeurSQL est identique à celle écrite pour le client d’écho

On voit donc que :

  • un client CORBA ne diffère d’un client RMI que par sa façon de contacter le serveur
  • cette façon est identique pour tous les clients CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;

public class clientSQL {

    // données globales de la classe
    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[]){
        // syntaxe : cltSQL machine port separateur pilote url id mdp
        // machine port : machine & port de l'annuaire des services CORBA à contacter
        // service : nom du service
        // pilote : pilote à utiliser pour la base de données à exploiter
        // urlBase : url jdbc de la base à exploiter
        // id : identité de l'utilisateur
        // mdp : son mot de passe
        // separateur : chaîne séparant les champs dans les résultats d'une requête

        // vérification du nb d'arguments
        if(arg.length!=8)
            erreur(syntaxe,1);

        // init paramètres de la connexion à la base de données
        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];       

        // paramètres du service d'annuaire CORBA
        String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
        // client CORBA - on demande une référence du serveur SQL
        interSQL serveurSQL=getServeurSQL(machine,port,service);

        // dialogue client-serveur
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // ouverture du flux clavier
            in=new BufferedReader(new InputStreamReader(System.in));
            // suivi
            System.out.println("--> Connexion à la base de données en cours");
            // demande de connexion initiale à la base de données
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // suivi
            System.out.println("<-- "+reponse);
            // analyse réponse
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // boucle de lecture des requêtes à envoyer au serveur SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // envoi de la requête au serveur et réception de la réponse
                lignes=serveurSQL.executeSQL(requete,separateur);
                // suivi
                afficheLignes(lignes);
                // requête suivante
                System.out.print("--> Requête : ");             
                requete=in.readLine().toLowerCase().trim();
            }// while
            // suivi
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // on clôt la connexion
            reponse=serveurSQL.close();
            // suivi
            System.out.println("<-- " + reponse);
            // fin
            System.exit(0);
        // gestion des erreurs      
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// try
    }// main

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

    // ------------ erreur
    private static void erreur(String msg, int exitCode){
        // affichage msg d'erreur
        System.err.println(msg);
        // libération éventuelle des ressources
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // on quitte
        System.exit(exitCode);
    }// erreur

    // ---------------------- getServeurSQL
    private static interSQL getServeurSQL(String machine, String port, String service){
        // demande une référence du serveur SQL
        // machine : machine de l'annuaire des services CORBA
        // port : port de l'annuaire des services CORBA
        // service : nom du service CORBA à demander

        // suivi
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // la référence du serveur SQL
        interSQL serveurSQL=null;
        // paramètres du service d'annuaire CORBA
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
                try{
            // on demande un objet CORBA pour travailler - on passe pour cela le port 
            // d'écoute de l'annuaire des services CORBA
      ORB orb=ORB.init(initORB,null);
            // on utilise le service d'annuaire pour localiser le serveur SQL
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // le service recherché s'appelle srvSQL - on le demande
      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
        // on rend la référence au serveur
        return serveurSQL;
    }// getServeurSQL

}// classe

Compilons la classe du client :

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

Nous sommes prêts pour les tests.

10.3.7. Tests

10.3.7.1. Pré-requis

On suppose qu’une base ACCESS appelée Articles est publiquement disponible sur la machine Windows du serveur SQL :

Image

Cette base a la structure suivante :

nom

type

code

code de l’article sur 4 caractères

nom

son nom (chaîne de caractères)

prix

son prix (réel)

stock_actu

son stock actuel (entier)

stock_mini

le stock minimum (entier) en-deça duquel il faut réapprovisioner l’article

10.3.7.2. Lancement du service d’annuaire

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. Lancement du serveur SQL

On lance le serveur Sql :

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

Il affiche dans une fenêtre DOS :

    Serveur SQL prêt

10.3.7.4. Lancement d’un client sur la même machine que le serveur

Voici les résultats obtenus avec un client sur la même machine que le serveur :

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
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 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
--> Requête : delete from articles where stock_mini<7
<-- 100 1
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 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. Lancement d’un client sur une autre machine que celle du serveur

Voici les résultats obtenus avec un client sur une autre machine que celle du serveur :

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
--> Requête : select * from articles
<-- 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. Correspondances IDL - JAVA

Nous donnons ici, les correspondances entre types simples IDL et JAVA :

type IDL

type Java

boolean

boolean

char

char

wchar

char

octet

byte

string

java.lang.String

wstring

java.lang.String

short

short

unsigned short

short

long

int

unsigned long

int

long long

long

unsigned long long

long

float

float

double

double

Rappelons que pour définir un tableau d’éléments de type T dans l’interface IDL, on utilise l’instruction :

    typedef   sequence<T> nomType;

et qu’ensuite on utilise nomType pour référencer le type du tableau. Ainsi, dans l’interface du serveur SQL on a utilisé la déclaration :

    typedef sequence<string> resultats;

pour que resultats désigne un tableau String[] sous Java.