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
|