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

Vedremo che la realizzazione 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. Processo di sviluppo di un’applicazione CORBA
10.2.1. Introduzione
Per sviluppare un’applicazione client-server CORBA, seguiremo i seguenti passaggi:
- scrittura dell’interfaccia del server con IDL (Interface Definition Language)
- generazione delle classi «scheletro» e «stub» del server
- scrittura del server
- scrittura del client
- compilazione di tutte le classi
- avvio di un elenco dei servizi CORBA
- avvio del server
- avvio del client
Prendiamo come primo esempio il server echo già utilizzato nel contesto RMI. Il lettore potrà così notare le differenze tra i due metodi.
L’applicazione è stata testata con il jdk1.2.
10.2.2. Scrittura dell’interfaccia del server
Come nel caso di Java RMI, dal punto di vista del client, il server è definito dalla sua interfaccia. Se le classi che implementano il server non sono necessarie al client, lo sono invece quelle della sua interfaccia. Laddove Java RMI utilizzava un'interfaccia Java che dava origine alle classi «scheletro» e «stub» del server, l'architettura Java CORBA richiede la descrizione dell'interfaccia con un linguaggio diverso da Java. Tale interfaccia darà origine a diverse classi, alcune delle quali utilizzate dal client, altre dal server.
La descrizione dell’interfaccia di eco sarà la seguente:
La descrizione dell’interfaccia sarà 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 creerà i file sorgente nel linguaggio utilizzato per sviluppare l’applicazione CORBA. In questo caso, utilizzeremo il programma idltojava.exe che, a partire dall’interfaccia precedente, creerà i file sorgente .java necessari all’applicazione. Il programma idltojava.exe non è incluso nella distribuzione di JDK. È possibile scaricarlo dal sito di Sun http://java.sun.com.
Analizziamo alcune righe dell’interfaccia idl precedente:
è equivalente al pacchetto echo di Java. La compilazione dell’interfaccia darà origine al pacchetto Java echo c.a.d, una directory contenente classi Java.
è equivalente all’interfaccia iSrvEcho di Java. Darà origine a un’interfaccia Java.
è equivalente all’istruzione Java String echo(String msg). I tipi del linguaggio IDL non corrispondono esattamente a quelli del linguaggio Java. Le corrispondenze sono riportate più avanti in questo capitolo. Nel linguaggio IDL, i parametri di una funzione possono essere parametri di ingresso (in), di uscita (out) o di ingresso-uscita (inout). In questo caso, il metodo echo riceve un parametro di ingresso msg, che è una stringa di caratteri, e restituisce una stringa di caratteri 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, si generano 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 serve a indicare che non è necessario utilizzare un preprocessore (utilizzato più spesso con C/C++). La compilazione del file echo.idl genera 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 * Da: ECHO.IDL * Data: lunedì 15 marzo 13:56:08 1999 * Da: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 agosto 1998 16:25:34 */
package echo;
public interface iSrvEcho
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String echo(String msg)
;
}
Si nota che si tratta praticamente della traduzione parola per parola dell’interfaccia IDL. Se si ha la curiosità di esaminare il contenuto degli altri file .java, si troveranno elementi più complessi. Ecco cosa dice la documentazione sul ruolo di questi diversi file:
l’interfaccia del server
implementa l’interfaccia precedente iSrvEcho. Si tratta di una classe astratta, lo «scheletro» del server, che fornisce al server le funzionalità CORBA necessarie all’applicazione distribuita.
È l’immagine («stub») del server che verrà utilizzata dal client. Fornisce al client le funzionalità CORBA per raggiungere il server.
Fornisce i metodi necessari per la gestione dei riferimenti agli oggetti CORBA
Fornisce i metodi necessari per la gestione dei parametri di ingresso-uscita dei metodi dell'interfaccia.
10.2.4. Compilazione delle classi generate dall'interfaccia IDL
È consigliabile compilare le classi precedenti. In un altro esempio vedremo che in questa fase è possibile individuare errori dovuti a un funzionamento non corretto del generatore idltojava. In questo caso tutto procede correttamente 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. Scrittura dal server
10.2.5.1. Implementazione dell'interfaccia iSrvEcho
Abbiamo definito in precedenza l’interfaccia iSrvEcho. Ora scriviamo la classe che implementa tale interfaccia. Essa deriverà dalla classe _iSrvEchoImplbase.java che, come indicato sopra, implementa già l’interfaccia iSrvEcho.
// pacchetti importati
import echo.*;
// classe che implementa l’echo remoto
public class srvEcho extends _iSrvEchoImplBase{
// metodo che implementa l’echo
public String echo(String msg){
return "[" + msg + "]";
}// fine eco
}// fine classe
Il codice è intuitivo. Questa classe è salvata nel file srvEcho.java nella directory padre di quella del pacchetto dell’interfaccia iSrvEcho.
È possibile compilare il codice per verificarlo:
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 dai client. È proprio questa procedura di registrazione che, a livello di sviluppo, varia a seconda che si tratti di un’applicazione CORBA o RMI. Ecco quella del server CORBA di eco registrata nel file serveurEcho.java:
// pacchetti importati
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- classe serveurEcho
public class serveurEcho{
// ------- main: avvia il server di eco
// sintassi pg machineAnnuaire portAnnuaire nomService
// macchina: macchina che supporta la directory CORBA
// porta: porta della directory CORBA
// nomService: nome del servizio da registrare
public static void main(String arg[]){
// gli argomenti sono presenti?
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
System.exit(1);
}
// si recuperano gli argomenti
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
try{
// è necessario un oggetto CORBA per operare
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// si inserisce il servizio nella directory dei servizi
// si chiamerà srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// creiamo il server e lo associamo al servizio srvEcho
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
// monitoraggio
System.out.println("Serveur d'écho prêt");
// in attesa delle richieste dei clienti
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// si è verificato un errore
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// in attesa
}// serveurEcho
Di seguito illustriamo le linee generali dell’avvio del server senza entrare nei dettagli, che a prima vista possono risultare complessi. È importante ricordare le linee generali dell’esempio precedente, che ricorreranno in ogni server CORBA.
10.2.5.2.1. I parametri del server
Un server CORBA deve registrarsi presso un servizio di directory operante su una macchina e una porta specificate. La nostra applicazione riceverà questi due dati come parametri. Il servizio così 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 raggiungere il servizio di directory e registrare il nostro server di eco, abbiamo bisogno di un oggetto denominato ORB (Object Request Broker) ottenuto tramite 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 viene lasciato a null. Se anche il primo parametro fosse stato lasciato a 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:
// si inserisce il servizio nell'elenco dei servizi
// si chiamerà srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// si crea il server e lo si associa al servizio srvEcho
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
La prima parte del codice consiste nel preparare il nome del servizio. Questo nome è rappresentato nel codice dalla variabile path. Il nome di un servizio comprende diverse componenti:
- una componente iniziale objRef, oggetto generico che deve essere convertito in un tipo NamingContext, in questo caso ncRef.
- il nome del servizio, in questo caso nomService, che è stato passato come parametro al server
Queste componenti del nome (NameComponent) sono raccolte in un array, in questo caso path. È questo array che «denomina» in modo preciso il servizio creato. Una volta creato il nome, resta
- associarlo a un’istanza del server (la classe srvEcho creata in precedenza)
srvEcho serveurEcho=new srvEcho();
- e a registrarlo nella directory
10.2.5.3. Compilazione della classe di avvio del server
Si compila 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 del cliente
10.2.6.1. Il codice
Stiamo scrivendo un client per testare il nostro servizio di eco. Passeremo al client gli stessi tre parametri utilizzati per il server:
Macchina: macchina su cui si trova la directory dei servizi CORBA
Porta: porta su cui opera tale directory
nomService: nome del servizio echo
Il client si connette al servizio echo e chiede all’utente di digitare dei messaggi sulla tastiera. Questi vengono inviati al server echo, che li rinvia. Il dialogo viene visualizzato sullo schermo.
Il client CORBA del 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 proprio in questo e solo in questo. Ecco il codice del client di eco CORBA:
10.2.6.2. La connessione del client al server
Il client CORBA sopra riportato si connette al server tramite l’istruzione:
// si effettua il collegamento con il server di eco
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
Al termine di questa operazione, il client dispone di un riferimento al server di eco. Successivamente, un client CORBA non differisce da un client RMI. Il metodo privato che garantisce la connessione al server è il seguente:
// pacchetti importati
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
// ---------- classe cltEcho
public class cltEcho {
public static void main(String arg[]){
// sintassi: cltEcho machineAnnuaire portAnnuaire nameservice
// macchina: macchina su cui opera la directory dei servizi CORBA
// porta: porta su cui opera la directory dei servizi
// nomService: nome del servizio di eco
// verifica degli argomenti
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
System.exit(1);
}
// si recuperano i parametri
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
// si stabilisce la connessione con il server di eco
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
// dialogo client-server
BufferedReader in=null;
String msg=null;
String reponse=null;
iSrvEcho serveur=null;
try{
// apertura del flusso da tastiera
in=new BufferedReader(new InputStreamReader(System.in));
// ciclo di lettura dei messaggi da inviare al server di eco
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// invio del messaggio al server e ricezione della risposta
reponse=serveurEcho.echo(msg);
// monitoraggio
System.out.println("Réponse serveur : " + reponse);
// messaggio successivo
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// finito
System.exit(0);
// gestione degli errori
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port,
String nomService){
// richiede un riferimento dal server di eco
// tracciamento
System.out.println("--> Connexion au serveur CORBA en cours...");
// riferimento del server di eco
iSrvEcho serveurEcho=null;
try{
// si richiede un oggetto CORBA per lavorare
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// si utilizza il servizio di directory per individuare il server di eco
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// il servizio ricercato si chiama srvEcho - lo si richiede
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
// si restituisce il riferimento al server
return serveurEcho;
}// getServeurEcho
}// classe
Si ritrovano le stesse sequenze di codice presenti nel server:
- si crea un oggetto ORB che ci consentirà di accedere alla directory dei servizi CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
- si definiscono le diverse componenti del nome del servizio di eco
org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
- si richiede al servizio di directory un riferimento al servizio di eco (è qui che ci si differenzia dal server)
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. Test
10.2.7.1. Avvio del servizio di directory
Su un computer Windows, avviamo il servizio di directory nel modo seguente:
in questo modo si avvia il servizio di directory sulla porta 1000 del computer.
Il servizio di directory tnameserv genera un output simile al seguente:
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
Non è molto leggibile, ma teniamo presente l'ultima riga: il servizio è attivo sulla porta 1000.
10.2.7.2. Avvio del server di eco
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 sullo stesso computer 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 da quello del 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
Riprendiamo qui la descrizione del server SQL già esaminato nel contesto di Java RMI, sempre per evidenziare i punti in comune tra i due metodi e le loro differenze. Ricordiamo la funzione di questo server SQL: si trova su una macchina Windows e consente ai client remoti di accedere ai database pubblici ODBC di questa postazione Windows.

Il client CORBA potrebbe eseguire 3 operazioni:
- connettersi al database di sua scelta
- inviare richieste SQL
- chiudere la connessione
Il server esegue le richieste SQL del client e gli invia i risultati. Questo è il suo compito principale ed è per questo che lo chiamiamo server SQL. Applichiamo le diverse fasi viste in precedenza con il server di eco.
10.3.2. Scrittura dell’interfaccia IDL del server
Ricordiamo, per completezza, l’interfaccia RMI che avevamo utilizzato per il server:
import java.rmi.*;
// l'interfaccia remota
public interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp)
throws java.rmi.RemoteException;
public String[] executeSQL(String requete, String separateur)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
Il ruolo dei diversi metodi era il seguente:
Connect: il client si connette a un database remoto specificandone il driver, l’URL JDBC, nonché il proprio ID e la password per accedere a tale database. Il server restituisce una stringa di caratteri che indica l’esito della connessione:
executeSQL: il client richiede l’esecuzione di una query SQL sul database a cui è connesso. Indica il carattere che deve separare i campi nei risultati che gli vengono restituiti. Il server restituisce un array di stringhe:
per una richiesta di aggiornamento del database, dove n è il numero di righe aggiornate
se la richiesta ha generato un errore
se la richiesta non ha generato alcun risultato
se la query ha generato dei risultati. Le righe così restituite dal server sono i risultati della query.
Close: il client chiude la connessione con il database remoto. Il server restituisce una stringa che indica l’esito di tale 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();
};// interfaccia
};// modulo
L'unica novità rispetto a quanto visto con l'interfaccia IDL del server di eco è 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 indicare l'array, in questo caso «resultats»:
La parola chiave typedef è ben nota ai programmatori C/C++: consente di definire un nuovo tipo. In questo caso, il tipo resultats è definito come equivalente al tipo sequence<string>, c.a.d, ovvero un array dinamico (senza dimensioni fisse) di stringhe.
- Utilizzo del 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 viene inserita nel file srvSQL.idl. Si compila 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
Si nota che la compilazione ha generato una cartella che porta il nome del modulo dell'interfaccia IDL (srvSQL). Diamo un'occhiata al contenuto di questa cartella:
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 ricorda che i file Helper e Holder sono classi relative 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 file relativi al tipo resultats creato nell’interfaccia IDL.
Il file interSQL.java è il file Java dell’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 * Da: SRVSQL.IDL * Data: venerdì 19 marzo 09:59:48 1999 * Da: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 agosto 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()
;
}
Si nota che l’interfaccia è la stessa utilizzata per il client-server RMI. Possiamo quindi procedere. 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. Codice del server SQL
Ora scriviamo 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à e fatta eccezione per le sequenze di codice relative alla registrazione del servizio in una directory, il codice del server CORBA è identico a quello del server RMI:
// pacchetti importati
import java.sql.*;
import java.util.*;
import srvSQL.*;
// classe SQLServant
public class SQLServant extends _interSQLImplBase{
// dati globali della classe
private Connection DB;
// --------------- connessione
public String connect(String pilote, String url, String id,
String mdp){
// connessione all'URL del database tramite il driver
// autenticazione con ID e password
String resultat=null; // risultato del metodo
try{
// caricamento del driver
Class.forName(pilote);
// richiesta di connessione
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// errore
resultat="500 Echec de la connexion (" + e + ")";
}
// fine
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// esegue una query SQL sul database DB
// e inserisce i risultati in un array di stringhe
// dati necessari per l'esecuzione della query
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// creazione del contenitore della query
S=DB.createStatement();
// esecuzione della query
if (! S.execute(requete)){
// richiesta di aggiornamento
// viene restituito il numero di righe aggiornate
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// si trattava di una query di interrogazione
// si recuperano i risultati
RS=S.getResultSet();
// numero di campi del Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// vengono elaborati
while(RS.next()){
// creazione della riga dei risultati
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// aggiunta al vettore dei risultati
resultats.addElement(ligne);
}// while
// fine dell'elaborazione dei risultati
// si liberano le risorse
RS.close();
S.close();
// restituzione dei risultati
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){
// errore
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- chiudi
public String close(){
// chiude la connessione al database
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// restituisce il risultato
return resultat;
}
}// classe SQLServant
Questa classe è contenuta 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. Codifica del programma di avvio del server SQL
La classe precedente rappresenta il server SQL una volta avviato. In precedenza, deve essere registrato in una directory dei servizi CORBA. Come per il servizio echo, lo faremo con una classe speciale alla quale passeremo, al momento dell’esecuzione, tre parametri:
Macchina: macchina su cui si trova la directory dei servizi CORBA
Porta: porta su cui opera tale directory
nomService: nome del servizio SQL
Il codice di questa classe è quasi identico a quello della classe che svolgeva la stessa funzione per il servizio echo. Abbiamo evidenziato in grassetto la riga che differisce tra le due classi: non crea lo stesso oggetto-server. Si vede quindi che la meccanica di avvio del server rimane sempre la stessa. Se si isola questa meccanica in una classe, come è stato fatto qui, essa diventa quasi trasparente per lo sviluppatore.
// pacchetti importati
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- classe serveurSQL
public class serveurSQL{
// ------- main: avvia il server SQL
public static void main(String arg[]){
// serveurSQL macchina porta servizio
//: il numero di argomenti è corretto?
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
System.exit(1);
}
// si recuperano gli argomenti
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// è necessario un oggetto CORBA per lavorare
ORB orb=ORB.init(initORB,null);
// si inserisce il servizio nella directory dei servizi
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// si crea il server e lo si associa al servizio srvSQL
SQLServant serveurSQL=new SQLServant();
ncRef.rebind(path,serveurSQL);
orb.connect(serveurSQL);
// monitoraggio
System.out.println("Serveur SQL prêt");
// in attesa delle richieste dei clienti
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// si è verificato un errore
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// in attesa
}// 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. Scrittura del client
Il client del server CORBA viene richiamato con i seguenti parametri:
macchina: macchina su cui si trova la directory dei servizi CORBA
porta: porta su cui opera tale directory
nomService: nome del servizio SQL
driver: driver che il server SQL deve utilizzare per gestire il database desiderato
urlBase: URL JDBC del database da gestire
id: identificativo del cliente o null se non è presente alcun identificativo
mdp: password del cliente o null se non è presente alcuna password
separatore: carattere che il server SQL deve utilizzare per separare i campi delle righe di risultato di una query
Ecco un esempio di parametri possibili:
dove:
macchina: macchina su cui si trova la directory dei servizi CORBA
porta: porta su cui opera tale directory
srvSQL: srvSQL, 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 pubblici ODBC del computer Windows
ID: null, nessuna identità
password: null, nessuna password
separatore: , i campi dei risultati saranno separati da una virgola
Una volta avviato con i parametri precedenti, il client segue i seguenti passaggi:
- si connette al computer machine sulla porta port per richiedere il servizio CORBA srvSQL
- richiede l’accesso al database degli articoli
- chiede all’utente di digitare una query SQL sulla tastiera
- la invia al server SQL
- visualizza sullo schermo i risultati restituiti dal server
- chiede nuovamente all’utente di digitare una richiesta SQL sulla tastiera. Si interromperà quando la richiesta sarà terminata.
Di seguito è riportato il codice Java del client. I commenti dovrebbero essere sufficienti per comprenderlo. Si noterà che:
- il codice è identico a quello del client RMI già esaminato. Si differenzia da esso per il processo di richiesta del servizio alla directory, processo isolato nel metodo getServeurSQL.
- il metodo getServeurSQL è identico a quello scritto per il client di eco
Si nota quindi che:
- un client CORBA differisce da un client RMI solo per il modo in cui si connette al server
- tale modalità è identica per tutti i client CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// dati globali della 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[]){
// sintassi: cltSQL macchina porta separatore driver URL ID password
// macchina porta: macchina e porta dell'elenco dei servizi CORBA da contattare
// servizio: nome del servizio
// driver: driver da utilizzare per il database da utilizzare
// urlBase: URL JDBC del database da utilizzare
// id: identificativo dell'utente
// mdp: la sua password
// separatore: stringa che separa i campi nei risultati di una query
// verifica del numero di argomenti
if(arg.length!=8)
erreur(syntaxe,1);
// Inizializzazione dei parametri di connessione al database
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];
// parametri del servizio di directory CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// client CORBA - richiesta di un riferimento al server SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// dialogo client-server
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// apertura del flusso da tastiera
in=new BufferedReader(new InputStreamReader(System.in));
// monitoraggio
System.out.println("--> Connexion à la base de données en cours");
// richiesta di connessione iniziale al database
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// monitoraggio
System.out.println("<-- "+reponse);
// analisi della risposta
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// ciclo di lettura delle richieste da inviare al server SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// invio della richiesta al server e ricezione della risposta
lignes=serveurSQL.executeSQL(requete,separateur);
// monitoraggio
afficheLignes(lignes);
// richiesta successiva
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// monitoraggio
System.out.println("--> Fermeture de la connexion à la base de données distante");
// chiusura della connessione
reponse=serveurSQL.close();
// seguito
System.out.println("<-- " + reponse);
// fine
System.exit(0);
// gestione degli errori
} 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
// ------------ errore
private static void erreur(String msg, int exitCode){
// visualizzazione messaggio di errore
System.err.println(msg);
// eventuale liberazione delle risorse
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// uscita
System.exit(exitCode);
}// errore
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// richiesta di un riferimento dal server SQL
// macchina: macchina della directory dei servizi CORBA
// porta: porta dell'elenco dei servizi CORBA
// servizio: nome del servizio CORBA da richiedere
// monitoraggio
System.out.println("--> Connexion au serveur CORBA en cours...");
// riferimento del server SQL
interSQL serveurSQL=null;
// parametri del servizio di directory CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// si richiede un oggetto CORBA per lavorare - a tal fine si utilizza la porta
// di ascolto della directory dei servizi CORBA
ORB orb=ORB.init(initORB,null);
// si utilizza il servizio di directory per individuare il server SQL
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// il servizio ricercato si chiama srvSQL - lo si richiede
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
// si restituisce il riferimento al server
return serveurSQL;
}// getServeurSQL
}// classe
Compiliamo la classe del 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. Pré-requis
Si suppone che un database ACCESS denominato «Articoli» sia pubblicamente disponibile sul server Windows SQL:

Questo database ha la seguente struttura:
nome | tipo |
code | codice articolo di 4 caratteri |
nom | nome (stringa di caratteri) |
prix | il suo prezzo (effettivo) |
stock_actu | la sua disponibilità attuale (numero intero) |
stock_mini | la quantità minima (numero intero) al di sotto della quale è necessario rifornire l'articolo |
10.3.7.2. Lancio del servizio di elenco
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
Si avvia il server SQL:
Viene visualizzato in una finestra DOS:
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 ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Query: select nome, stock_actu, stock_mini from articoli
<-- 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
--> Query: delete from articoli where stock_mini<7
<-- 100 1
--> Query: select nome, stock_actu, stock_mini da articoli
<-- 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. Avvio di un client su un computer diverso da quello del server
Ecco i risultati ottenuti con un client su un computer diverso da quello del 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 ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Query: 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. Corrispondenze IDL - JAVA
Di seguito riportiamo le corrispondenze tra i tipi semplici IDL e JAVA:
tipo IDL | tipo Java |
boolean | boolean |
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 successivamente si utilizza nomType per fare riferimento al tipo dell'array. Pertanto, nell'interfaccia del server SQL è stata utilizzata la dichiarazione:
affinché resultats indichi un array String[] in Java.