Skip to content

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:

Image

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:

  1. scrittura dell’interfaccia del server con IDL (Interface Definition Language)
  2. generazione delle classi «scheletro» e «stub» del server
  3. scrittura del server
  4. scrittura del client
  5. compilazione di tutte le classi
  6. avvio di un elenco dei servizi CORBA
  7. avvio del server
  8. 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:

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

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:

module echo

è equivalente al pacchetto echo di Java. La compilazione dell’interfaccia darà origine al pacchetto Java echo c.a.d, una directory contenente classi Java.

interface iSrvEcho

è equivalente all’interfaccia iSrvEcho di Java. Darà origine a un’interfaccia Java.

string echo(in string msg)

è 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:

iSrvEcho.java

l’interfaccia del server

_iSrvEchoImplbase.java

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.

_iSrvEchoStub.java

È l’immagine («stub») del server che verrà utilizzata dal client. Fornisce al client le funzionalità CORBA per raggiungere il server.

iSrvEchoHelper.java

Fornisce i metodi necessari per la gestione dei riferimenti agli oggetti CORBA

iSrvEchoHolder.java

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 lecho remoto
public class srvEcho extends _iSrvEchoImplBase{
    // metodo che implementa lecho
    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:

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 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
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

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)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

10.2.6.3. Compilation

E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java

E:\data\java\corba\ECHO>dir

CLTECH~1 CLA         2 599  18/03/99  13:51 cltEcho.class
CLTECH~1 JAV         2 907  16/03/99  16:15 cltEcho.java
ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.7. Test

10.2.7.1. Avvio del servizio di directory

Su un computer Windows, avviamo il servizio di directory nel modo seguente:

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

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:

    Serveur d’écho prêt

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.

Image

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:

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

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:

    100 n

per una richiesta di aggiornamento del database, dove n è il numero di righe aggiornate

    500 msg d’erreur

se la richiesta ha generato un errore

    501 Pas de résultats

se la richiesta non ha generato alcun risultato

    101 ligne1
    101 ligne2
    101 ...

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:

    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();
    };// 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»:
typedef sequence<string> 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
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 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:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

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:

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

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

Image

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:

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

Viene visualizzato in una finestra DOS:

    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 ,
--> 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:

    typedef   sequence<T> nomType;

e che successivamente si utilizza nomType per fare riferimento al tipo dell'array. Pertanto, nell'interfaccia del server SQL è stata utilizzata la dichiarazione:

    typedef sequence<string> resultats;

affinché resultats indichi un array String[] in Java.