Skip to content

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:

Image

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:

  1. Scrivere l'interfaccia del server utilizzando l'IDL (Interface Definition Language)
  2. Generazione delle classi "skeleton" e "stub" del server
  3. Scrittura del server
  4. Scrittura del client
  5. compilazione di tutte le classi
  6. Avvio di una directory di servizi CORBA
  7. Avvio del server
  8. 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:

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

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:

module echo
è equivalente al **pacchetto** Java **echo**. La compilazione dell'interfaccia genererà il pacchetto Java *echo*, ovvero una directory contenente classi Java.
interface iSrvEcho
è equivalente **all'interfaccia** Java **iSrvEcho**. Genererà un'interfaccia Java.
string echo(in string msg)
è 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:

iSrvEcho.java

l'interfaccia del server

_iSrvEchoImplbase.java

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.

_iSrvEchoStub.java

Questo è lo "stub" del server che verrà utilizzato dal client. Fornisce al client le funzionalità CORBA necessarie per accedere al server.

iSrvEchoHelper.java

Fornisce i metodi necessari per gestire i riferimenti agli oggetti CORBA

iSrvEchoHolder.java

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:

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


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


Prop :     propriétés de l’application

L'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

    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

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)

serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

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:

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

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:

    Serveur d’écho prêt

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.

Image

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:

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

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:

    100 n
per una query di aggiornamento del database, dove n è il numero di righe aggiornate
    500 msg d’erreur
    se la query ha generato un errore
    501 Pas de résultats
    se la query non ha restituito risultati
    101 ligne1
    101 ligne2
    101 ...
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:

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

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:
typedef sequence<string> resultats;

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
resultats executeSQL(in string requete, in string separateur);

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:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

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:

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

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
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • chiedono all'utente di digitare una query SQL sulla tastiera
  • la inviano al server SQL
executeSQL(requete, ’’,’’);
  • 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:

Image

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:

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

In una finestra DOS viene visualizzato quanto segue:

    Serveur SQL prêt

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:

    typedef   sequence<T> nomType;

e che poi usiamo TypeName per riferirci al tipo di array. Pertanto, nell'interfaccia SQL Server, abbiamo utilizzato la dichiarazione:

    typedef sequence<string> resultats;

in modo che results si riferisca a un array String[] in Java.