Capitolo 2
Il Package java.rmi
In questo capitolo viene data una descrizione sufficientemente dettagliata delle varie classi e interfacce che compongono il package java.rmi [5], allo scopo di fornire una conoscenza più a basso livello del meccanismo di invocazione di metodo remoto del linguaggio Java.
Il package java.rmi contiene tutte quelle classi e interfacce necessarie a creare e gestire oggetti remoti (anche permanenti), a fornire il servizio di registrazione (registry) di tali oggetti e l’eliminazione automatica di quelli non referenziati (il cosiddetto servizio di garbage collection dinamico). Tale package è strutturato in modo da contenere altri quattro package, ciascuno con classi e interfacce specifiche per i servizi enunciati, che saranno descritte nei paragrafi seguenti.
2.1 Principali classi e interfacce del
package java.rmi
In tale package sono presenti l’interfaccia Remote, le classi
MarshalledObject, Naming e RMISecurityManager oltre a numerose eccezioni
che vengono sollevate durante la chiamata remota di un metodo.
2.1.1 L’interfaccia Remote
Serve ad identificare tutte quelle interfacce i cui metodi possono essere invocati da una JVM remota. Ogni oggetto remoto deve, direttamente o indirettamente, implementare un’interfaccia remota, cioè un’interfaccia che estenda java.rmi.Remote, e i metodi disponibili in remoto saranno soltanto quelli specificati in essa.
RMI fornisce alcune classi base a partire dalle quali è possibile implementare oggetti remoti (e che ovviamente possono essere estese da altre classi), che verranno descritte successivamente all’interno di tale capitolo:
java.rmi.server.UnicastRemoteObject java.rmi.activation.Activatable
2.1.2 La classe Naming
Fornisce metodi per memorizzare e ricercare dei riferimenti ad oggetti remoti dal registry. Ogni metodo della classe Naming ha come argomento un nome, costituito da una stringa in formato URL ( //host:port/name ), dove host rappresenta l’host locale o remoto dove è situato il registry, port è il numero di porta su cui il registry accetta le chiamate e name è una semplice stringa che non viene interpretata dal registry e che verrà associata al relativo oggetto remoto. Sia
host che port sono opzionali e qualora vengano omessi, l’host di default è quello
locale e la porta di default è la numero 1099.
Dopo che un oggetto remoto è stato registrato (bound) col registry sull’host locale, i client che vogliono invocare i suoi metodi, possono ricercare l’oggetto mediante il nome associato, ottenendo il riferimento a tale oggetto remoto. Un registry può essere condiviso da tutti i server in esecuzione sullo stesso host oppure ogni singolo processo server può creare e usare un proprio registry qualora lo desideri.
public final class Naming extend Object { public static Remote lookup(String name)
throws NotBoundException, MalformedURLException, RemoteException { //...
}
public static void bind(String name, Remote obj) throws AlreadyBoundException, MalformedURLException, RemoteException { //...
}
public static void unbind(String name)
throws NotBoundException, MalformedURLException, RemoteException { //...
}
public static void rebind(String name, Remote obj) throws RemoteException,
MalformedURLException { //...
}
public static String[] list(String name)
throws RemoteException,
MalformedURLException { //...
} }
Il metodo lookup restituisce un riferimento (uno stub) per l’oggetto remoto a cui é associato il nome name. Il metodo bind serve invece ad associare lo specifico nome name all’oggetto remoto obj. Il metodo unbind elimina il collegamento col nome associato allo specifico oggetto remoto, mentre rebind é simile al metodo bind e associa un legame tra il nome name e l’oggetto remoto obj, rimpiazzando eventuali collegamenti precedenti. Infine, il metodo list restituisce un array contenente i nomi associati ai vari oggetti remoti presenti nel registry al momento della sua invocazione. I nomi sono stringhe in formato URL.
2.1.3 La classe RMISecurityManager
Tale classe fornisce un manager per la gestione della sicurezza per quelle applicazioni che importano del codice (downloaded code). Il class loader (caricatore di classi) di RMI non importerà alcuna classe da locazioni remote, se non è stato settato nessun manager della sicurezza (security manager). La RMISecurityManager non viene applicata agli applets di Java, in quanto questi vengono eseguiti sotto la protezione del security manager del proprio browser.
Per usare la RMISecurityManager nella propria applicazione, occorre scrivere il seguente statement (prima che l’RMI possa prelevare del codice da qualche host remoto):
System.setSecurityManager( new RMISecurityManager());
Occorre notare che, la classe appena descritta estende la classe java.lang.SecurityManager[6] e, di conseguenza, eredita da questa tutti i
metodi per la gestione della sicurezza (che non verranno trattati qui, ma ai quali si rimanda per maggiori dettagli).
public class RMISecurityManager extends java.lang.SecurityManager { public RMISecurityManager () { //... } } 2.1.4 La classe MarshalledObject
Tale classe contiene uno stream di byte con la rappresentazione serializzata dell’oggetto passato come parametro nel suo costruttore.
L’oggetto viene serializzato e deserializzato con la stessa semantica usata per il marshaling e l’unmarshaling dei parametri e dei valori di ritorno delle chiamate RMI. In particolare, quando viene creata la forma serializzata, le classi vengono contrassegnate da un codebase URL che indica da dove possono essere caricate (se disponibili), e ogni oggetto remoto in MarshalledObject è rappresentato da un’istanza serializzata del suo stub. Quando viene ricercata una copia dell’oggetto remoto (mediante il metodo get), se la classe non è disponibile localmente, essa verrà caricata dalla locazione appropriata (quella specificata dall’URL accoppiato col descrittore di classe al momento della serializzazione).
public final class MarshalledObject implements Serializable { private byte[] objBytes = null;
private byte[] locBytes = null; private int hash;
public MarshalledObject(Object obj)
throws java.io.IOException { //...
}
public Object get() throws java.io.IOException,
java.lang.ClassNotFoundException { //...
public int hashCode() {
//... }
public boolean equals(Object obj) {
//... }
private static class MarshalledObjectOutputStream
extends MarshalOutputStream { private ObjectOutputStream locOut;
private boolean hadAnnotations;
MarshalledObjectOutputStream(OutputStream objOut, OutputStream locOut) throws IOException { //... } boolean hadAnnotations() { //... }
protected void writeLocation(String loc)
throws IOException { //...
}
public void flush() throws IOException
{
//... } }
private static class MarshalledObjectInputStream
extends MarshalInputStream { private ObjectInputStream locIn;
MarshalledObjectInputStream(InputStream objIn,
InputStream locIn)
throws IOException { //...
protected Object readLocation() throws IOException, ClassNotFoundException { //... } } }
Il costruttore crea un oggetto MarshalledObject che contiene la rappresentazione serializzata dello stato corrente dell’oggetto obj passato come parametro. Ovviamente obj deve essere serializzabile.
Il metodo get restituisce una nuova copia deserializzando ciò che è contenuto nell’oggetto MarshalledObject. La rappresentazione interna viene deserializzata con la stessa semantica usata per l’unmarshaling dei parametri nelle chiamate RMI.
Il metodo equals confronta l’oggetto MarshalledObject con un altro oggetto dato dal parametro obj. Restituisce il valore booleano true se e solo se l’argomento obj riferisce un altro oggetto MarshalledObject che contiene la stessa rappresentazione serializzata dell’oggetto in questione. Il confronto viene fatto ignorando il codebase URL associato.
Come si può osservare dal codice scritto sopra, la classe MarshalledObject contiene a sua volta ulteriori due classi: MarshalledObjectOutputStream e MarshalledObjectInputStream. La prima serve a separare il codebase URL associato alla rappresentazione serializzata dell’oggetto su cui si è eseguito il marshal, allo scopo di poter fare correttamente il confronto di uguaglianza. La seconda è invece la duale e serve a riottenere l’oggetto MarshalledObject completo.
2.1.5 La classe RemoteException
Essa é la superclasse di tutte le eccezioni relative alla comunicazione, che vengono sollevate durante l’esecuzione di una chiamata di metodo remoto. Ogni metodo di un’interfaccia remota (cioè ogni interfaccia che estenda java.rmi.Remote) deve necessariamente contenere una clausola throws RemoteException.
public class RemoteException extends IOException { public Throwable detail
public RemoteException() { //... } public RemoteException(String s) { //... }
public RemoteException(String s, Throwable ex) {
//... }
public String getMessage() {
//... }
public Throwable getCause() {
//... }
}
I costruttori creano rispettivamente, un oggetto RemoteException con e senza un messaggio associato, e un oggetto al quale oltre al messaggio viene fornita l’eccezione associata ad essa (la cosiddetta nested exception). Il metodo
exception qualora fosse presente. Il metodo getCause restituisce l’eccezione che
ha causato la remote exception (può anche restituire null se non ci sono eccezioni più esterne che hanno generato la remote exception).
2.1.6 Altre classi Exception
Non verranno descritte nel dettaglio tutte le altre classi Exception, in quanto sono relativamente semplici e già dal loro nome si intuisce il tipo di eccezione descritta. Per completezza comunque, nella seguente figura viene mostrato un class diagram (secondo la notazione UML) che elenca e mostra le relazioni di parentela tra le varie classi Exception.
java.io.Serializable java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.SecurityException RMISecurityException AlreadyBoundException NotBoundException java.io.IOException RemoteException AccessException ConnectException ConnectIOException MarshalException ServerError ServerException UnexpectedException UnknownHostException StubNotFoundException NoSuchObjectException ServerRuntimeException UnmarshalException
2.2 Il package java.rmi.server
Il package java.rmi.server implementa varie interfacce e classi che supportano gli aspetti client e server dell’RMI. Di seguito vengono presentate le principali, tralasciando quelle contrassegnate dagli sviluppatori Java come “deprecated”. Tali classi (LoaderHandler, Operation, RemoteCall e Skeleton) sono presenti perché usate nella versione 1.1 del JDK, ma per le nuove versioni (già a partire dal JDK 1.2) il loro uso è sconsigliato.
2.2.1 La classe RemoteObject
Implementa il comportamento della classe java.lang.Object per gli oggetti remoti. Essa fornisce la semantica di oggetto remoto, ridefinendo i metodi
hashCode, equals e toString.
public abstract class RemoteObject extends Object
implements Remote, Serializable { protected transient RemoteRef ref
protected RemoteObject() {
//... }
protected RemoteObject(RemoteRef newref) {
//... }
public RemoteRef getRef() {
//... }
public static Remote toStub(Remote obj)
throws NoSuchObjectException { //...
public int hashCode() {
//... }
public boolean equals(Object obj) {
//... }
public String toString() {
//... }
}
I costruttori creano un oggetto remoto, con la possibilità di inizializzarlo con un riferimento remoto specificato dal parametro newref.
Il metodo getRef restituisce il riferimento remoto dell’oggetto, mentre il metodo toStub restituisce lo stub per l’oggetto remoto obj; tale operazione è valida solo dopo che l’oggetto è stato esportato.
Il metodo hashCode restituisce un hashcode per l’oggetto remoto: due stub che riferiscono lo stesso oggetto remoto avranno lo stesso hashcode (per poter usare gli oggetti remoti come chiavi nelle tabelle hash).
Il metodo equals confronta se due oggetti remoti sono uguali; in particolare restituisce un booleano che indica se l’oggetto in questione è equivalente all’oggetto specificato dal parametro obj.
Infine, il metodo toString restituisce una stringa che rappresenta il valore dell’oggetto remoto.
2.2.2 La classe RemoteServer
La classe RemoteServer é la superclasse utilizzata come base di partenza per implementare oggetti remoti. Più precisamente, le funzioni che
servono per creare ed esportare oggetti remoti vengono fornite astrattamente da RemoteServer e in maniera concreta dalle sue sottoclassi.
public abstract class RemoteServer
extends RemoteObject { protected RemoteServer()
{
//... }
protected RemoteServer(RemoteRef ref) {
//... }
public static String getClientHost()
throws ServerNotActiveException { //...
}
public static void setLog(OutputStream out) {
//... }
public static PrintStream getLog() {
//... }
}
Il costruttore crea un oggetto remoto RemoteServer con, eventualmente, un riferimento remoto dato dal parametro ref.
Il metodo getClientHost restituisce una stringa che rappresenta l’host del client relativo all’invocazione di metodo remoto che deve essere eseguita nel thread corrente (se nessuna invocazione di metodo remoto deve essere eseguita, viene sollevata una ServerNotActiveException).
2.2.3 La classe UnicastRemoteObject
Fornisce un supporto per la creazione e l’esportazione di oggetti remoti, implementando un oggetto server remoto con le seguenti caratteristiche:
• I riferimenti a tale oggetto sono validi al più per il tempo di vita del processo che crea l’oggetto remoto.
• La comunicazione con l’oggetto remoto usa un canale TCP.
• Le invocazioni, i parametri e i risultati usano uno stream protocol nella comunicazione tra client e server.
public class UnicastRemoteObject extends RemoteServer { protected UnicastRemoteObject() throws RemoteException {
//... }
protected UnicastRemoteObject(int port)
throws RemoteException { //...
}
protected UnicastRemoteObject(int port,
RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { //...
}
public Object clone()
throws java.lang.CloneNotSupportedException { //...
}
public static RemoteStub exportObject(Remote obj)
throws RemoteException { //...
}
public static RemoteStub exportObject(Remote obj, int port) throws RemoteException { //...
public static RemoteStub exportObject(Remote obj, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { //... }
public static boolean unexportObject(Remote obj, boolean force )
throws NoSuchObjectException { //...
} }
Il costruttore senza argomenti crea ed esporta un oggetto remoto su una porta scelta a runtime, mentre la seconda forma consente di specificare il numero di porta (dato dal parametro port) sul quale l’oggetto remoto accetta le chiamate. L’ultimo costruttore crea ed esporta un oggetto remoto che accetta chiamate sulla
port specificata tramite un socket creato dalle RMISocketFactory.
Il metodo exportObject viene usato per esportare un oggetto remoto che non è implementato estendendo la classe UnicastRemoteObject. Anche adesso (come per il costruttore) ci sono tre diverse forme, il cui significato è analogo a quello descritto prima, con in più il parametro obj, che rappresenta l’oggetto da esportare; ciò che viene restituito è, invece, uno stub per l’oggetto remoto obj che viene usato al posto dell’oggetto stesso in una chiamata di metodo remoto. Infatti, l’implementazione dell’oggetto remoto esportato rimane sempre sulla macchina virtuale in cui l’oggetto è stato creato. In altre parole, si può dire che un oggetto remoto viene passato “per riferimento” (e non “per valore”) in una chiamata RMI.
Infine, il metodo unexportObject rende l’oggetto obj non esportabile; in particolare, se il parametro force vale true, obj non verrà esportato sebbene ci siano chiamate pendenti ad esso, mentre se assume il valore false, l’oggetto non viene esportato solo se non ci sono delle chiamate pendenti che lo riguardino.
2.2.4 La classe RMIClassLoader
Tale classe comprende diversi metodi statici per supportare il caricamento dinamico di classi con l’RMI; metodi che vengono usati a runtime quando si rende necessario il marshalling e l’unmarshalling di argomenti e valori di ritorno delle chiamate di metodo remoto.
L’implementazione di tali metodi statici è fornita da un’istanza della classe RMIClassLoaderSpi. Quando uno di essi viene invocato, si comporterà in modo da delegare al corrispondente metodo dell’istanza del service provider, la quale viene scelta come segue:
• Se la proprietà di sistema java.rmi.server.RMIClassLoaderSpi è definita e assume per valore la stringa “default”, allora l’istanza del provider sarà il valore restituito dall’invocazione del metodo getDefaultProviderIstance(); se, invece, assume per valore il nome di una classe che possa essere caricata dal caricatore di classi di sistema, che sia assegnabile alla classe RMIClassLoaderSpi e che abbia un costruttore pubblico senza argomenti, allora verrà invocato tale costruttore per creare l’istanza del provider. Infine, se la proprietà è definita, ma alcune delle condizioni enunciate non sono vere, verrà sollevato un errore non specificato (appartenente alla classe Error quindi) quando si tenta di usare RMIClassLoader, per indicare il fallimento del tentativo di ottenere un’istanza del provider.
• Se la risorsa META-INF/services/java.rmi.server.RMIClassLoaderSpi è
visibile dal caricatore di classi di sistema, allora il contenuto di tale risorsa verrà interpretato come un file di configurazione del provider e il primo nome di classe specificato in tale file sarà usato come nome della classe provider; ovviamente tale classe dovrà poter essere caricata dal caricatore di classi di sistema, dovrà poter essere assegnata alla classe RMIClassLoaderSpi e dovrà avere un costruttore pubblico senza
argomenti, affinché quest’ultimo venga invocato per creare l’istanza del provider. Se, invece, la risorsa viene trovata, ma non si verificano le condizioni appena dette, verrà sollevato un errore ad indicare il fallimento del tentativo di ottenere un’istanza del provider.
• Altrimenti, l’istanza del provider sarà il valore restituito dall’invocazione del metodo getDefaultProviderInstance().
Di seguito vediamo come si presentano i principali metodi :
public class RMIClassLoader {
private static final RMIClassLoaderSpi defaultProvider = newDefaultProviderInstance();
private static final RMIClassLoaderSpi provider =
(RMIClassLoaderSpi)java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() {
public Object run() { return initializeProvider(); } });
private RMIClassLoader() {}
public static Class loadClass(URL codebase, String name) throws MalformedURLException, ClassNotFoundException { //...
}
public static Class loadClass(String codebase, String name) throws MalformedURLException, ClassNotFoundException { //...
}
public static Class loadClass(String codebase, String name, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { //...
}
public static Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { //... }
public static ClassLoader getClassLoader(String codebase) throws MalformedURLException, SecurityException { //...
}
public static String getClassAnnotation(Class cl) {
//... }
public static RMIClassLoaderSpi getDefaultProviderInstance() {
//... }
}
I metodi loadClass si occupano di caricare la classe specificata dalla parte name dell’URL specificato col parametro codebase. In un caso è anche possibile specificare il caricatore da usare (tramite il parametro defaultLoader). Se il parametro codebase assume il valore null, allora verrà usato il valore della proprietà di sistema java.rmi.server.codebase. Ognuno di questi metodi delega la chiamata al metodo RMIClassLoaderSpi.loadClass (String, String, ClassLoader) dell’istanza del provider, passando gli opportuni
argomenti. La classe loadProxyClass carica una classe proxy dinamica che implementa un insieme di interfacce con i nomi dati dal cammino URL codebase. Se quest’ultimo assume il valore null, verrà utilizzato il valore della proprietà di sistema java.rmi.server.codebase. Tale metodo, delega al metodo
RMIClassLoaderSpi.loadProxyClass(String, String[], ClassLoader)
dell’istanza del provider. Il metodo getClassLoader restituisce il caricatore di classe che si occupa di caricare le classi dal path URL dato da codebase. Questo metodo delega al metodo RMIClassLoaderSpi.getClassLoader(String) dell’istanza del provider. Infine, la classe getDefaultProviderInstance restituisce l’istanza del provider di default per l’interfaccia di servizio RMIClassLoaderSpi.
2.2.5 La classe RMISocketFactory
Un’istanza di questa classe viene usata dalla runtime di RMI per ottenere i socket del client e del server nelle chiamate remote. Un’applicazione può richiedere alla runtime di usare una propria istanza di socket factory anziché quella di default (mediante il metodo setSocketFactory).
L’implementazione di default prevede tre approcci per la creazione dei socket del lato client. Innanzitutto viene tentata una connessione diretta con la Virtual Machine remota. Se ciò fallisce (a causa della presenza di firewall), la runtime usa HTTP con il numero di porta esplicito del server. Se il firewall non permette nemmeno questo tipo di comunicazione, allora viene usato HTTP, verso uno script cgi-bin sul server, per effettuare la chiamata remota.
La classe RMISocketFactory implementa le interfacce RMIClientSocketFactory e RMIServerSocketFactory, definendo il comportamento dei metodi in esse dichiarati.
public abstract class RMISocketFactory
implements RMIClientSocketFactory, RMIServerSocketFactory{ /** Client/server socket factory to be used by RMI runtime */ private static RMISocketFactory factory = null;
/** default socket factory used by this RMI implementation */ private static RMISocketFactory defaultSocketFactory;
/** Handler for socket creation failure */
private static RMIFailureHandler handler = null; public RMISocketFactory()
{
//... }
public abstract Socket createSocket(String host, int port) throws IOException; public abstract ServerSocket createServerSocket(int port)
public synchronized static void setSocketFactory(
RMISocketFactory fac) throws IOException { //...
}
public synchronized static RMISocketFactory getSocketFactory() { //...
}
public synchronized static RMISocketFactory
getDefaultSocketFactory() { //...
}
public synchronized static void
setFailureHandler(RMIFailureHandler fh) { //...
}
public synchronized static RMIFailureHandler
getFailureHandler() { //...
} }
I metodi createSoscket e createServerSocket creano rispettivamente, un socket lato client connesso sull’host e sulla port specificati, e un socket lato server sulla porta specificata dal parametro port (un valore 0 per port indica una porta anonima).
Il metodo setSocketFactory serve a settare il socket factory globale (dal quale si otterranno i vari socket), che, come già detto, può essere differente da quello di default. Occorre tener presente però che, il socket factory di RMI può essere settato una sola volta, soltanto se il security manager correntemente installato lo permette; in caso contrario verrà sollevata una SecurityException.
Il metodo getSocketFactory restituisce il socket factory precedentemente settato o null nel caso non sia stato effettuato alcun settaggio,
invece, col metodo getDefaultSocketFactory viene restituito il socket factory di default, cioè quello usato dalla runtime di RMI se non diversamente specificato.
Col metodo setFailureHandler è possibile settare l’handler che deve essere richiamato dalla runtime quando non va a buon fine la creazione di un socket lato server. Nel caso in cui non è stato settato alcun handler, il comportamento di default della runtime è quello di tentare di ricreare il server socket, chiedendo prima al security manager (se esso è installato) se l’operazione è consentita. Infine, il metodo getFailureHandler restituisce l’handler settato.
2.2.6 La classe RemoteStub
Costituisce la superclasse per gli stub dei client e fornisce la struttura per supportare le varie semantiche inerenti il riferimento remoto di oggetti.
Gli oggetti stub forniscono lo stesso insieme di metodi definito nelle interfacce che descrivono i vari oggetti remoti.
abstract public class RemoteStub extends RemoteObject {
protected RemoteStub() { //...
}
protected RemoteStub(RemoteRef ref) { //...
} }
Tramite i due costruttori su indicati è possibile ottenere oggetti di tipo RemoteStub ed in particolare nel secondo caso è possibile specificare un riferimento remoto mediante il parametro ref.
2.2.7 L’interfaccia RemoteRef
Serve a descrivere un riferimento remoto col quale accedere ad un oggetto mediante il meccanismo di invocazione di metodo remoto.
public interface RemoteRef extends java.io.Externalizable { final static String packagePrefix = "sun.rmi.server"; Object invoke(Remote obj,
java.lang.reflect.Method method, Object[] params,
long opnum)
throws Exception;
RemoteCall newCall(RemoteObject obj, Operation[] op, int opnum, long hash)
throws RemoteException;
void invoke(RemoteCall call) throws Exception;
int remoteHashCode();
boolean remoteEquals(RemoteRef obj); String remoteToString();
}
I metodi qui definiti verranno descritti in dettaglio nel capitolo successivo quando parleremo del package sun.rmi.server, il quale contiene una serie di classi che implementano l’interfaccia RemoteRef e che entrano in gioco durante una invocazione di metodo remoto.
2.2.8 L’interfaccia ServerRef
Tale interfaccia estende la precedente (RemoteRef) aggiungendo la definizione dei metodi necessari al meccanismo di invocazione remota dal lato server.
public interface ServerRef extends RemoteRef {
RemoteStub exportObject(Remote obj, Object data) throws RemoteException;
String getClientHost() throws ServerNotActiveException; }
Per ciò che concerne la descrizione dei metodi sopra definiti vale lo stesso discorso fatto nel paragrafo precedente, in base al quale si rimanda al capitolo successivo.
2.2.9 Classi Exception
In questo paragrafo non vengono descritte nel dettaglio le varie classi Exception presenti; per completezza, comunque, nella seguente figura viene mostrato un class diagram (secondo la notazione UML) che elenca le varie classi e mostra le relazioni di parentela tra esse.
java.io.Serializable java.lang.Throwable java.lang.Exception java.lang.CloneNotSupported Exception .ServerCloneException ServerNotActiveException java.io.IOException SocketSecurityException ExportException RemoteException
2.3 Il package java.rmi.registry
Contiene l’interfaccia registry e la classe LocateRegistry utilizzate per registrare e accedere oggetti remoti per nome. Gli oggetti remoti sono registrati quando sono noti al processo registro di una macchina (host), il quale viene creato eseguendo il programma rmiregistry[7].
2.3.1 L’interfaccia Registry
Costituisce l’interfaccia remota per un semplice oggetto remoto registry, il quale fornisce metodi per la memorizzazione e la ricerca dei riferimenti ai vari oggetti remoti.
public interface Registry extends Remote { public static final int REGISTRY_PORT public void bind(String name, Remote obj)
throws RemoteException, AccessException, AlreadyBoundException {
//... }
public void rebind(String name, Remote obj)
throws RemoteException, AccessException { //...
}
public void unbind(String name)
throws RemoteException, NotBoundException,
AccessException { //...
}
public Remote lookup(String name)
throws RemoteException, NotBoundException,
AccessException { //...
public String[] list() throws RemoteException, AccessException { //... } }
I metodi bind, unbind e rebind vengono utilizzati per manipolare i riferimenti associati con i nomi degli oggetti remoti registrati, mentre i metodi list e lookup sono usati per ricercare i riferimenti all’interno del registry.
2.3.2 La classe LocateRegistry
Viene utilizzata per ottenere un riferimento ad un oggetto remoto registry iniziale su di un particolare host (incluso l’host locale) o per creare un registry che accetti chiamate su una specifica porta.
public final class LocateRegistry extends Object {
public static Registry getRegistry() throws RemoteException {
//... }
public static Registry getRegistry(int port)
throws RemoteException { //...
}
public static Registry getRegistry(String host) throws RemoteException { //...
}
public static Registry getRegistry(String host, int port) throws RemoteException { //...
}
public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf) throws RemoteException { //...
public static Registry createRegistry(int port)
throws RemoteException { //...
}
public static Registry createRegistry(int port,
RMIClientSocketFactory csf RMIServerSocketFactory ssf) throws RemoteException { //... } }
I primi quattro metodi getRegistry restituiscono un riferimento all’oggetto remoto registry che si trova rispettivamente sull’host locale alla porta di default 1099, sull’host locale alla porta specificata dal parametro port , sull’host specificato dal parametro host alla porta di default, sull’host e alla porta specificati dai parametri host e port. L’ultimo metodo getRegistry, non solo restituisce il riferimento al registry che si trova all’host e alla porta specificati, ma farà in modo che la comunicazione con esso avvenga mediante un socket creato dalla factory specificata come ultimo parametro.
Occorre notare che una chiamata a getRegistry, in realtà, non crea una connessione all’host remoto, ma crea semplicemente un riferimento locale al
registry remoto. Quindi essa terminerà con successo anche se nessun registry è in
esecuzione sull’host remoto, con la conseguenza che una successiva invocazione di metodo al registry restituito dalla getRegistry può fallire.
Il primo metodo createRegistry si occupa di creare ed esportare un
registry sull’host locale che accetti richieste sulla porta specificata dal parametro port. Il secondo svolge la stessa funzione con l’aggiunta del fatto che fornirà dei
socket per la comunicazione col registry.
2.4 Il package java.rmi.dgc
Contiene l’interfaccia DGC e le classi Lease e VMID che vengono utilizzate dal sistema distribuito di garbage collection. In particolare, DGC è implementata dal lato server e, come vedremo in seguito, contiene due metodi usati per indicare che un oggetto remoto è referenziato da un client e per indicare che un riferimento remoto è stato completato.
2.4.1 L’interfaccia DGC
Contiene i due metodi dirty e clean. Una chiamata a dirty viene effettuata quando un client (individuato col proprio VMID) crea un riferimento ad un oggetto remoto, mentre una corrispondente chiamata al metodo clean viene fatta quando non ci sono più riferimenti ad un dato oggetto remoto in un client.
Un riferimento remoto ad un oggetto viene mantenuto per un periodo di tempo prestabilito (lease period) a partire da quando viene ricevuta una dirty call. Sarà responsabilità del client mantenere in vita il riferimento, facendo delle dirty
call aggiuntive prima che scada il lease period, altrimenti il garbage collector
distribuito assume che l’oggetto remoto non è più referenziato da quel client.
public interface DGC extends Remote {
Lease dirty(ObjID[] ids, long sequenceNum, Lease lease)
throws RemoteException; void clean(ObjID[] ids, long sequenceNum, VMID vmid,
boolean strong) throws RemoteException; }
Quando un client effettua una dirty call, comunica, tramite il parametro
ids[], quali sono gli identificatori degli oggetti remoti riferiti. Da parte sua il
garbage collector mantiene una lista dei riferimenti, cioè per ogni oggetto remoto esso mantiene e aggiorna una lista contenente i client che riferiscono quell’oggetto.
Il parametro lease contiene un identificatore unico di VM (il VMID) per individuare il client e assieme ad esso anche il lease period richiesto. Bisogna tener presente che non tutti i client sono in grado di generare un VMID che sia universalmente unico a causa di restrizioni di sicurezza (infatti dovrebbe contenere l’indirizzo dell’host); in tal caso, il client può usare un VMID di valore
null e il garbage collector si occuperà di assegnargliene uno.
Il parametro sequenceNum rappresenta un numero di sequenza (che viene incrementato ad ogni chiamata) usato per rilevare ed eventualmente scartare chiamate arrivate in ritardo al garbage collector.
Una chiamata al metodo dirty restituisce un oggetto Lease, contenente il VMID usato e il lease period accordato dal garbage collector (infatti un server può decidere di concedere un lease period più breve di quello richiesto dal client).
Occorre notare che un client deve fare una sola dirty call iniziale per riferire ciascun oggetto remoto (anche se possiede riferimenti multipli allo stesso oggetto remoto) e successivamente eseguirà ulteriori dirty call per rinnovare di volta in volta il lease period.
Quando un client non ha più alcun riferimento ad uno specifico oggetto remoto, eseguirà una chiamata al metodo clean, con la quale rimuove il vmid dalla lista dei riferimenti di ogni oggetto remoto indicato in ids[]. Anche in tal caso il sequenceNum serve per individuare delle clean call ritardate.
Se l’argomento strong vale true, la clean call è il risultato di una dirty call fallita e, in tal caso, il sequence number del client individuato da vmid deve essere memorizzato.
2.4.2 La classe Lease
Questa classe contiene un identificatore di Virtual Machine (VM) unico e un lease period. Un oggetto Lease viene usato per richiedere o per concedere dei lease period per i riferimenti ad un dato oggetto remoto.
public final class Lease implements java.io.Serializable {
private VMID vmid; private long value;
public Lease(VMID id, long duration) {
//... }
public VMID getVMID() {
//... }
public long getValue() {
//... }
}
Il costruttore crea un oggetto Lease con uno specifico VMID e uno specifico lease period, dati dai due parametri id e duration. Come già detto, id può anche assumere il valore null.
Il metodo getVMID restituisce il VMID associato all’oggetto Lease, così come il metodo getValue restituisce il lease period.
2.4.3 La classe VMID
Tale classe rappresenta un identificatore unico per tutte le Java Virtual Machine (JVM). Un oggetto VMID è usato dal garbage collector distribuito per individuare le VM client.
public final class VMID implements java.io.Serializable { private static byte[] localAddr = computeAddressHash();
private byte[] addr; private UID uid; public VMID() {
//... }
public int hashCode() {
//... }
public boolean equals(Object obj) {
//... }
public String toString() {
//... }
private static byte[] computeAddressHash() {
//... }
}
Il costruttore crea un oggetto VMID unico per tutte le JVM sotto le seguenti condizioni:
a) Le condizioni per l’unicità degli oggetti della classe java.rmi.server.UID devono essere soddisfatte
b) L’indirizzo dell’host deve essere unico e costante almeno per tutto il tempo di vita dell’oggetto in questione.
Il metodo hashCode calcola un hash code per l’oggetto VMID, mentre il metodo equals confronta l’oggetto VMID con quello passato come parametro e restituisce il valore true se essi costituiscono lo stesso identificatore.
Infine, il metodo toString restituisce una rappresentazione sottoforma di stringa dell’oggetto VMID.
2.5 Il package java.rmi.activation
Questo package è stato introdotto a partire dalla versione 1.2 del JDK e fornisce la capacità di attivare oggetti remoti persistenti. Poiché, ai fini degli studi successivi, questi oggetti non rivestono particolare importanza, viene data solo una brevissima descrizione di alcune delle classi principali e si rimanda alla documentazione fornita dagli sviluppatori Java[8], chiunque fosse interessato ad approfondimenti.
La classe Activatable definisce i metodi base implementati da oggetti persistenti attivabili; contiene due costruttori: uno è utilizzato per creare e registrare (nel sistema di attivazione) gli oggetti a cui si accede attraverso specifiche porte TCP, l’altro è usato per attivare un oggetto in base all’ActivationID di un oggetto e ai dati persistenti che sono stati archiviati per esso.
Gli oggetti della classe ActivationID sono utilizzati per identificare univocamente gli oggetti disponibili e contengono informazioni su come gli oggetti devono essere attivati. L’ActivationID di un oggetto è creato quando quest’ultimo è registrato nel sistema di attivazione.
La classe ActivationDesc incapsula le informazioni necessarie ad attivare un oggetto. Essa mette a disposizione cinque metodi che possono essere utilizzati per recuperare queste informazioni.
La classe ActivationGroup è usata per raggruppare gli oggetti attivabili in modo che possano essere eseguiti nella stessa JVM. Gli oggetti ActivationGroup sono utilizzati per creare istanze degli oggetti attivabili all’interno del loro gruppo.
La classe ActivationGroupID identifica univocamente un oggetto ActivationGroup e contiene informazioni relative al sistema di attivazione.
La classe ActivationGroupDesc incapsula le informazioni necessarie alla creazione di un oggetto ActivationGroup.
L’interfaccia ActivationSystem è implementata da oggetti che registrano gli oggetti attivabili e gruppi di essi. Con la costante SYSTEM_PORT viene identificata la porta TCP usata dal sistema di attivazione.
L’interfaccia Activator è implementata da quegli oggetti che attivano oggetti registrati in un ActivationSystem.
L’interfaccia ActivationInstantiator offre dei metodi per le classi che creano istanze di oggetti attivabili.
Infine, ActivationMonitor offre dei metodi per la gestione delle informazioni relative agli oggetti attivi e non attivi.