MokaByte 50 - Marzo 2001
Foto dell'autore non disponibile
di
Gianluca Morello
Corso CORBA
III Parte: un semplice esempio
Questo mese realizzeremo una semplice applicazione CORBA: dall’IDL al codice Java

Introduzione
In un caso semplice i passi da seguire per creare, esporre ed utilizzare un oggetto CORBA sono i seguenti:
  • Descrivere mediante IDL l’interfaccia dell’oggetto che si intende implementare
  • Compilare con il tool apposito il file IDL
  • Identificare tra le classi e le interfacce generate quelle necessarie alla definizione  dell’oggetto e specializzarle opportunamente
  • Scrivere il codice necessario per inizializzare l’ORB ed informarlo circa la presenza dell’oggetto creato
  • Compilare il tutto con un normale compilatore Java
  • Avviare la classe di inizializzazione e l’applicazione distribuita


Definizione IDL
Definiamo un semplice oggetto Calcolatrice in grado di computare la somma tra due numeri interi dati in input.
 

// IDL
module utility {
   interface Calcolatrice {
   long somma(in long a, in long b);
   };
};


Compiliamo il file Calcolatrice.idl mediante il compilatore fornito dall’ORB (vedi [1]). Il processo di compilazione creerà una directory utility e gli opportuni file Java: 

_CalcolatriceImplBase
_CalcolatriceStub
CalcolatriceOperations
Calcolatrice, 
CalcolatriceHelper
CalcolatriceHolder


Implementare l’oggetto remoto
La classe base per l’implementazione è la classe astratta _CalcolatriceImplBase.java, ovvero lo skeleton. 


Figura 1 - Gerarchia di derivazione della classe servant



Si può notare nella figura 1 come l’implementazione dell’interfaccia “remota” Calcolatrice non sia a carico dell’oggetto remoto, bensì a carico dello skeleton.

Lo skeleton è una classe astratta e non fornisce alcuna implementazione del metodo somma definito nell’interfaccia IDL. Per definire il servant CalcolatriceImpl sarà quindi necessario specializzare _CalcolatriceImplBase e fornire l’opportuna implementazione del metodo somma.

Ecco il codice completo del servant

// JAVA
package server;

import utility.*;

public class CalcolatriceImpl extends _CalcolatriceImplBase {

   public CalcolatriceImpl() {
    super();
   }

 // Implementazione del metodo remoto
 public int somma(int a, int b) {
  return a + b;
 }
}

Poiché Java non supporta l’ereditarietà multipla, in alcune situazioni può essere limitante dover derivare necessariamente il servant da ImplBase. Nel caso in cui il servant debba derivare da un’altra classe è possibile utilizzare un meccanismo alternativo di delega detto Tie che non implica la specializzazione di ImplBase. In questo articolo l’approccio Tie non sarà esaminato.
 
 
 

Implementare la classe Server
Si è già avuto modo di notare negli articoli precedenti come nel gergo CORBA il componente remoto che espone i servizi venga definito servant. Il server invece è la classe che inizializza l’environment, istanzia l’oggetto remoto, lo rende disponibile ai client e si pone in attesa.

La classe server è quindi una classe di servizio che ha come compito fondamentale quello di creare e agganciare all’ORB l’istanza di oggetto remoto che utilizzeranno i client e di fornire a questa un contesto di esecuzione.
L’inizializzazione dell’ORB è effettuata utilizzando il metodo init

org.omg.CORBA.ORB orb;
orb=org.omg.CORBA.ORB.init(args, null);
il parametro args è semplicemente l’array di input. Saranno quindi valorizzabili da linea di comando alcune proprietà dell’ORB (per un elenco delle proprietà disponibili consultare la documentazione dell’implementazione CORBA utilizzata). L’ORB ignorerà tutti gli argomenti non riconosciuti come proprietà valide.

In questo primo esempio l’aggancio è effettuato senza l’ausilio di un Object Adapter. Una semplice forma di “registrazione” è fornita dal metodo connect dell’ORB

CalcolatriceImpl calc = new CalcolatriceImpl();
orb.connect(calc);
Utilizzando un meccanismo di questo tipo, il servant va considerato come un oggetto CORBA di tipo transient. Un riferimento ad un oggetto di questo tipo è valido solo nel tempo di vita di una precisa istanza del servant. Gli oggetti di tipo persistent saranno analizzati in un prossimo articolo.
Per ogni ORB CORBA 2.0 compliant, l’object reference (IOR) (vedi [2]) in versione stringa è ottenibile invocando 
orb.object_to_string(calc)
La stringa ottenuta è il riferimento CORBA all’istanza di calcolatrice; come tale è  esattamente tutto ciò di cui necessita un client per accedere ai servizi dell’oggetto. Per fornire al client lo IOR esistono molte soluzioni, la più semplice consiste nel salvarlo su file
PrintWriter out=new PrintWriter(
            new BufferedWriter(new FileWriter(args[0])));
out.println(orb.object_to_string(calc));
out.flush();
out.close();
A questo punto il server può mettersi in attesa. L’attesa è necessaria in quanto l’istanza di calcolatrice “vive” solo e soltanto nel contesto fornito dall’applicazione server; è all’interno di questa che è stato effettuato il new. Si può implementare un’attesa idle del processo server utilizzando il metodo wait di un Java Object

java.lang.Object sync = new java.lang.Object();
synchronized (sync) {
 sync.wait();
}

Ecco il codice completo della classe CalcolatriceServer

// JAVA
package server;

import utility.*;
import java.io.*;

public class CalcolatriceServer {

  public static void main(String[] args) {

    if (args.length!=1) {
      System.err.println("Manca argomento: path file ior");
      return;
    }

    try {

      // Inizializza l'ORB.
      org.omg.CORBA.ORB orb = 
      org.omg.CORBA.ORB.init(args, null);

      // Crea un oggetto Calcolatrice
      CalcolatriceImpl calc = new CalcolatriceImpl();
      orb.connect(calc);

    // Stampa l'object reference in versione stringa
      System.out.println("Creata Calcolatrice:\n" + 
                             orb.object_to_string(calc));

      // Scrive l'object reference nel file 
      PrintWriter out = new PrintWriter(new 
     BufferedWriter(new FileWriter(args[0])));
      out.println(orb.object_to_string(calc));
      out.close();

    // Attende l'invocazione di un client
    java.lang.Object sync = new java.lang.Object();
  synchronized (sync) {
   sync.wait();
       }

    } catch (Exception e) {
    System.err.println("Server error: " + e);
  e.printStackTrace(System.out);
  }
  }
}

Implementare il Client
Il client dovrà in primo luogo inizializzare l’ORB con il metodo init(), come effettuato nella classe server.
Per ottenere il riferimento all’istanza di calcolatrice, il client dovrà leggere lo IOR memorizzato nel file generato dal server

BufferedReader in = new BufferedReader(new  FileReader(args[0]));
String ior = in.readLine();
in.close();
E’ possibile ottenere lo IOR invocando il metodo opposto a quello utilizzato per trasformarlo in stringa. Il metodo string_to_object fornito dall’ORB restituisce un CORBA Object a partire dalla stringa che rappresenta il suo IOR
org.omg.CORBA.Object obj = orb.string_to_object(ior);
Il metodo string_to_object restituisce un oggetto di tipo generico e non un riferimento che consenta di invocare il metodo somma. 
Per ottenere tale riferimento, in uno scenario Java, si effettuerebbe un cast (in RMI ad esempio dopo aver effettuato una lookup si opera un cast per ottenere il tipo corretto). In un contesto CORBA invece, per convertire il generico oggetto in un oggetto di tipo determinato, bisogna utilizzare il metodo narrow della classe <Tipo>Helper
Calcolatrice calc = CalcolatriceHelper.narrow(obj);
A questo punto il client è in condizione di invocare il metodo remoto con le medesime modalità usate per una comune invocazione di metodo
calc.somma(a, b)
dove a e b sono da intendersi come 2 int. E’ da notare come questo non sia l’unico modello di invocazione CORBA. Un’invocazione di questo tipo viene detta invocazione statica, in uno dei prossimi articoli sarà affrontata l’invocazione dinamica.
Ecco il codice completo del client
package client;

import utility.*;
import java.io.*;

public class CalcolatriceClient {

 public static void main(String args[]) {

   if (args.length!=1) {
     System.err.println("Manca argomento: path file ior");
      return;
    }

 try{

     // Crea ed inizializza l'ORB
    org.omg.CORBA.ORB orb = 
     org.omg.CORBA.ORB.init(args, null);

     // Legge dal file il reference all'oggetto 
  // Si assume che il server lo abbia generato
     BufferedReader in = new BufferedReader(
         new FileReader(args[0]));
     String ior = in.readLine();
     in.close();

     // Ottiene dal reference un oggetto remoto...
     org.omg.CORBA.Object obj = orb.string_to_object(ior);

       // ... e ne effettua il narrow a tipo Calcolatrice
       Calcolatrice calc = CalcolatriceHelper.narrow(obj);

  // Ottiene da input tastiera i 2 numeri da sommare
  BufferedReader inputUser = new BufferedReader (
       new InputStreamReader(System.in)); 
  String first, second;
  int a, b;

  // Leggo primo addendo
  System.out.println ();
  System.out.print("A = ");
  first = inputUser.readLine();
  a = Integer.valueOf(first).intValue ();

  // Leggo secondo addendo
  System.out.println ();
  System.out.print("B = ");
  second = inputUser.readLine();
  b = Integer.valueOf(second).intValue ();

  // Invoca il metodo remoto passandogli i parametri
  System.out.println ();
  System.out.print("Il risultato e': ");
  System.out.print(calc.somma(a, b));

 } catch (Exception e) {
  e.printStackTrace();
 }
  }
}


Eseguire l’esempio
Dopo aver compilato tutte le classi, comprese quelle generate dal precompilatore, è finalmente possibile eseguire l’esempio.
La prima classe da mandare in esecuzione è la classe CalcolatriceServer

java server.CalcolatriceServer calc.ior
passando come parametro il path del file su cui si intende memorizzare lo IOR.
L’output prodotto sarà

Creata Calcolatrice:
IOR:000000000000001d49444c3a7574696c6974792f43616c636f6c6174726
963653a312e300000000000000001000000000000002c000100000000000469
6e6b0005b7000000000018afabcafe00000002a1ed120b00000008000000000
0000000

E’ bene che, poiché il processo server si pone in attesa, l’esecuzione effettivamente non termina. Se si terminasse il processo, il client avrebbe uno IOR inservibile in quanto l’istanza identificata da questo non sarebbe più “viva”.
A questo punto si può mandare in esecuzione il client fornendogli il path del file generato dal server

java client.CalcolatriceClient calc.ior
Il programma richiederà in input i due numeri da sommare (non sono stati gestiti eventuali errori di conversione) e stamperà, a seguito di un’invocazione remota, il risultato.
 
 
 

Client e server Stub
E’ bene effettuare una breve digressione sul concetto di stub (letteralmente surrogato). Lo stub  compare in molti scenari di programmazione (ad esempio in DLL ed in generale nella programmazione distribuita). Esiste una precisa corrispondenza con un celebre pattern di programmazione: il pattern Proxy.
Il pattern Proxy viene tipicamente utilizzato per aggiungere un livello di indirezione. Il Proxy è un surrogato di un altro oggetto ed è destinato a controllare l’accesso a quest’ultimo. In generale implica la presenza di un oggetto, detto Proxy, che abbia la stessa interfaccia dell’oggetto effettivo, detto Real Subject. Il Proxy riceve le richieste destinate al Real Subject e le comunica a quest’ultimo effettuando eventualmente delle operazioni prima e/o dopo l'accesso.
Osservando la figura 2 è possibile vedere come lo stub e lo skeleton implementino la stessa interfaccia, quella dell’oggetto remoto. Quindi un generico client sarà in grado di dialogare con lo stub invocando i metodi che intende far eseguire all’oggetto remoto. In questo senso lo stub opera da procuratore dell’oggetto presso il client (Proxy significa procuratore, delegato per l’appunto).


Figura 2 — Il pattern Proxy e gli stub server e client

Lo stub, nella sua opera di delegato, sarà in grado di rendere invisibili al client tutti i dettagli della comunicazione remota e della locazione fisica dell’oggetto. Nell’ottica del client il dialogo sarà operato direttamente con l’oggetto remoto (questo è garantito dal fatto che lo stub implementa l’interfaccia dell’oggetto remoto). Sostituire un’implementazione non avrà alcun impatto sul client purché l’interfaccia rimanga inalterata.
 
 
 

Un possibile miglioramento
La soluzione proposta nell’esempio precedente è decisamente primitiva. Di fatto l’accesso ad un oggetto remoto è possibile solo se il client ed il server condividono una porzione di file system (in realtà sono utilizzabili anche altri “sofisticati” meccanismi quali mail, floppy, …).
Un primo miglioramento potrebbe essere ottenuto utilizzando un Web Server. Con un Web Server attivo un client potrebbe leggere il file contenente lo IOR via HTTP.
Il codice di lettura del client potrebbe essere qualcosa del genere

URL urlIOR = new URL(args[0]);
DataInputStream in = 
                              new DataInputStream(urlIOR.openStream());
String ior = in.readLine();
in.close();


Al client andrebbe fornito non più il path, ma l’url corrispondente al file generato dal server, ad esempio http://localhost/corba/calc.ior.
Il server dovrà generare il file nella virtual directory corretta del Web Server (“corba” nell’esempio). Nel caso non si disponga di un Web Server è possibile scaricare gratuitamente lo “storico” Apache da www.apache.org.

Anche con questa modifica la soluzione, pur essendo praticabile in remoto e totalmente portabile, è ben lontana dall’ottimo. Tra i difetti che presenta è bene notare come in parte violi la location transparency promessa da CORBA: non si conosce l’effettiva collocazione dell’implementazione, ma è necessario conoscere l’url del file IOR.
Un approccio decisamente migliore prevede l’utilizzo del CORBA Naming Service.
 
 
 

Conclusioni
In questo articolo abbiamo presentato un primo semplice esempio di applicazione CORBA. Il prossimo mese miglioreremo “l’utilissima” Calcolatrice remota con l’ausilio del CORBA Naming Service.
 
 
 

Bibliografia
[1] G. Morello - "Corso CORBA II Parte: mapping IDL to Java", Mokabyte N. 49, Febbraio 2001
[2] G. Morello - "Corso CORBA I Parte: una breve introduzione", Mokabyte N. 48, Gennaio 2001

 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it