10. Creazione di applicazioni distribuite CORBA
10.1. Introduzione
Nel capitolo precedente abbiamo visto come creare applicazioni distribuite in Java utilizzando il pacchetto RMI. Qui affrontiamo lo stesso problema, questa volta utilizzando l'architettura CORBA. CORBA (Common Object Request Broker Architecture) è una specifica definita dall'OMG (Object Management Group), che riunisce molte aziende del settore IT. CORBA definisce un "bus software" accessibile ad applicazioni scritte in linguaggi diversi:

Vedremo che la creazione di un'applicazione distribuita con CORBA è simile al metodo utilizzato con Java RMI: i concetti sono simili. CORBA offre il vantaggio dell'interoperabilità con applicazioni scritte in altri linguaggi.
10.2. Il processo di sviluppo di un'applicazione CORBA
10.2.1. Introduzione
Per sviluppare un'applicazione client-server CORBA, seguiremo questi passaggi:
- Scrivere l'interfaccia del server utilizzando l'IDL (Interface Definition Language)
- Generazione delle classi "skeleton" e "stub" del server
- Scrittura del server
- Scrittura del client
- compilazione di tutte le classi
- Avvio di una directory di servizi CORBA
- Avvio del server
- Avvio del client
Come primo esempio useremo il server echo, già utilizzato nel contesto RMI. Questo permetterà al lettore di vedere le differenze tra i due metodi.
L'applicazione è stata testata con JDK 1.2.
10.2.2. Scrittura dell'interfaccia del server
Come con Java RMI, il server è definito dalla sua interfaccia in relazione al client. Mentre le classi che implementano il server non sono richieste dal client, quelle della sua interfaccia lo sono. Mentre Java RMI utilizzava un'interfaccia Java per generare le classi "skeleton" e "stub" del server, l'architettura Java CORBA richiede che l'interfaccia sia descritta in un linguaggio diverso da Java. Questa interfaccia genererà diverse classi, alcune delle quali sono utilizzate dal client e altre dal server.
La descrizione dell'interfaccia echo sarà la seguente:
La descrizione dell'interfaccia verrà memorizzata in un file echo.idl. È scritta nel linguaggio IDL (Interface Definition Language) dell'OMG. Per essere utilizzabile, deve essere analizzata da un programma che genererà i file sorgente nel linguaggio utilizzato per sviluppare l'applicazione CORBA. In questo caso, useremo il programma idltojava.exe, che genererà i file sorgente .java necessari per l'applicazione basati sull'interfaccia precedente. Il programma idltojava.exe non è incluso nel JDK. Può essere scaricato dal sito web di Sun all'indirizzo http://java.sun.com.
Analizziamo alcune righe della precedente interfaccia IDL:
è equivalente al **pacchetto** Java **echo**. La compilazione dell'interfaccia genererà il pacchetto Java *echo*, ovvero una directory contenente classi Java.
è equivalente **all'interfaccia** Java **iSrvEcho**. Genererà un'interfaccia Java.
è equivalente all'istruzione Java **String echo(String msg)**. I tipi nel linguaggio IDL non corrispondono esattamente a quelli nel linguaggio Java. Le corrispondenze sono spiegate più avanti in questo capitolo. Nel linguaggio IDL, i parametri di una funzione possono essere parametri di input (**in**), di output (**out**) o di input-output (**inout**). In questo caso, il metodo *echo* riceve un parametro di input *msg,* che è una stringa, e restituisce una stringa come risultato.
L'interfaccia precedente è quella del nostro server echo. Ricordiamo che un'interfaccia remota descrive i metodi dell'oggetto server accessibili ai client. In questo caso, solo il metodo echo sarà disponibile per i client.
10.2.3. Compilazione dell'interfaccia IDL del server
Una volta definita l'interfaccia del server, generiamo i file Java corrispondenti.
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'opzione -fno-cpp viene utilizzata per indicare che non deve essere utilizzato alcun preprocessore (utilizzata più comunemente con C/C++). La compilazione del file echo.idl crea una sottodirectory echo contenente i seguenti file:
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
Il file iSrvEcho.java è il file Java che descrive l'interfaccia del server:
/*
* 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)
;
}
Possiamo notare che si tratta quasi di una traduzione parola per parola dell'interfaccia IDL. Se siete abbastanza curiosi da dare un'occhiata al contenuto degli altri file .java, troverete cose più complesse. Ecco cosa dice la documentazione riguardo al ruolo di questi diversi file:
l'interfaccia del server
implementa la precedente interfaccia iSrvEcho. Si tratta di una classe astratta, lo "scheletro" del server, che fornisce al server le funzionalità CORBA richieste dall'applicazione distribuita.
Questo è lo "stub" del server che verrà utilizzato dal client. Fornisce al client le funzionalità CORBA necessarie per accedere al server.
Fornisce i metodi necessari per gestire i riferimenti agli oggetti CORBA
Fornisce i metodi necessari per gestire i parametri di input e output dei metodi dell'interfaccia.
10.2.4. Compilazione delle classi generate dall'interfaccia IDL
È consigliabile compilare le classi precedenti. Vedremo in un altro esempio che qui è possibile individuare gli errori causati da un funzionamento errato del generatore idltojava. In questo caso, tutto procede senza intoppi e, dopo la compilazione, nella directory del pacchetto echo sono presenti i seguenti file:
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. Implementazione del server
10.2.5.1. Implementazione dell'interfaccia iSrvEcho
Abbiamo definito l'interfaccia iSrvEcho in precedenza. Ora scriveremo la classe che implementa questa interfaccia. Deriverà dalla classe _iSrvEchoImplbase.java, che, come indicato sopra, implementa già l'interfaccia iSrvEcho.
// imported packages
import echo.*;
// class implementing remote echo
public class srvEcho extends _iSrvEchoImplBase{
// method performing the echo
public String echo(String msg){
return "[" + msg + "]";
}// fine echo
}// end of class
Il codice è autoesplicativo. Questa classe è salvata nel file srvEcho.java nella directory principale del pacchetto dell'interfaccia iSrvEcho.
È possibile compilarlo per verificare:
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. Scrittura della classe di creazione del server
Come per un'applicazione client-server RMI, un server CORBA deve essere registrato in una directory per essere accessibile ai client. È questa procedura di registrazione che, a livello di sviluppo, differisce a seconda che si tratti di un'applicazione CORBA o RMI. Ecco quella per il server CORBA echo registrata nel file serverEcho.java:
// imported packages
import echo.*
; import org.omg.CosNaming.
*; import org.omg.CosNaming.NamingContextPackage
.*; import org.omg.CORB
A.*; //----------- class serveu
rEcho public class serveu
rEcho{ // ------- main: launches the ech
o server // syntax pg machineAnnuaire portAnnuaire n
omService // machine: machine supporting CORBA
directory // port: directory port
CORBA // nomService: name of the service t
o be registered public static void
main(String arg[]){ // are th
e arguments there?
if(arg.length!=3){ System.err.println("Syntaxe : pg machineAnnuaire por
tAnnuaire nomSe
r
vice"); System.exit(1); }
// retrieve the argu
ments String machi
ne=arg[0]; String port=arg[1]; String nomService=arg[2];
try{
// you need a CORBA object to work
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// put the service in the service directory
// it will be called srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// create the server and associate it with the srvEcho service
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
// follow-up
System.out.println("Serveur d'écho prêt");
// waiting for customer requests
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// there has been an error
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// hand
}// serveurEcho
Di seguito illustriamo le nozioni di base per l'avvio del server senza addentrarci in dettagli che a prima vista potrebbero sembrare complessi. È importante ricordare i punti chiave dell'esempio precedente, poiché saranno presenti in ogni server CORBA.
10.2.5.2.1. Parametri del server
Un server CORBA deve registrarsi presso un servizio di directory operante su una determinata macchina e porta. La nostra applicazione riceverà queste due informazioni come parametri. Il servizio registrato deve avere un nome, che costituirà il terzo parametro.
10.2.5.2.2. Creazione dell'oggetto di accesso al servizio di directory CORBA
Per accedere al servizio di directory e registrare il nostro server echo, abbiamo bisogno di un oggetto chiamato ORB (Object Request Broker), ottenuto utilizzando il seguente metodo di classe:
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'esempio utilizza la seguente sequenza per ottenere l'oggetto ORB:
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
Le coppie (parametro, valore) utilizzate sono le seguenti:
("-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.
Il secondo parametro del metodo init è impostato su null. Se anche il primo parametro fosse stato impostato su null, la coppia (macchina, porta) utilizzata sarebbe stata quella predefinita (localhost,900).
10.2.5.2.3. Registrazione del server nella directory dei servizi CORBA
La registrazione del server nella directory avviene tramite le seguenti operazioni:
// put the service in the service directory //
it will be called srvEcho
org.omg.CORBA.Object o
b jRef= orb.resolve_initial_references("
N ameService"); NamingContext ncRef=NamingContextHe
l per.narrow(objRef); NameComponent nc= new Nam
e Component(nomService,"");
NameComponent path[]={nc}; // create the server a
nd associate it with the srvEcho s
e rvice srvEcho serveurEcho=new
srvEcho(); ncRef.rebind(path,serveurEcho); orb.connect(serveurEcho);
La prima parte del codice riguarda la preparazione del nome del servizio. Questo nome è rappresentato nel codice dalla variabile path. Un nome di servizio è composto da diversi elementi:
- un componente iniziale objRef, un oggetto generico che deve essere convertito in un tipo NamingContext, qui ncRef.
- il nome del servizio, qui `serviceName`, che è stato passato come parametro al server
Questi componenti del nome (NameComponent) sono raccolti in un array, qui path. È proprio questo array che “denomina” il servizio creato. Una volta creato, il nome rimane
- associarlo a un'istanza del server (la classe srvEcho costruita in precedenza)
srvEcho serveurEcho=new srvEcho();
- e registrarlo nella directory
10.2.5.3. Compilazione della classe di avvio del server
Compilare la classe precedente:
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. Scrittura client
10.2.6.1. Il codice
Stiamo scrivendo un client per testare il nostro servizio echo. Passeremo al client gli stessi tre parametri che abbiamo passato al server:
Machine: la macchina su cui si trova la directory del servizio CORBA
Porta: la porta su cui opera questa directory
serviceName: nome del servizio echo
Il client si connette al servizio echo e quindi richiede all'utente di digitare dei messaggi sulla tastiera. Questi messaggi vengono inviati al server echo, che li rinvia. Sullo schermo viene visualizzato un registro di questo dialogo.
Il client CORBA per il servizio echo è molto simile al client RMI già scritto. Anche in questo caso, il client deve connettersi a un servizio di directory per ottenere un riferimento all'oggetto server a cui desidera connettersi. La differenza tra i due client risiede esclusivamente in questo. Ecco il codice per il client echo CORBA:
10.2.6.2. Connessione del client al server
Il client CORBA sopra riportato si connette al server utilizzando la seguente istruzione:
// on fait la liaison avec le serveur d'écho
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
Dopo questa operazione, il client mantiene un riferimento al server echo. Da questo punto in poi, un client CORBA non è diverso da un client RMI. Il metodo privato che stabilisce la connessione al server è il seguente:
// imported packages
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
// ---------- class cltEcho
public class cltEcho {
public static void main(String arg[]){
// syntax : cltEcho machineAnnuaire portAnnuaire nameservice
// machine: machine where the CORBA service directory operates
// port: port where the service directory operates
// nomService: echo service name
// argument verification
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
System.exit(1);
}
// parameters are retrieved
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
// link to echo server
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
// client-server dialogue
BufferedReader in=null;
String msg=null;
String reponse=null;
iSrvEcho serveur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// loop for reading msg to be sent to echo server
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// send msg to server and receive response
reponse=serveurEcho.echo(msg);
// follow-up
System.out.println("Réponse serveur : " + reponse);
// next msg
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// it's over
System.exit(0);
// error management
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// hand
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port,
String nomService){
// requests an echo server reference
// follow-up
System.out.println("--> Connexion au serveur CORBA en cours...");
// echo server reference
iSrvEcho serveurEcho=null;
try{
// we request a CORBA object to work with
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// use the directory service to locate the echo server
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// the service required is called srvEcho - it is requested
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
// return the reference to the server
return serveurEcho;
}// getServeurEcho
}// class
Vediamo le stesse sequenze di codice presenti nel server:
- creiamo un oggetto ORB che ci permetterà di contattare la directory dei servizi CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
- definiamo i diversi componenti del nome del servizio echo
org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
- Richiediamo un riferimento al servizio echo dal servizio di directory (è qui che ci differenziamo dal server)
10.2.6.3. Compilazione
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. Test
10.2.7.1. Avvio del servizio directory
Su un computer Windows, avviamo il servizio di directory come segue:
che avvia il servizio di directory sulla porta 1000 del computer.
Il servizio di directory tnameserv produce una schermata simile alla seguente:
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
È difficile da leggere, ma notate l'ultima riga: il servizio è attivo sulla porta 1000.
10.2.7.2. Avvio del server echo
Il servizio echo viene avviato con tre parametri:
E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho
Il server visualizza:
10.2.7.3. Avvio del client sulla stessa macchina del server
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. Avvio del client su un computer Windows diverso dal server
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. Esempio 2: un server SQL
10.3.1. Introduzione
Qui riprendiamo il codice del server SQL precedentemente esaminato nel contesto di Java RMI, ancora una volta per evidenziare le somiglianze e le differenze tra i due approcci. Ricordiamo che il ruolo di questo server SQL è il seguente: gira su una macchina Windows e consente ai client remoti di accedere ai database ODBC pubblici presenti su quella macchina Windows.

Il client CORBA potrebbe eseguire tre operazioni:
- connettersi al database di sua scelta
- inviare query SQL
- chiudere la connessione
Il server esegue le query SQL del client e invia i risultati al client. Questa è la sua funzione principale, motivo per cui lo chiamiamo server SQL. Applichiamo i vari passaggi trattati in precedenza con il server echo.
10.3.2. Scrittura dell'interfaccia IDL del server
Come promemoria, ecco l'interfaccia RMI che abbiamo utilizzato per il server:
import java.rmi.*;
// remote interface p
ublic interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp
) throws java.rmi.RemoteExcept
ion; public String[] executeSQL(String requete, String separa
teur) throws java.rmi.RemoteE
xception; public Str
ing close() throws java.rmi.Re
moteException; }
I ruoli dei vari metodi erano i seguenti:
Connect: il client si connette a un database remoto, fornendo il driver, l'URL JDBC, nonché il proprio ID e la password per accedere al database. Il server restituisce una stringa che indica il risultato della connessione:
executeSQL: il client richiede l'esecuzione di una query SQL sul database a cui è connesso. Specifica il carattere che deve separare i campi nei risultati restituiti. Il server restituisce un array di stringhe:
per una query di aggiornamento del database, dove n è il numero di righe aggiornate
se la query ha generato un errore
se la query non ha restituito risultati
se la query ha restituito risultati. Le righe restituite dal server sono le righe di risultato della query.
Chiudi: il client chiude la connessione al database remoto. Il server restituisce una stringa che indica il risultato di questa chiusura:
L'interfaccia IDL del server sarà la seguente:
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
L'unica novità rispetto a quanto visto con l'interfaccia IDL del server Echo è l'uso della parola chiave sequence. Questa parola chiave consente di definire un array unidimensionale. La definizione avviene in due fasi:
- definizione di un tipo per designare l'array, in questo caso results:
La parola chiave typedef è ben nota ai programmatori C/C++: permette di definire un nuovo tipo. Qui, il tipo resultats è definito come equivalente al tipo sequence<string>, ovvero un array dinamico (senza dimensione fissa) di stringhe di caratteri.
- utilizzando il nuovo tipo dove necessario
Il metodo executeSQL restituisce quindi un array di stringhe.
10.3.3. Compilazione dell'interfaccia IDL del server
L'interfaccia IDL precedente è contenuta nel file srvSQL.idl. Compiliamo questo file:
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
Possiamo notare che la compilazione ha creato una directory che prende il nome dal modulo di interfaccia IDL (srvSQL). Diamo un'occhiata al contenuto di questa directory:
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
Si noti che i file Helper e Holder sono classi associate ai parametri di input/output e ai risultati dei metodi dell'interfaccia remota. La directory srvSQL contiene tutti i file .java relativi all'interfaccia interSQL definita nel file .idl. Contiene inoltre i file relativi al tipo di risultato creato nell'interfaccia IDL.
Il file interSQL.java è il file Java per l'interfaccia del nostro server. È importante verificare che ciò che è stato generato automaticamente corrisponda alle nostre aspettative. Il file interSQL.java generato è il seguente:
/*
* 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()
;
}
Possiamo notare che abbiamo la stessa interfaccia utilizzata per il client-server RMI. Quindi possiamo continuare. Compiliamo tutti questi file .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. Scrittura di SQL Server
Ora scriveremo il codice del server SQL. Ricordiamo che questa classe deve derivare dalla classe astratta _nomInterfaceImplBase generata dalla compilazione del file IDL. A parte questa particolarità ed escludendo le sequenze di codice relative alla registrazione del servizio in una directory, il codice del server CORBA è identico a quello del server RMI:
// imported packages
import java.sql.*;
import java.util.*;
import srvSQL.*;
// class SQLServant
public class SQLServant extends _interSQLImplBase{
// global class data
private Connection DB;
// --------------- connect
public String connect(String pilote, String url, String id,
String mdp){
// connection to url database via driver
// identification with identity id and password mdp
String resultat=null; // result of the method
try{
// loading the driver
Class.forName(pilote);
// connection request
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// error
resultat="500 Echec de la connexion (" + e + ")";
}
// end
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// executes a SQL query on the DB database
// and puts the results in an array of strings
// data required to execute the request
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// create query container
S=DB.createStatement();
// request execution
if (! S.execute(requete)){
// update request
// returns the number of lines updated
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// it was a query request
// retrieve results
RS=S.getResultSet();
// number of Resultset fields
int nbChamps=RS.getMetaData().getColumnCount();
// we exploit them
while(RS.next()){
// create results line
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// add to results vector
resultats.addElement(ligne);
}// while
// end of results processing
// free up resources
RS.close();
S.close();
// we return the results
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){
// error
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- close
public String close(){
// closes database connection
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// return result
return resultat;
}
}// class SQLServant
Questo metodo si trova nel file SQLServant.java che stiamo 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. Scrittura del programma di avvio di SQL Server
La classe precedente rappresenta il server SQL una volta avviato. Prima di ciò, deve essere registrato in una directory dei servizi CORBA. Come per il servizio echo, lo faremo utilizzando una classe speciale alla quale passeremo tre parametri in fase di esecuzione:
Machine: la macchina su cui si trova la directory dei servizi CORBA
Porta: la porta su cui opera questa directory
serviceName: nome del servizio SQL
Il codice di questa classe è quasi identico a quello della classe che svolgeva la stessa attività per il servizio echo. Abbiamo evidenziato in grassetto la riga che differisce tra le due classi: non crea lo stesso oggetto server. Possiamo quindi notare che il meccanismo di avvio del server rimane lo stesso. Se isoliamo questo meccanismo in una classe, come è stato fatto qui, esso diventa praticamente trasparente per lo sviluppatore.
// imported packages
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- class serveurSQL
public class serveurSQL{
// ------- main: launches the SQL server
public static void main(String arg[]){
// serveurSQL machine port service
//do we have the right number of arguments
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
System.exit(1);
}
// retrieve the arguments
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// you need a CORBA object to work
ORB orb=ORB.init(initORB,null);
// put the service in the service directory
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// create the server and associate it with the srvSQL service
SQLServant serveurSQL=new SQLServant();
ncRef.rebind(path,serveurSQL);
orb.connect(serveurSQL);
// follow-up
System.out.println("Serveur SQL prêt");
// waiting for customer requests
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// there has been an error
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// hand
}// srvSQL
Compiliamo questa nuova 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. Codice client
Il client del server CORBA viene chiamato con i seguenti parametri:
macchina: macchina su cui si trova la directory del servizio CORBA
porta: la porta su cui opera questa directory
serviceName: nome del servizio SQL
driver: driver che il server SQL deve utilizzare per gestire il database desiderato
baseUrl: URL JDBC del database da gestire
id: ID del client o null se non è presente alcun ID
password: password del client o null se non c'è password
separator: carattere che il server SQL deve utilizzare per separare i campi nelle righe di risultato di una query
Ecco un esempio di possibili parametri:
dove:
machine: macchina su cui si trova la directory del servizio CORBA
porta: la porta su cui opera questa directory
srvSQL: srvSQL, il nome CORBA del server SQL
driver: sun.jdbc.odbc.JdbcOdbcDriver, il driver standard per i database con interfaccia ODBC
urlBase: jdbc:odbc:articles, per utilizzare un database articles dichiarato nell'elenco dei database ODBC pubblici sul computer Windows
id: null, nessun ID
password: null, nessuna password
separatore: , i campi dei risultati saranno separati da una virgola
Una volta avviato con i parametri sopra indicati, il client esegue i seguenti passaggi:
- si connette alla macchina machine sulla porta port per richiedere il servizio CORBA srvSQL
- richiede una connessione al database degli articoli
- chiedono all'utente di digitare una query SQL sulla tastiera
- la inviano al server SQL
- Visualizza sullo schermo i risultati restituiti dal server
- chiede nuovamente all'utente di digitare una query SQL sulla tastiera. Si fermerà quando la query sarà terminata.
Di seguito è riportato il codice del client Java. I commenti dovrebbero essere sufficienti per comprenderlo. Si noti che:
- il codice è identico a quello del client RMI già studiato. Si differenzia nel processo di richiesta del servizio dalla directory, un processo isolato nel metodo getServeurSQL.
- il metodo getServeurSQL è identico a quello scritto per il client echo
Possiamo quindi notare che:
- un client CORBA differisce da un client RMI solo nel modo in cui contatta il server
- questo metodo è identico per tutti i client CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// global class data
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[]){
// syntax : cltSQL machine port separator driver url id mdp
// machine port: machine & service directory port CORBA to contact
// department: department name
// driver: driver to be used for the database to be processed
// urlBase: jdbc url of the database to be used
// id: user identity
// mdp: password
// separator: string separating fields in query results
// check number of arguments
if(arg.length!=8)
erreur(syntaxe,1);
// init database connection parameters
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];
// directory service parameters CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// client CORBA - request a server reference SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// client-server dialogue
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// follow-up
System.out.println("--> Connexion à la base de données en cours");
// initial database connection request
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// follow-up
System.out.println("<-- "+reponse);
// response analysis
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// loop for reading requests to be sent to the server SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// send request to server and receive response
lignes=serveurSQL.executeSQL(requete,separateur);
// follow-up
afficheLignes(lignes);
// following request
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// follow-up
System.out.println("--> Fermeture de la connexion à la base de données distante");
// close the connection
reponse=serveurSQL.close();
// follow-up
System.out.println("<-- " + reponse);
// end
System.exit(0);
// error management
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// hand
// ----------- 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){
// error msg display
System.err.println(msg);
// possible release of resources
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// we leave
System.exit(exitCode);
}// error
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// requests a server reference SQL
// machine: service directory machine CORBA
// port: service directory port CORBA
// service: service name CORBA to request
// follow-up
System.out.println("--> Connexion au serveur CORBA en cours...");
// server reference SQL
interSQL serveurSQL=null;
// directory service parameters CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// a CORBA object is required to work - to do this, we pass the port
// service directory listening CORBA
ORB orb=ORB.init(initORB,null);
// use the directory service to locate the SQL server
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// the service required is called srvSQL - it is requested
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
// return the reference to the server
return serveurSQL;
}// getServeurSQL
}// class
Compiliamo la classe 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
Siamo pronti per i test.
10.3.7. Test
10.3.7.1. Prerequisiti
Partiamo dal presupposto che un database ACCESS denominato Articles sia pubblicamente disponibile sul computer Windows del server SQL:

Questo database ha la seguente struttura:
nome | tipo |
codice | codice articolo di 4 caratteri |
nome | il suo nome (stringa) |
prezzo | il suo prezzo (effettivo) |
stock_attuale | scorte attuali (numero intero) |
min_stock | la scorta minima (numero intero) al di sotto della quale l'articolo deve essere rifornito |
10.3.7.2. Avvio del servizio di directory
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. Avvio del server SQL
Avvio del server SQL:
In una finestra DOS viene visualizzato quanto segue:
10.3.7.4. Avvio di un client sulla stessa macchina del server
Ecco i risultati ottenuti con un client sulla stessa macchina del server:
E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 sperm whale,6,6
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Requête : delete from articles where stock_mini<7
<-- 100 1
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
10.3.7.5. Avvio di un client su una macchina diversa dal server
Ecco i risultati ottenuti con un client su una macchina diversa dal server:
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 ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select * from articles
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoe,1502,7,7
<-- 101 x123,rifle,3000,9,8
<-- 101 s345,water skis,1800,13,8
<-- 101 f450,test3,3,13,9
<-- 101 z400,leopard,500000,7,7
<-- 101 g457,panther,800000,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
10.4. Corrispondenze IDL - JAVA
Qui forniamo le mappature tra i tipi IDL semplici e quelli Java:
Tipo IDL | Tipo Java |
boolean | booleano |
char | char |
wchar | char |
byte | byte |
stringa | java.lang.String |
wstring | java.lang.String |
short | short |
short senza segno | short |
long | int |
long senza segno | int |
long long | long |
long long senza segno | long |
float | float |
double | double |
Ricordiamo che per definire un array di elementi di tipo T nell'interfaccia IDL, si utilizza l'istruzione:
e che poi usiamo TypeName per riferirci al tipo di array. Pertanto, nell'interfaccia SQL Server, abbiamo utilizzato la dichiarazione:
in modo che results si riferisca a un array String[] in Java.