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
|