Introduzione
L’Object
Adapter è un componente molto importante dell’architettura CORBA.
Uno dei suoi compiti è quello di associare un riferimento ad una
specifica implementazione nel momento in cui un oggetto è invocato.
Quando un client effettua un’invocazione l’adapter collabora con l’ORB
e con l’implementazione per fornire il servizio richiesto.
Se
un client chiama un oggetto che non è effettivamente in memoria,
l’adapter si occupa anche di attivare l’oggetto affinché questo
possa rispondere all’invocazione. In molte implementazioni l’adapter può
occuparsi anche di disattivare un oggetto non utilizzato da lungo tempo.
Dal punto di vista del client l’implementazione è sempre disponibile
e caricata in memoria.
Formalmente
le specifiche CORBA individuano per l’adapter sei funzionalità chiave:
-
Generazione
ed interpretazione degli object references
-
Invocazione
dei metodi attraverso lo skeleton
-
Sicurezza
delle interazioni
-
Autenticazione
alla chiamata (utilizzando un’entità CORBA detta Principal)
-
Attivazione
e disattivazione degli oggetti
-
Mapping
tra reference e corrispondente implementazione
-
Registrazione
dell’implementazione
Queste
funzionalità sono associate al componente logico adapter e nella
pratica sono compiute in collaborazione con il core dell’ORB ed eventualmente
con altri componenti; alcune funzionalità sono delegate integralmente
all’ORB ed allo skeleton. L’adapter è comunque coinvolto in ogni
invocazione di metodo.
Le
funzionalità dell’adapter rendono disponibile via ORB l’implementazione
del CORBA object e supportano l’ORB nella gestione del run-time environment
dell’oggetto. Dal punto di vista del client, l’adapter è il componente
che garantisce che le sue richieste siano recapitate ad un oggetto attivo
in grado di soddisfare la richiesta.
Il
meccanismo CORBA opera in modo tale da consentire l’utilizzo contemporaneo
di più tipologie di adapter con differenti comportamenti (nelle
specifiche gli adapter vengono definiti pluggable). A livello di design,
l’idea di individuare un’altra entità come l’adapter nasce dalla
necessità di modellare in maniera flessibile alcuni aspetti senza
estendere l’interfaccia dell’ORB, ma individuando nuovi moduli pluggable.
Il
primo tipo di adapter introdotto da OMG è il Basic Object Adapter
o BOA. Poiché le specifiche del BOA erano lacunose (non definivano
ad esempio i meccanismi di attivazione e disattivazione degli oggetti),
i vari venditori finirono per realizzarne implementazioni proprietarie
largamente incompatibili tra loro che minavano di fatto la portabilità
lato server di CORBA. Per questa ragione OMG decise di abbandonare il BOA
specificando un nuovo tipo di adapter, il Portable Object Adapter o POA.
Nel
caso in cui l’ORB scelto supporti il POA sarà sicuramente opportuno
utilizzarlo. Esistono tuttavia molti ORB che attualmente non forniscono
un’implementazione del POA. Per questa ragione sarà esaminato anche
il BOA nonostante il suo utilizzo sia formalmente deprecato.
Per
gli esempi di questa sezione non sarà possibile utilizzare l’implementazione
SUN che non fornisce BOA e POA.
Basic Object Adapter
(BOA)
Le
specifiche CORBA elencano come compiti primari del BOA la creazione/distruzione
degli object reference ed il reperimento delle informazioni a questi correlate.
Nelle varie implementazioni il BOA, per compiere le sue attività,
può accedere al componente proprietario Implementation Repository.
Come
si è detto in precedenza, BOA va pensato come un’entità logica
ed in effetti alcuni dei suoi compiti sono svolti in cooperazione con altri
componenti (ad esempio la creazione e la distruzione di object reference
sono a carico dello skeleton). Questo ha un impatto sulla sua implementazione
che solitamente suddivide i suoi compiti tra il processo ORB, il codice
generato dal compilatore IDL e l’effettivo BOA.
Comunque
il BOA fornisce l’interfaccia con i metodi necessari a registrare/deregistrare
gli oggetti e ad avvertire l’ORB che l’oggetto è effettivamente
pronto a rispondere alle invocazioni.
Si
è già visto negli articoli precedenti il concetto di server:
il server è un’entità eseguibile separata che attiva l’oggetto
e gli fornisce un contesto di esecuzione. Anche il BOA per attivare un
oggetto si appoggia ad un server.
Il
server può essere attivato on demand dal BOA (utilizzando informazioni
contenute nell’Implementation Repository) oppure da qualche altra entità
(ad esempio uno shell script). In ogni caso il server attiverà l’implementazione
chiamando il metodo obj_is_ready oppure il metodo impl_is_ready definiti
nel seguente modo
//
PIDL
module
CORBA {
interface
BOA {
void impl_is_ready (in ImplementationDef impl);
void deactivate_impl (in ImplementationDef impl);
void obj_is_ready (
in Object obj, in ImplementationDef impl
);
void deactivate_obj (in Object obj);
// … altri metodi di generazione references
// ed access control
};
};
In
quasi tutti gli ORB, obj_is_ready è il metodo di registrazione del
singolo oggetto all’interno di un server, stabilisce un’associazione tra
un’istanza ed un’entità nell’Implementation Repository.
Il
metodo impl_is_ready è comunemente implementato come un loop infinito
che attende le request del client, il ciclo non cessa fino a quando non
viene invocato il deactivate_impl.
Esistono
molti modi di combinare un server process con l’attivazione di oggetti
(un server registra un oggetto, un server registra n oggetti, …). Le specifiche
CORBA individuano quattro differenti politiche di attivazione:
-
Shared
Server: il processo server inizializza più oggetti invocando per
ognuno obj_is_ready. Al termine di queste inizializzazioni il server notifica
al BOA, con impl_is_ready, la sua disponibilità e rimane attivo
fino all’invocazione di deactivate_impl. Gli oggetti possono essere singolarmente
disattivati con deactivate_obj. La disattivazione è quasi sempre
automatizzata dal distruttore dello skeleton.
-
Unshared
Server: ogni oggetto viene associato ad un processo server differente.
L’inizializzazione avviene comunque con le due chiamate obj_is_ready ed
impl_is_ready.
-
Server-per-method:
un nuovo processo viene creato ad ogni invocazione. Il processo termina
al terminare dell’invocazione e, poiché ogni invocazione implica
un nuovo processo, non è necessario inviare una notifica al BOA
(alcuni ORB richiedono comunque l’invocazione di impl_is_ready).
-
Persistent
Server: tipicamente è un processo avviato mediante qualche meccanismo
esterno al BOA (shell script o avvio utente) e va registrato mediante impl_is_ready.
Dopo la notifica al BOA si comporta esattamente come uno shared server.
A
differenza di quanto indicato nelle specifiche, la maggior parte delle
implementazioni fornisce un sottoinsieme delle activation policy. In generale
l’utilizzo dei metodi legati al BOA è differente tra i vari ORB
e genera quasi sempre problemi di portabilità per quanto concerne
l’attivazione delle implementazioni.
BOA in pratica
Riscriviamo
ora l’applicazione ShoppingCart utilizzando l’implementazione BOA fornita
da Visibroker. Per quanto detto sugli adapter si dovrà intervenire
sulle classi coinvolte nell’attivazione e nell’invocazione degli oggetti
CORBA, andranno quindi modificate le seguenti classi: ShoppingCartServer,
ShoppingCartFactoryImpl e ShoppingCartClient.
Utilizziamo
il modello di server persistent, una classe Java con metodo main lanciata
da linea di comando o da script. La registrazione dell’oggetto Factory
sarà effettuata via BOA.
Il
BOA è un cosiddetto pseudo-object ed è possibile ottenere
un riferimento valido ad esso mediante l’invocazione di un metodo dell’ORB:
BOA_init. Nell’implementazione Visibroker esistono due differenti metodi
BOA_init che permettono di ricevere un BOA inizializzato con differenti
politiche di gestione dei thread (thread pooling o per session) e di trattamento
delle comunicazioni (utilizzo di Secure Socket Layer o no).
Invocando
il metodo BOA_init senza parametri si otterrà un BOA con le politiche
di default (thread pooling senza SSL). Il codice completo della classe
server è
package
server;
import
shopping.*;
public
class ShoppingCartServer {
public static void main(String[] args) {
//
Inizializza l'ORB.
org.omg.CORBA.ORB
orb =
org.omg.CORBA.ORB.init(args,null);
//
Crea l'oggetto Factory
ShoppingCartFactoryImpl
factory =
new ShoppingCartFactoryImpl("ShoppingCartFactory");
// Inizializza il BOA
// N.B. Utilizzo classi proprietarie
com.inprise.vbroker.CORBA.BOA
boa =
((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
//
Esporta l'oggetto factory
boa.obj_is_ready(factory);
System.out.println(factory + " is ready.");
// Attende le requests
boa.impl_is_ready();
}
}
Si
noti che istanziando l’oggetto Factory si è fornita al costruttore
la stringa "ShoppingCartFactory". In Visibroker, specificando un object
name quando si istanzia l’oggetto, si ottiene un riferimento di tipo persistent.
Il costruttore dell’oggetto dovrà comunque notificare il nome alla
superclasse
public
ShoppingCartFactoryImpl(String name) {
super(name);
}
L’associazione
tra un reference persistent ed il nome viene registrata in un tool proprietario
denominato OSAgent o ORB Smart Agent.
Pur
essendo uno strumento proprietario OSAgent è molto utilizzato nella
pratica. Fornisce una versione semplificata di naming service (con Visibroker
viene fornita anche un’implementazione standard del servizio) ed implementa
alcuni meccanismi proprietari di fault-tolerance e load-balancing. Per
una trattazione completa dell’OSAgent si faccia riferimento alla documentazione
Visibroker (www.inprise.com).
Un
riferimento persistent rimane vivo nell’OSAgent anche al termine del processo
server (può essere verificato utilizzando il tool osfind).
Un riferimento transient è invece rigidamente associato al ciclo
di vita del server e non può avvalersi dei meccanismi di load-balancing
e fault-tolerance forniti dall’OSAgent.
Nell’esempio
l’unico oggetto con riferimento persistent è la Factory. I vari
carrelli hanno riferimenti transient, sono registrati solo dalla chiamata
obj_is_ready della Factory e sono quindi accessibili solo tramite questa.
Anche
utilizzando il BOA, l’oggetto CORBA deve specializzare la classe <Interfaccia>ImlBase.
Ecco il codice completo della Factory
package
server;
import
shopping.*;
import
java.util.*;
public
class ShoppingCartFactoryImpl extends
_ShoppingCartFactoryImplBase {
private Dictionary allCarts = new Hashtable();
// N.B. Registro nell’OSAgent
public
ShoppingCartFactoryImpl(String name) {
super(name);
}
public
synchronized ShoppingCart
getShoppingCart(String userID) {
// Cerca il carrello assegnato allo userID...
shopping.ShoppingCart cart =
(shopping.ShoppingCart) allCarts.get(userID);
// ...se non lo trova...
if(cart == null) {
// Crea un nuovo carrello...
cart = new ShoppingCartImpl();
// Rende l'oggetto disponibile sull'ORB
//
N.B. _boa() è fornito dalla classe
//
com.inprise.vbroker.CORBA.Object
_boa().obj_is_ready(cart);
System.out.println("Created " + userID
+ "'s cart: " + cart);
// Salva il carrello nel dictionary
// associandolo allo userID
allCarts.put(userID, cart);
}
// Restituisce il carrello
return cart;
}
}
Il
client può ottenere un riferimento alla Factory invocando il metodo
bind fornito dall’Helper che si preoccupa anche di eseguire l’opportuno
narrow.
Ecco
il codice completo della classe client
package
client;
import
shopping.*;
import
org.omg.CORBA.*;
public
class ShoppingCartClient {
public
static void main(String args[]) {
if (args.length != 3) {
System.err.println("Uso corretto:
java ShoppingCartClient userId Autore Titolo");
return;
}
// Crea ed inizializza l'ORB
ORB orb = ORB.init(args, null);
// Localizza l'oggetto Factory
ShoppingCartFactory factory =
ShoppingCartFactoryHelper.bind
(orb, "ShoppingCartFactory");
// Ottengo dalla Factory un'oggetto ShoppingCart
ShoppingCart cart = factory.getShoppingCart(args[0]);
// Aggiungo un libro
cart.addBook(new Book(args[1],args[2]));
// Ottengo la lista dei libri e la stampo
Book[] list = cart.getBookList();
for(int i=0; i<list.length; i++)
System.out.println("Autore " + list[i].Author
+ " - Titolo " + list[i].Title);
}
}
Prima
di avviare il server si attivi l’OSAgent (nel caso si lavori in ambiente
distribuito è necessario attivare l’OSAgent sia sulla macchina client
che sulla macchina server). Fatto questo, per l’esecuzione dell’esempio
si compiano i soliti passi. I riferimenti persistent registrati nell’OSAgent
sono controllabili usando il tool osfind.
Conclusioni
In
questo articolo abbiamo introdotto il concetto di Object Adapter ed abbiamo
utilizzato il Basic Object Adapter. Il prossimo mese vedremo i benefici
di portabilità offerti dal Portable Object Adapter.
Bibliografia
[1]
G. Morello - "Corso CORBA V Parte: Il pattern Factory in uno scenario CORBA",
Mokabyte N. 52, Maggio 2001
[2]
G. Morello - "Corso CORBA III Parte: un semplice esempio", Mokabyte N.
50, Marzo 2001 |