Lo scorso mese abbiamo presentato il Basic Object Adapter. Come si è visto BOA presenta forti limiti di portabilità lato server. Per una completa portabilità server side è opportuno utilizzare il Portable Object Adapter (POA)
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 (SINGLETHREADMODEL) o multithread (ORBCTRLMODEL).
Lifespan: specifica il modello di persistenza (PERSISTENT o TRANSIENT).
Object Id Uniqueness: specifica se l’Id di un servant deve essere unico (UNIQUEID) o può essere multiplo (MULTIPLEID).
Id Assignment: specifica se l’Id deve essere assegnato dall’applicazione (USERID) o dal POA (SYSTEMID).
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 (IMPLICITACTIVATION o NO IMPLICIT_ACTIVATION).
Request Processing: specifica come vengono processate le request; usando l’Active Object Map (USEACTIVEOBJECTMAPONLY, da usare con RETAIN) o un servant manager (USEDEFAULTSERVANT o USESERVANTMANAGER, da usare con NON_RETAIN).
Per ognuna di queste categorie il POA offre una Factory del tipo createXXXpolicy (createthreadpolicy, createlifespanpolicy, …), 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 USEACTIVEOBJECTMAPONLY (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. USESERVANTMANAGER è più indicato per un gran numero di oggetti persistent, mentre USEDEFAULTSERVANT 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 activateobjectwith_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 getservantmanager()
raises (WrongPolicy);
void setservantmanager(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 IMPLICITACTIVATION, SYSTEMID, RETAIN. Le operazioni che generano un’attivazione implicita sono
// IDL
module PortableServer {
//…
interface POA {
//…
// Identity mapping operations
ObjectId servanttoid(
in Servant p_servant)
raises (ServantNotActive, WrongPolicy);
Object servanttoreference(
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 waitforcompletion)
raises(AdapterInactive);
void discard_requests(
in boolean waitforcompletion)
raises(AdapterInactive);
void deactivate(
in boolean etherealize_objects,
in boolean waitforcompletion)
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.resolveinitialreferences("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
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
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
(defaultPOA().servanttoreference
(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
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
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 info@mokabyte.it