MokaByte 54 - Luglio/Agosto 2001
Foto dell'autore non disponibile
di
Gianluca Morello
Corso CORBA
VII Parte: Portable Object Adapter
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 (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

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


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
mokainfo@mokabyte.it