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 :

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 :
- écriture de l’interface du serveur avec IDL (Interface Definition Language)
- génération des classes « squelette » et « stub » du serveur
- écriture du serveur
- écriture du client
- compilation de l’ensemble des classes
- lancement d’un annuaire des services CORBA
- lancement du serveur
- 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 :
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 :
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.
est équivalent à interface iSrvEcho de Java. Va donner naissance à une interface Java.
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 :
l’interface du serveur
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.
C’est l’image (« stub ») du serveur dont usera le client. Elle fournit au client les fonctionnalités CORBA pour atteindre le serveur.
Fournit les méthodes nécessaires à la gestion des références d’objets CORBA
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 :
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
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)
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 :
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 :
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.

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 :
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 :
pour une requête de mise à jour de la base, n étant le nombre de lignes mises à jour
si la requête a généré une erreur
si la requête n’a généré aucun résultat
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 :
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 :
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
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 : 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 :
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
- il demande à l’utilisateur de taper une requête SQL au clavier
- il l’envoie au serveur SQL
- 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 :

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 :
Il affiche dans une fenêtre DOS :
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 :
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 :
pour que resultats désigne un tableau String[] sous Java.