MokaByte 66 - 7mbre 2002 
Java Naming and Directory Interface API
II parte: la gestione di oggetti remoti
di
Giovanni Puliti
Il mese scorso è stata fatta una introduzione alla API JNDI ed il suo legame con il protocollo LDAP. Questo mese entreremo nel dettaglio dei meccanismi di gestione e memorizzazione di oggetti particolari all'interno di un directory.

Memorizzare oggetti nel directory
Nel caso in cui si lavori con applicazioni Java distribuite, uno dei problemi da risolvere è consentire ai vari client di utilizzare oggetti remoti in modo condiviso. La soluzione più semplice per risolvere questo aspetto è utilizzare un directory service, memorizzando tali oggetti al suo interno, in modo che tutti possano localizzare e ricavare tali oggetti.
Esistono sostanzialmente tre modi per inserire un oggetto in un directory service: memorizzare una copia dell'oggetto stesso, un suo reference, o l'insieme degli attributi che lo descrivono. Ad esempio utilizzando la serializzazione Java, si può memorizzare sia lo stato dell'oggetto, mentre il suo reference non è altro una rappresentazione compatta dell'indirizzo di memoria utilizzabile per ricavare l'oggetto. Infine rappresentare l'oggetto tramite i suoi attributi significa utilizzare una collezione di proprietà che ne descrivono lo stato compreso il suo indirizzo e le informazioni sullo stato.
Scegliere quale forma di rappresentazione utilizzare dipende principalmente dal tipo di oggetto in esame e dal sistema sottostante, ma anche dal tipo di applicazione che si deve realizzare e dalla particolare modalità con cui si desidera interagire con gli oggetti.
I metodi utilizzati per memorizzare un oggetto sono i seguenti

Context.bind()(in the API reference documentation)
DirContext.bind()(in the API reference documentation)
Context.rebind()(in the API reference documentation)
DirContext.rebind()(in the API reference documentation)

L'oggetto passato a questi metodi verrà trasformato in una forma equivalente all'interno del directory, in funzione del tipo di oggetto e del supporto offerto dal directory per quell'oggetto. In questo secondo articolo si vedrà come memorizzare nel directory i seguenti tipi di oggetti

Java serializable objects
Referenceable objects and JNDI References
Objects with attributes (DirContext
)

Mentre si tralasceranno i dettagli relativi alla gestione degli oggetti remoti RMI (inclusi quelli che utilizzano IIOP) e gli oggetti CORBA, per i quali sicuramente è necessaria una trattazione più ampia delle tecnologie relative. Per chi fosse interessato a maggiori approfondimenti relativi a queste tecnologie e soprattutto alla memorizzazione degli oggetti nel directory, si rimanda alla letteratura ufficiale, nonché ai capitoli del libro di MokaByte che li trattano (vedi [RMI] e [CORBA]).

 

Oggetti Serializabili
Il processo di serializzazione Java permette di trasformare un oggetto in uno stream di byte in modo che il processo inverso possa essere effettuato partendo direttamente da tale stream. Questa caratteristica dovrebbe essere ormai ben nota, e quindi brevemente si ricorderà che un oggetto Java per essere serializzabile deve necessariamente implementare l'interfaccia java.io.Serializable (se è sufficiente utilizzare il meccanismo di serializzazione standard) o la sua sottointerfaccia java.io.Externalizable (nel caso in cui si voglia dar vita a meccanismo particolari di trasformazione dell'oggetto in uno strema).
Il runtime Java fornisce il supporto per il processo di serializzazione e deserializzazione standard, anche se è possibile personalizzare tali trasformazioni, variando quindi il modo con cui i dati sono estrapolati da un oggetto ed immessi nello stream.
Importante ricordare che durante la serializzazione di un oggetto esso viene trasformato in una sequenza di byte, prelevando dal suo interno i suoi dati ed il nome della classe. L'oggetto in se non verrà memorizzato nello stream, ma solamente il suo codice univoco, il serialVersionUID. Tramite tale codice al momento della deserializzazione, la JVM potrà ricavare il nome esatto della classe da istanziare, che dovrà essere fornita alla JVM tramite classpath. Dato che lo scopo di questo articolo esula dal trattare la serializzazione in modo approfondito, si rimanda a [SER] per maggiori approfondimenti.
L'operazione di bind di un oggetto serializzabile avviene tramite i metodi
DirContext.bind() nel caso di un nuovo bind, oppure Context.rebind() e DirContext.rebind() per le operazioni di riscrittura. Se ad esempio si disponesse di un oggetto serializzabile MySerial, si potrebbe scrivere

String ObjectName = "Oggetto serializzabile numero 1";
MySerial ms = new MySerial (ObjectName);

// Esegue la bind
ctx.bind("cn=MySerial", ms);

L'operazione di ricerca a questo punto può avvenire banalmente nel seguente modo

// Ricava l'oggetto dal directory
MySerial ms2 = (MySerial)ctx.lookup("cn=MySerial");

// Fai qualcosa con ms2


Durante le operazioni di bind e lookup l'oggetto deve essere rintracciabile dalla JVM, ovvero deve essere presente nel classpath.
In alternativa si può pensare di utilizzare il suo codebase direttamente al momento della bind: questa tecnica può risultare utile in quei casi in cui l'oggetto risulti non direttamente accedibile nel classpath locale oppure quando il sistema sottostante non supporta l'accesso a tale ambiente.
Per memorizzare nel repository anche il codebase dell'oggetto si può procedere direttamente al momento della bind, oppure successivamente specificando un attributo DirContext.modifyAttributes().
Anche se non obbligatorio, l'etichetta alla quale si associa il codebase è normalmente "javaCodebase" (attributo specificato in RFC 2713), il quale deve puntare all' URL del codebase della classe, o in forma di directory o di JAR file; se il codebase contiene più URL, questi dovranno essere separati da uno spazio.
Lo stesso esempio di prima potrebbe quindi essere riscritto nel seguente modo

String ObjectName = "Oggetto serializzabile numero 1";
MySerial ms = new MySerial (ObjectName);

// Esegue la bind utilizzando il codebase dell'oggetto MySerial
ctx.bind("cn= MySerial ", ms, new BasicAttributes("javaCodebase", codebase));

In questo caso il codebase dell'oggetto MySerial potrebbe puntare ad un file server accessibile tramite il protocollo HTTP.
Differentemente dal caso precedente (bind del nome dell'oggetto), dopo l'operazione di bind, il file MySerial.class non sarà più necessario e potrà essere rimosso dal file system.

 

Oggetti Referenceable e Reference
In alcuni casi memorizzare oggetti in forma serializzata può non essere la soluzione migliore: la forma serializzata potrebbe essere troppo ingombrante, potrebbe essere riduttiva o non adatta per le informazioni che realmente si vogliono memorizzare, o infine perché il sistema di directory non supporta tale meccanismo.
In questi casi si può passare a memorizzare l'oggetto nel directory utilizzando il suo reference.
Formalmente un reference, rappresentato dalla classe Reference è costituito da una lista ordinata di indirizzi di memoria ed informazioni sulla classe che rappresenta l'oggetto in questione. Ogni indirizzo è rappresentato dalla classe RefAddrs e contiene alcune informazioni su come costruire l'oggetto
In genere un reference è utilizzato per rappresentare oggetti di alto livello in modo da tralasciare i dettagli implementativi, come nel caso delle connessioni di rete, database o file system. Vedendo le cose da un altro punto di vista si potrebbe dire che tali oggetti non sono altro che interfacce o puntatori ad aree di memoria; un puntatore ad una connessione JDBC o un socket, tutte cose che in genere sono gestite direttamente dal sistema sottostante o direttamente dal sistema operativo; in questi casi Java partecipa come sistema di astrazione per ciò che sta sotto.
Fra i vari indirizzi di memoria che sono contenuti in un reference ci possono essere anche puntatori alla classe che rappresenta tale oggetto o ad un particolare factory necessario per ricrearlo.
Un oggetto referenceable implementa l'interfaccia Referenceable la tramite il metodo getReference() restituisce il reference all'oggetto.
Si potrebbe quindi pensare di scrivere

public class MyRefereceableClass implements Referenceable {

  String Name;

  public MyRefereceableClass (String n) {
    Name = n;
  }

  public Reference getReference() throws NamingException {
    return new Reference(MyRefereceableClass.class.getName(),
                         new StringRefAddr("name", Name),
                         MyFactory.class.getName(), somewhere);
  }

  public String toString() {
    return name;
  }
}


In questo caso il reference di una istanza di MyRefereceableClass consiste di un indirizzo (rappresentati dalla classe StringRefAddr) che contiene il tipo dell'oggetto, ovvero in questo caso la variabile Name. Da notare l'utilizzo del Factory necessario in seguito per ricreare l'oggetto (vedi [TUT] per maggiori informazioni sui factory).
Più semplicemente in questo caso, se creassimo

MyRefereceableClass mrc = new MyRefereceableClass("myObject");

Allora l'address type sarebbe "name", ed il suo valore "myObject". Infine a questo punto si potrebbe scrivere

MyRefereceableClass mrc = new MyRefereceableClass("myObject");
// esegue la bind
ctx.bind("cn=mypersonalobject", mrc);

l'esecuzione dei metodi bind() e rebind(), o meglio la loro specifica implementazione all'interno del service provider, provocano l'estrazione del reference dell'oggetto (ricavato dal metodo Referenceable.getReference()) e la successiva memorizzazione nel directory.
Al momento della operazione simmetrica di lookup il reference verrà nuovamente convertito nell'oggetto corrispondente tramite l'apposito factory specificato.

// ricava l'oggetto indietro
MyRefereceableClass anothermrc = (MyRefereceableClass ) ctx.lookup("cn=mypersonalobject");
...

Rispetto all'utilizzo di oggetti serializzabili si può notare come anche in questo caso dal punto di vista del client le operazioni di bind e lookup sono del tutto analoghe.
Infine da tenere presente che ovviamente si possono memorizzare oggetti referenceable nel directory solo se il service provider utilizzato supporta entrambi gli oggetti References e Referenceable.

 

Oggetti con attributi
Se l'oggetto che si vuole memorizzare nel directory non è ne serializzabile e nemmeno referenceable si può procedere ad una forma differente di trasformazione, basata sui suoi attributi, a patto che il sistema sottostante permetta di effettuare bind di oggetti DirContext.
In questo caso la procedura base è fornire all'oggetto gli strumenti per estrapolare da se stesso tutte le informazioni descrittive dell'oggetto e di fornirle al servizio di directory tramite un oggetto apposito; questa funzionalità è possibile se l'oggetto implementa l'interfaccia DirContext e se ridefinisce il metodo getAttributes().
Ad esempio un oggetto di questo genere potrebbe essere

public class MyClassWithAtributes implements DirContext {
  String Name;

  public MyClassWithAtributes (String n) {
    Name = n;
    myAttrs = new BasicAttributes(true);
    Attribute oc = new BasicAttribute("objectclass");
    oc.add("extensibleObject");
    oc.add("top");

    myAttrs.put(oc);
    myAttrs.put("myobjectName", n);
  }

  public Attributes getAttributes(String name) throws NamingException {  
    return (Attributes)myAttrs.clone();
  }

  public String toString() {
    return type;
  }
}

Il server al momento della memorizzazione all'interno del directory, estrapolerà tutte le informazioni che verranno fornite dal metodo getAttributes(). In questo modo di fatto è il programmatore che definisce l'oggetto, che decide cosa e come debba essere memorizzato nel directory. Tale soluzione risulta essere per questo motivo molto potente e consente di ottimizzare lo spazio utilizzato da ogni oggetto e di conseguenza anche le performance complessive.
Anche in questo caso il client dovrà al solito eseguire semplicemente una bind ed una lookup successivamente.

 

Object Factory
Fino a questo momento si è potuto vedere in più di una occasione che per l'operazione di lookup di un oggetto memorizzato nel directory, implichi internamente la sua ricreazione e la successiva restituzione al client che ha invocato tale metodo. Tale ricreazione non può avvenire in modo automatico e trasparente ma si rende necessario un apposito factory dell'oggetto in questione.
Affrontare in modo esauriente e completo tutti gli aspetti legati ai factory richiederebbe molto più spazio di quello qui a disposizione, per cui ci si limiterà a dare alcuni esempi limitatamente a quanto visto fino a questo punto.

Un factory, come lascia intuire il nome, è un produttore di oggetti che accetta in input una serie di informazioni necessarie per poter ricostruire correttamente l'oggetto.
Tutte le informazioni necessarie ed i criteri necessari per validare tali dati sono relative al factory in questione e quindi al tipo di oggetto utilizzato.
Ad esempio nel caso degli oggetti referenceable l'operazione di bind da vita ad una serie di operazioni di preparazione necessarie per poter effettuare correttamente la lookup.
In particolare in questa fase il service provider utilizzato invoca il metodo DirectoryManager.getObjectInstance() per ricavare il reference individuato dal nome logico passato alla invocazione della lookup.
A questo punto a partire dal reference si procede a ricavare il factory specifico per quell'oggetto, e successivamente ad invocarne il metodo getObjectInstance().
Nel caso visto in precedenza, MyRefereceableClass, il factory potrebbe essere qualcosa di molto semplice. Il metodo getObjectInstance prima verifica che i dati passati sono corretti ed utilizzabili per ricreare l'oggetto, ovvero che i dati contenuti nel Reference contengono un address mrc (dove mrc è l'istanza di MyRefereceableClass precedentemente inserita nel directory) e che l'oggetto contenuto sia di tipo MyRefereceableClass. Se tale verifica fallisce, allora il factory restituisce un null, in modo che nessun altra operazione di factory possa essere tentato; infatti la ricreazione di un oggetto segue in modo simile il meccanismo a cascata di ricreazione di una connessione JDBC a partire dal nome del driver (il primo driver che corrisponde al tipo di connessione viene utilizzato).
Se invece i controlli avvengono con successo, l'address contenuto (nell'esempio precedente era myObject) viene utilizzato per ricreare l'oggetto.
Il metodo getObjectInstance di MyRefereceableClassFactory potrebbe essere così definito

  public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env)
                                 
throws Exception {
    if (obj instanceof Reference) {
      Reference ref = (Reference)obj;
      if (ref.getClassName().equals(Fruit.class.getClassName())) {
        RefAddr addr = ref.get("mrc");
        if (addr != null) {
          return new MyRefereceableClass ((String)addr.getContent());
        }
      }
    }
    return null;
  }


Come si può notare il codice è molto semplice, anche se concettualmente molto importante. Per chi fosse interessato ad ulteriori informazioni o altri esempi di factory differenti potrà fare riferimento al tutorial su JNDI fornito da Sun [TUT]

 

Conclusione
Come si è potuto vedere in questi due articoli pubblicati su MokaByte, la API JNDI offre molti e potenti meccanismi sia per la gestioni di strutture gerarchiche di nomi che di oggetti.
Si sarà notato come molte delle nozioni affrontate sono spesso legate ad altre API e tecnologie di J2EE, a dimostrazione dell'importanza di questa API. Una volta di più mi preme ricordare che JNDI, benché spesso trascurata dai vari testi su J2EE, rappresenta una delle colonne portanti non solo di tutto J2EE ma anche dell'intera piattaforma Java2.
Una sua corretta conoscenza è quindi un fattore indispensabile per ogni programmatore che desideri maneggiare con sicurezza oggetti come RMI, CORBA, EJB, ma anche JSP o JDBC.
Bibliografia
[MBOOK] - Manuale pratico di Java - www.mokabyte.it/manualejava o per la versione elettronica www.mokabyte.it/mokabook
[RMI], [CORBA], [SER] - capitoli su RMI CORBA e Serializzazione di [MBOOK]
[TUT] - The JNDI Tutorial - http://java.sun.com/products/jndi/tutorial

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