Il
POA entra a far parte delle specifiche CORBA nel 1997 e va a sostituire
integralmente a livello funzionale le precedenti specifiche BOA.
La
scelta di sostituire integralmente le API BOA è legata all’impossibilità
di coniugare i complessi sviluppi presi dalle varie implementazioni proprietarie.
Poiché l’ORB è pensato per supportare un numero arbitrario
di adapter, BOA e POA possono comunque coesistere.
Lo
sviluppo del POA prende l’avvio e si basa sulle molteplici esperienze derivate
dalle varie implementazioni del BOA (spesso si dice che POA è semplicemente
una versione corretta di BOA), è quindi chiaro che molteplici sono
i concetti comuni alle due tipologie di adapter.
Anche
per POA valgono dunque le distinzioni effettuate sull’attivazione di implementazioni,
sulle tipologie di server e la distinzione tra oggetti persistent o transient
(vedi [1]).
Un po’ di teoria
Per
ogni implementazione è definibile un servant manager che, invocato
dal POA, crea, attiva o disattiva i vari servant on demand. Il meccanismo
dei servant manager aiuta il POA nella gestione degli oggetti server-side.
Quasi sempre gli ORB forniscono dei servant manager di default che implementano
politiche definite.
E’
comunque possibile definire direttamente il set di politiche applicate
al server senza utilizzare un servant manager, ma operando direttamente
sul POA. Nel caso in cui si utilizzi un servant manager, sarà suo
il compito di associare la request ad un preciso servant, attivandolo o
creandolo se necessario.
Un
servant manager implementa una delle due interfacce di callback: ServantActivator
e ServantLocator. In generale l’Activator si riferisce ad oggetti di tipo
persistent, mentre il Locator si riferisce ad oggetti di tipo transient.
Indipendentemente
da quale interfaccia si utilizzi le operazioni da definire sono due, una
per reperire e restituire il servant, l’altra per disattivarlo. Nel caso
di ServantActivator le due operazioni di cui sopra sono incarnate ed etherealize,
mentre nel caso di ServantLocator sono preinvoke e postinvoke.
Il
POA mantiene una mappa (Active Object Map) dei servant attivi in ogni istante.
All’interno della mappa i servant sono associati ad uno o più Object
Id. Un riferimento ad un oggetto sul lato client incapsula l’Object Id
ed il riferimento al POA ed è utilizzato sul lato server da ORB,
POA e servant manager per recapitare la request ad un preciso servant.
Figura
1 — Funzionamento di POA
Non
esiste una forma standard dell’Object Id, può essere generato dal
POA oppure essere assegnato dall’implementazione. In ogni caso l’Object
Id deve essere unico nel namespace e quindi nel POA su cui è mantenuto.
La
struttura dei POA è ad albero a partire da un RootPOA. Il RootPOA
è sempre disponibile e possiede politiche di default. A partire
da questo è possibile generare una gerarchia di nodi POA child con
politiche differenti. Sul RootPOA sono mantenuti solo riferimenti transient
ed è per questo che nella pratica gli oggetti si installano quasi
sempre su child POA creati opportunamente.
Un
Child POA può essere creato invocando il metodo create_POA sul suo
nodo padre. Per definire le politiche del POA creato bisogna invocare il
metodo di creazione fornendogli un oggetto di tipo Policy opportunamente
inizializzato. Non è possibile modificare successivamente le politiche
di un nodo POA.
Ecco
la definizione dei metodi che gestiscono il ciclo di vita di un POA
//
IDL
module
PortableServer {
//…
interface
POA {
//…
// POA creation and destruction
POA create_POA(
in string adapter_name,
in POAManager a_POAManager,
in CORBA::PolicyList policies)
raises (AdapterAlreadyExists, InvalidPolicy);
POA find_POA(
in string adapter_name,
in boolean activate_it)
raises (AdapterNonExistent);
void destroy(
in boolean etherealize_objects,
in boolean wait_for_completion);
};
};
L’oggetto
Policy va a coprire vari aspetti del run-time environment di un servant
associato ad un preciso POA. Le specifiche CORBA individuano sette differenti
aspetti (in corsivo sono indicati i default):
-
Thread:
specifica le modalità di trattamento dei thread; singolo thread
(SINGLE_THREAD_MODEL) o multithread (ORB_CTRL_MODEL).
-
Lifespan:
specifica il modello di persistenza (PERSISTENT o TRANSIENT).
-
Object
Id Uniqueness: specifica se l’Id di un servant deve essere unico (UNIQUE_ID)
o può essere multiplo (MULTIPLE_ID).
-
Id Assignment:
specifica se l’Id deve essere assegnato dall’applicazione (USER_ID) o dal
POA (SYSTEM_ID).
-
Servant
Retention: specifica se il POA mantiene i servant nell’Active Object Map
(RETAIN) o si affida ad un servant manager (NON_RETAIN).
-
Activation:
specifica se il POA supporta l’attivazione implicita dei servant (IMPLICIT_ACTIVATION
o NO_ IMPLICIT_ACTIVATION).
-
Request
Processing: specifica come vengono processate le request; usando l’Active
Object Map (USE_ACTIVE_OBJECT_MAP_ONLY, da usare con RETAIN) o un servant
manager (USE_DEFAULT_SERVANT o USE_SERVANT_MANAGER, da usare con NON_RETAIN).
Per
ognuna di queste categorie il POA offre una Factory del tipo create_XXX_policy
(create_thread_policy, create_lifespan_policy, …), la costruzione di un
oggetto di tipo Policy avviene utilizzando la Factory opportuna.
La
scelta della combinazione da adottare è legata alla tipologia di
applicazione che si sta realizzando. Ad esempio, una combinazione di RETAIN
ed USE_ACTIVE_OBJECT_MAP_ONLY (il default) può essere accettabile
per applicazioni che gestiscono un numero finito di oggetti attivati all’avvio
(come un Application Server che fornisca servizi continuativamente), ma
è una soluzione troppo limitata per un’applicazione che gestisca
un gran numero di oggetti. Per applicazioni di questo tipo sono più
opportune soluzioni RETAIN che utilizzino servant manager. USE_SERVANT_MANAGER
è più indicato per un gran numero di oggetti persistent,
mentre USE_DEFAULT_SERVANT per un gran numero di oggetti transient.
Esistono
tre differenti tipologie di attivazione di un oggetto mediante POA: attivazione
esplicita, attivazione on demand utilizzando un servant manager e attivazione
implicita.
L’attivazione
esplicita avviene con l’invocazione di alcuni metodi forniti dal POA
//
IDL
module
PortableServer {
//…
interface
POA {
//…
// object activation and deactivation
ObjectId activate_object(
in Servant p_servant)
raises (ServantAlreadyActive, WrongPolicy);
void activate_object_with_id(
in ObjectId id,
in Servant p_servant)
raises ( ServantAlreadyActive,
ObjectAlreadyActive, WrongPolicy);
void deactivate_object(
in ObjectId oid)
raises (ObjectNotActive, WrongPolicy);
};
};
Nel
caso in cui si usi la registrazione esplicita, il server crea tutti i servant
e li registra con uno dei due metodi activate_object.
Nel
caso di attivazione on demand, il server si limita ad informare il POA
su quale servant manager utilizzare per l’attivazione degli oggetti. I
metodi per la gestione dei servant manager sono
//
IDL
module
PortableServer {
//…
interface
POA {
//…
// Servant Manager registration
ServantManager get_servant_manager()
raises (WrongPolicy);
void set_servant_manager(in ServantManager imgr)
raises (WrongPolicy);
};
};
Si
ha un’attivazione implicita effettuando su di un servant inattivo, senza
Object Id, operazioni che implichino la presenza di un Object Id nell’Active
Map. E’ possibile solo per la combinazione IMPLICIT_ACTIVATION, SYSTEM_ID,
RETAIN. Le operazioni che generano un’attivazione implicita sono
//
IDL
module
PortableServer {
//…
interface
POA {
//…
// Identity mapping operations
ObjectId servant_to_id(
in Servant p_servant)
raises (ServantNotActive, WrongPolicy);
Object servant_to_reference(
in Servant p_servant)
raises (ServantNotActive, WrongPolicy);
};
};
Anche
il POA può essere attivato e disattivato, queste operazioni possono
essere effettuate utilizzando il POAManager che è un oggetto associato
ad uno o più POA. Il POAManager permette anche di bloccare e scartare
le request in arrivo
//
IDL
module
PortableServer {
//…
//
POAManager interface
interface
POAManager {
exception AdapterInactive{};
enum State {HOLDING, ACTIVE, DISCARDING, INACTIVE};
void activate()raises(AdapterInactive);
void hold_requests(
in boolean wait_for_completion)
raises(AdapterInactive);
void discard_requests(
in boolean wait_for_completion)
raises(AdapterInactive);
void deactivate(
in boolean etherealize_objects,
in boolean wait_for_completion)
raises(AdapterInactive);
State get_state();
};
};
POA in pratica
L’utilizzo
di POA permette di configurare l’environment ed il comportamento del servant
in maniera indipendente dall’ORB. Sarà presentato il solito esempio
dello ShoppingCart avendo come punto di riferimento Visibroker. L’esempio
sarà comunque utilizzabile con qualunque altro ORB dotato di POA.
Il
modo più semplice di utilizzare POA è quello di adottare
l’attivazione esplicita senza definire servant manager. Sarà quindi
realizzato un server che attivi il servant ShoppingCartFactory in modo
persistent. La Factory sarà un servizio sempre disponibile, mentre
i singoli carrelli, come già nella versione BOA, saranno oggetti
transient. Il primo passo da compiere è quello di ottenere un riferimento
al RootPOA
POA
rootPOA = POAHelper.narrow
(orb.resolve_initial_references("RootPOA"));
Fatto
questo è possibile creare un POA con le necessarie politiche. Non
è possibile utilizzare direttamente il RootPOA in quanto non supporta
la modalità persistent.
Un
POA di default utilizza modalità multithread, riferimenti transient,
Active Object Map. Nell’esempio è quindi sufficiente modificare
la proprietà Lifespan da TRANSIENT a PERSISTENT e, con queste politiche,
creare il nuovo nodo POA assegnandogli un nome identificativo
org.omg.CORBA.Policy[]
policies = {
rootPOA.create_lifespan_policy
(LifespanPolicyValue.PERSISTENT)
};
POA
myPOA = rootPOA.create_POA
("shopping_cart_poa", rootPOA.the_POAManager(), policies);
A questo
punto è possibile istanziare il servant ed attivarlo sul POA. Poiché
l’Id deve essere conosciuto dal client per l’invocazione, in uno scenario
semplice è conveniente definirlo esplicitamente nel server
byte[]
factoryId = "ShoppingCartFactory".getBytes();
myPOA.activate_object_with_id(factoryId,
factory);
Il
servant non sarà in condizione di rispondere fino all’attivazione
del POA che lo ospita. L’attivazione è effettuata utilizzando il
POAManager
rootPOA.the_POAManager().activate();
Il
ciclo di attesa infinita del server può essere implementato con
un metodo dell’ORB (non più impl_is_ready)
orb.run();
Ecco
il codice completo della classe server
package
server;
import
shopping.*;
import
org.omg.PortableServer.*;
public
class ShoppingCartServer {
public static void main(String[] args) {
try {
// Inizializza l'ORB.
org.omg.CORBA.ORB orb =
org.omg.CORBA.ORB.init(args,null);
// Prende il reference al the root POA
POA rootPOA =
POAHelper.narrow
(orb.resolve_initial_references("RootPOA"));
// Crea le policies per il persistent POA
org.omg.CORBA.Policy[] policies = {
rootPOA.create_lifespan_policy
(LifespanPolicyValue.PERSISTENT)
};
// Crea myPOA con le date policies
POA myPOA = rootPOA.create_POA
("shopping_cart_poa", rootPOA.the_POAManager(),
policies);
// Crea l'oggetto Factory
ShoppingCartFactoryImpl factory =
new ShoppingCartFactoryImpl();
// Stabilsco l'ID del servant
byte[] factoryId = "ShoppingCartFactory".getBytes();
// Attiva il servant su myPOA con il dato Id
myPOA.activate_object_with_id(factoryId, factory);
// Attiva il POA
rootPOA.the_POAManager().activate();
System.out.println(myPOA.servant_to_reference(factory)
+ " is ready.");
// Si mette in attesa delle requests
orb.run();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Utilizzando
POA un servant non deve più specializzare _<NomeInterfaccia>ImplBase,
bensì <NomeInterfaccia>POA. La ShoppingCartFactoryImpl sarà
quindi
package
server;
import
org.omg.PortableServer.*;
import
shopping.*;
import
java.util.*;
public
class ShoppingCartFactoryImpl extends
ShoppingCartFactoryPOA {
private Dictionary allCarts = new Hashtable();
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...
ShoppingCartImpl cartServant = new ShoppingCartImpl();
try {
// Attiva l'oggetto sul default POA che
// è il root POA di questo servant
cart = shopping.ShoppingCartHelper.narrow
(_default_POA().servant_to_reference
(cartServant));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Created " + userID
+ "'s cart: " + cart);
// Salva il carrello associandolo allo userID
allCarts.put(userID, cart);
}
// Restituisce il carrello
return cart;
}
}
Si
noti che l’attivazione del singolo ShoppingCart è implicita. La
generazione dell’Id e la sua registrazione nell’Active Objec Map saranno
in questo caso a carico del POA.
L’oggetto
ShoppingCartImpl dovrà specializzare ShoppingCartPOA, il codice
rimarrà inalterato rispetto all’esempio BOA (vedi [1]).
Il
client accederà alla Factory in modo molto simile a quanto visto
nel BOA. Sul bind andrà specificato l’opportuno Id ed il nome del
POA su cui è registrato il servant
byte[]
factoryId = "ShoppingCartFactory".getBytes();
ShoppingCartFactory
factory = ShoppingCartFactoryHelper.bind
(orb, "/shopping_cart_poa", factoryId);
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);
// ID del servant
byte[] factoryId = "ShoppingCartFactory".getBytes();
// Localizza l'oggetto Factory
// Devo usare il POA name e l'Id del servant
ShoppingCartFactory factory =
ShoppingCartFactoryHelper.bind
(orb, "/shopping_cart_poa", factoryId);
// 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);
}
}
Conclusioni
In
questo articolo abbiamo visto all’opera il Portable Object Adapter. Il
prossimo mese studieremo il passaggio di parametri per valore in uno scenario
CORBA.
Bibliografia
[1]
G. Morello - "Corso CORBA VII Parte: Basic Object Adapter", Mokabyte N.
53, Giugno 2001
Allegati
Gli
esempi completi si possono scaricare qui |