Introduzione
In
Java esiste la possibilità di ottenere a run-time una serie d’informazioni
sulla composizione di un qualunque oggetto (costruttori, metodi, attributi,
…). Grazie a queste informazioni è possibile utilizzare un’istanza
(invocare metodi, impostare attributi, …) pur non conoscendo a priori la
sua classe. Questa potenzialità consente di realizzare soluzioni
molto eleganti e flessibili a problemi storicamente complessi quali la
definizione/manipolazione di componenti.
CORBA
fornisce meccanismi simili a quelli appena descritti, tutti i CORBA object
possono fornire a run-time informazioni sulla propria struttura. Nel gergo
CORBA queste informazioni sono dette metadata e pervadono l’intero sistema
distribuito. Il repository destinato a contenere i metadata è l’Interface
Repository che consente di reperire ed utilizzare un oggetto ottenendo
dinamicamente la sua descrizione IDL.
Queste
caratteristiche conferiscono una flessibilità notevole al modello
CORBA. Nella pratica risultano particolarmente attraenti per i venditori
di tool che sviluppano componenti distribuiti. L’invocazione dinamica,
presentando minori performance e maggiori difficoltà di sviluppo,
è sicuramente meno interessante per un utilizzo medio.
Introspezione
CORBA
Ogni
oggetto CORBA specializza CORBA::Object, le capacità introspettive
di ogni CORBA object derivano da questa superclasse
//
IDL
module
CORBA {
//
…
interface
Object {
// …
IRObject get_interface_def();
ImplementationDef get_implementation();
boolean is_nil();
boolean is_a(in string logical_type_id);
boolean is_equivalent(in Object other_object);
boolean non_existent();
};
};
Il
metodo get_interface_def può essere visto come l’equivalente del
getClass Java, fornisce i dettagli relativi ad un oggetto (attributi, metodi,
…). Il metodo restituisce un oggetto generico di tipo IRObject, per ottenere
i dettagli di cui sopra è necessario effettuare il narrow ad InterfaceDef.
Fino alle specifiche 2.3 esisteva un metodo get_interface che restituiva
direttamente un oggetto di tipo InterfaceDef, molti ORB lo supportano ancora
per backward compatibility.
I restanti
metodi sono funzioni di test: is_nil è l’equivalente CORBA di “obj
== null”, is_a controlla la compatibilità dell’oggetto con l’interfaccia
data, is_equivalent verifica l’equivalenza tra due reference e non_existent
verifica se l’oggetto non è più valido.
Va
notato che le signature viste sopra rappresentano le signature IDL del
CORBA::Object, ma nella maggior parte delle implementazioni i metodi visti
sopra sono forniti dal CORBA.Object con un ‘_’ davanti (_is_a, _get_implementation,
…).
Interface Repository
(IR)
L’Interface
Repository è il servizio che contiene e fornisce le informazioni
sulla struttura IDL degli oggetti. Si è visto nel paragrafo precedente
che, attraverso l’invocazione del metodo get_interface_def, è possibile
ottenere un oggetto di tipo InterfaceDef che fornisca tutte le informazioni
sulla struttura di una interfaccia.
L’IR
contiene in forma persistente gli oggetti InterfaceDef che rappresentano
le interfacce IDL registrate presso il repository. L’IR può esser
utilizzato anche dall’ORB per effettuare il type-checking nell’invocazione
dei metodi, per assistere l’interazione tra differenti implementazioni
di ORB o per garantire la correttezza del grafo di derivazione.
Nel
caso in cui si intenda utilizzare esplicitamente l’IR sarà necessario
registrare l’IDL. La registrazione varia nelle varie implementazioni. VisiBroker
prevede due comandi: irep (attiva l’IR) ed idl2ir (registra un IDL presso
l’IR). Il JDK non fornisce attualmente alcuna implementazione dell’IR.
Interrogando
l’IR è possibile ottenere tutte le informazioni descrivibili con
un’IDL. La navigazione di queste informazioni avviene tipicamente a partire
dalla classe InterfaceDef e coinvolge un complesso insieme di classi che
rispecchiano l’intera specifica IDL: ModuleDef, InterfaceDef, OperationDef,
ParameterDef, AttributeDef, ConstantDef, ...
Dynamic Invocation
Interface (DII)
Negli
esempi visti finora, per invocare i metodi sugli oggetti CORBA si è
utilizzata l’invocazione statica. Questo modello di invocazione richiede
la precisa conoscenza, garantita dalla presenza dello stub, dell’interfaccia
dell’oggetto.
In
realtà in CORBA è possibile accedere ad un oggetto, scoprire
i suoi metodi ed eventualmente invocarli, senza avere alcuno stub
precompilato e senza conoscere a priori l’interfaccia esposta dall’oggetto
CORBA.
Questo
implica la possibilità di scoprire le informazioni di interfaccia
a run-time. Ciò è possibile utilizzando una delle più
note caratteristiche CORBA: la Dynamic Invocation Interface (DII).
La
DII opera soltanto nei confronti del client, esiste un meccanismo analogo
lato server (Dynamic Skeleton Interface, DSI) che consente ad un ORB di
dialogare con un’implementazione senza alcuno skeleton precompilato.
Purtroppo,
anche se DII fa parte del core CORBA, le sue funzionalità sono disperse
su un gran numero di oggetti. Per invocare dinamicamente un metodo su di
un oggetto i passi da compiere sono:
-
Ottenere
un riferimento all’oggetto: anche con DII per utilizzare un oggetto è
necessario ottenere un riferimento valido. Il client otterrà un
riferimento generico ma, non avendo classi precompilate, non potrà
effettuare un narrow o utilizzare meccanismi quali il bind fornito dall’Helper.
-
Ottenere
l’interfaccia: invocando il metodo get_interface_def sul riferimento si
ottiene un IRObject e da questo, con narrow, l’oggetto navigabile di tipo
InterfaceDef. Questo oggetto è contenuto nell’IR e consente di ottenere
tutte le informazioni di interfaccia. Con i metodi lookup_name e describe
di InterfaceDef è possibile reperire un metodo ed una sua completa
descrizione.
-
Creare
la lista di parametri: per definire la lista di parametri viene usata una
struttura dati particolare, NVList (Named Value List). La creazione di
una NVList è effettuata o da un metodo dell’ORB (create_operation_list)
o da un metodo di Request (arguments). In ogni caso con il metodo add_item
è possibile comporre la lista di parametri.
-
Creare
la Request: la Request incapsula tutte le informazioni necessarie all’invocazione
di un metodo (nome metodo, lista argomenti e valore di ritorno). Comporre
la Request è la parte più pesante e laboriosa della DII.
Può essere creata invocando sul reference dell’oggetto il metodo
create_request o la versione semplificata _request.
-
Invocare
il metodo: utilizzando Request esistono più modi di invocare il
metodo. Il primo modo è quello di invocarlo in modalità sincrona
con invoke. Il secondo modo è quello di invocarlo in modalità
asincrona con send_deferred e controllare in un secondo momento la risposta
con poll_response o get_response. Il terzo modo è l’invocazione
senza response con send_oneway.
Come
si può osservare, un’invocazione dinamica può essere effettuata
seguendo percorsi differenti. Lo scenario più complesso implica
un’interazione con l’Interface Repository (il secondo ed il terzo passo
dell’elenco precedente) mediante la quale è possibile ottenere l’intera
descrizione di un metodo.
Scenari
più semplici non prevedono l’interazione con l’IR e sono ad esempio
adottati quando ci si limita a pilotare l’invocazione dinamica con parametri
inviati da script.
Poiché
l’invocazione dinamica è un argomento complesso e di uso non comune,
verrà proposto un semplice esempio che non utilizzi l’IR, ma sia
pilotato da linea di comando.
Definiamo
una semplice interfaccia come segue
//
IDL
module
dii {
interface
DynObject {
string m0();
string m1(in string p1);
string m2(in string p1, in string p2);
string m3(in string p1, in string p2, in string p3);
};
};
Come
si è detto in precedenza, l’uso di DII non ha alcuna influenza sul
lato server. Il server si limiterà ad istanziare l’oggetto e a registrarlo
col nome di DynObject sul Naming Service.
L’implementazione
dell’oggetto è molto semplice ed ha come unico scopo quello di consentire
il controllo dei parametri e del metodo invocato
package
server;
import
dii.*;
public
class DynObjectImpl extends _DynObjectImplBase {
public DynObjectImpl() {
super();
}
public
String m0() {
return "Metodo 0 # nessun parametro";
}
public
String m1(String p1) {
return "Metodo 1 # " + p1;
}
public
String m2(String p1, String p2) {
return "Metodo 2 # " + p1 + " - " + p2;
}
public
String m3(String p1, String p2, String p3) {
return "Metodo 3 # " + p1 + " - " + p2 + " - " + p3;
}
}
Il
client deve identificare dall’input fornito da linea di comando il metodo
da invocare ed i suoi eventuali parametri; il primo passo significativo
da compiere è l’accesso al Naming Service
//
…
org.omg.CORBA.Object
obj = ncRef.resolve(path);
Poiché
stub, skeleton, Helper ed Holder saranno distribuiti solo sul server, non
è possibile effettuare un narrow.
A
questo punto è possibile iniziare a costruire la Request con il
metodo più semplice
org.omg.CORBA.Request
request = obj._request(args[0]);
Si
noti che il parametro passato args[0] è il nome del metodo che si
intende utilizzare, letto da linea di comando.
Ora
è possibile costruire la lista di argomenti, nel caso in esame la
lista è costruita dinamicamente effettuando un parsing dell’array
di input
org.omg.CORBA.NVList
arguments = request.arguments();
for
(int i = 1; i < args.length; i++) {
org.omg.CORBA.Any
par = orb.create_any();
par.insert_string(args[i]);
arguments.add_value("p"
+ 1, par,
org.omg.CORBA.ARG_IN.value);
}
Ogni
valore della NVList è rappresentato da un oggetto di tipo Any, questo
è uno speciale tipo CORBA che può incapsulare qualunque altro
tipo ed ha un’API che fornisce specifiche operazioni di inserimento ed
estrazione di valori (nell’esempio si usano insert_string ed extract_string).
Per
invocare il metodo è ancora necessario impostare il tipo di ritorno.
Per fare questo si utilizza l’interfaccia TypeCode che è in grado
di rappresentare qualunque tipo IDL. Le costanti che identificano i TypeCode
dei vari tipi IDL sono fornite da TCKind
request.set_return_type(orb.get_primitive_tc
(org.omg.CORBA.TCKind.tk_string));
request.invoke();
L’invocazione
utilizza la normale chiamata sincrona di metodo data da invoke.
Terminata
l’invocazione è possibile ottenere e visualizzare la stringa di
ritorno
org.omg.CORBA.Any
method_result = request.return_value();
System.out.println(method_result.extract_string());
L’esecuzione
dell’esempio è possibile con VisiBroker utilizzando i consueti passi,
in particolare si faccia riferimento all’esempio visto in precedenza sul
Naming Service ([1]).
DII
fornisce un meccanismo estremamente elastico e flessibile che prospetta
un sistema assolutamente libero in cui tutti gli oggetti interrogano un
Trader Service, individuano ed utilizzano dinamicamente i servizi di cui
necessitano.
Malauguratamente
l’invocazione dinamica presenta alcuni limiti che ne condizionano l’utilizzo.
In primo luogo l’invocazione dinamica è decisamente più lenta
dell’invocazione statica poiché ogni chiamata a metodo implica un
gran numero di operazioni remote.
In
secondo luogo la costruzione di Request è un compito complesso e
richiede uno sforzo supplementare in fase di sviluppo (lo sviluppatore
deve implementare i compiti normalmente svolti dallo stub). L’invocazione
dinamica è inoltre meno robusta in quanto l’ORB non può effettuare
il typechecking prima dell’invocazione, questo può causare anche
un crash durante l’unmarshalling.
Conclusioni
In
questo articolo abbiamo visto un semplice esempio di invocazione dinamica
con CORBA. Il prossimo mese vedremo come realizzare callback con CORBA.
Bibliografia
[1]
G. Morello - "Corso CORBA IV Parte: CORBA Naming Service", Mokabyte N.
51, Aprile 2001
Allegati
Gli
esempi completi si possono scaricare qui |