Java RMI (Remote Method Invocation) RPC in JAVA Le RMI introducono la possibilità di richiedere esecuzione di metodi remoti in JAVA integrando il tutto con il paradigma OO Accesso ad oggetti remoti In Java non sono possibili riferimenti remoti C1 instance CLASS server S1 S1 instance S1 instance state operations Supporto di INTEGRAZIONE Definizioni e generalità per la DISTRIBUZIONE Insieme di politiche e meccanismi che permettono ad un’applicazione Java in esecuzione su una macchina di invocare i metodi di un oggetto di una applicazione Java in esecuzione su una macchina remota Viene creato localmente solo il riferimento ad un oggetto remoto, che è invece effettivamente attivo su un nodo remoto Un programma cliente invoca i metodi attraverso questo riferimento locale ma si possono costruire con RMI Remote Method Invocation due proxy, stub dalla parte del cliente, skeleton dalla parte del servitore C1 instance S1 instance Unico ambiente di lavoro come conseguenza del linguaggio Java ma Eterogeneità di ambienti C1 Stub CLIENT node Reti di calcolatori, Java RMI - 1 S1 Skeleton SERVER node Reti di calcolatori, Java RMI - 2 Architettura Caratteristiche Registry Modello a oggetti distribuito RMI System Client Program Server Program Stubs Skeletons Remote Reference Layer Remote Reference Layer Transport Layer Stub: proxy locale su cui vengono fatte le invocazioni destinate all’oggetto remoto Skeleton: entità remota che riceve le invocazioni fatte sullo stub e le realizza effettuando le corrispondenti chiamate sul server Registry: servizio di nomi che consente al server di pubblicare un servizio e al client di recuperarne il proxy Remote Reference Layer: - fornisce il supporto alle chiamate inoltrate dallo stub - localizza il server RMI relativo all’oggetto remoto richiesto Transport Layer: definisce e supporta la semantica dell’invocazione e della comunicazione, gestisce le connessioni (TCP/IP, timeout) e le trasmissioni (sequenziali, serializzate), usando un protocollo proprietario Reti di calcolatori, Java RMI - 3 Nel modello ad oggetti distribuito di Java un oggetto remoto consiste in: - un oggetto i cui metodi sono invocabili da un'altra JVM, potenzialmente in esecuzione su un host differente - un oggetto descritto tramite interfacce remote che dichiarano i metodi accessibili da remoto Chiamata locale vs. chiamata remota Il cliente invoca un metodo di un oggetto non locale Sintassi: uguale => trasparenza Chiamata sincrona Semantica: diversa Chiamate locali: affidabilità ≈ 100% Chiamate remote: uso di comunicazione possibilità di fallimento semantica “at most once” con uso TCP Server remoto come locale: Ogni chiamata esegue in modo indipendente e parallelo (?) Reti di calcolatori, Java RMI - 4 SERVIZIO REMOTO RMI Uso di RMI Interfacce e Implementazione Per sviluppare un’applicazione distribuita usando RMI si deve: Separazione tra definizione del comportamento => interfacce implementazione del comportamento => classi Per realizzare componenti utilizzabili in remoto: 1. definizione del comportamento Ö metodi disponibili in interfaccia che - estende java.rmi.Remote e - propaga java.rmi.RemoteException 2. implementare comportamento Ö il server specificato in una classe che - implementa l’interfaccia definita - estende 1. Definire interfacce e implementazioni dei server utilizzabili in remoto (implementazioni?) 2. Compilare le classi (con javac) e generare stub e skeleton (con rmic) delle classi utilizzabili in remoto 3. Pubblicare il servizio - attivare il registry - registrare il servizio (il server deve fare una bind sul registry) 4. Il cliente deve ottenere il riferimento all’oggetto remoto tramite il name service, facendo una lookup sul registry java.rmi.UnicastRemoteObject A questo punto l’interazione tra il cliente e il server può procedere N.B.: questa è una descrizione di base, dettagli sul registry e sul caricamento dinamico delle classi saranno dati in seguito Reti di calcolatori, Java RMI - 5 Reti di calcolatori, Java RMI - 6 Esempio: servizio di echo remoto Implementazione del Server public class EchoRMIServer extends java.rmi.server.UnicastRemoteObject implements EchoInterface{ Definizione dell’interfaccia del servizio public interface EchoInterface extends java.rmi.Remote { String getEcho(String echo) throws java.rmi.RemoteException; // Costruttore public EchoRMIServer() throws java.rmi.RemoteException { super(); } // Implementazione del metodo remoto dichiarato nell'interfaccia public String getEcho(String echo) throws java.rmi.RemoteException { return echo; } } public static void main(String[] args){ // Registrazione del servizio try { EchoRMIServer serverRMI = new EchoRMIServer(); Naming.rebind("EchoService", serverRMI); } catch (Exception e) {e.printStackTrace(); System.exit(1); } } } Reti di calcolatori, Java RMI - 7 Reti di calcolatori, Java RMI - 8 Implementazione del Client Compilazione public class EchoRMIClient { // Avvio del Client RMI public static void main(String[] args) { BufferedReader stdIn= new BufferedReader( new InputStreamReader(System.in)); javac try { // Connessione al servizio RMI remoto EchoInterface serverRMI = (EchoInterface) java.rmi.Naming.lookup("EchoService"); // Notare l’uso di cast Con il compilatore RMI (con opzione –vcompat in Java 1.5): rmic [-vcompat] EchoRMIServer // Interazione con l'utente String message, echo; System.out.print("Messaggio? "); message = stdIn.readLine(); // Richiesta del servizio remoto echo = serverRMI.getEcho(message); System.out.println("Echo: "+echo+"\n"); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } EchoInterface.java EchoRMIClient.java EchoRMIServer.java Creazione dello Stub e dello Skeleton Che genera i file: EchoRMIServer_Stub.class EchoRMIServer_Skel.class Esecuzione 1. Avviamento del registry: rmiregistry 2. Avviamento del server: java EchoRMIServer 3. Avviamento del client: java EchoRMIClient Reti di calcolatori, Java RMI - 9 Reti di calcolatori, Java RMI - 10 Passaggio di parametri Serializzazione Trattamento dei parametri tra pari Tipo Tipi primitivi Oggetti Metodo Locale Per valore Per riferimento Oggetti Remoti Esportati Per riferimento Metodo Remoto Per valore Per valore (deep copy) Per riferimento remoto Shallow Copy vs Deep Copy Passaggio per valore => Serializable Objects Passaggio per riferimento => Remote Objects Serializable Objects Oggetti la cui locazione non è rilevante per lo stato sono passati per valore: ne viene serializzata l’istanza che sarà deserializzata a destinazione per crearne una copia locale. Remote Objects Oggetti la cui funzione è strettamente legata alla località in cui eseguono (server) sono passati per riferimento: ne viene serializzato lo stub, creato automaticamente dal proxy (stub o skeleton) su cui viene fatta la chiamata in cui compaiono come parametri Reti di calcolatori, Java RMI - 11 Marshalling: processo di codifica degli argomenti e dei risultati per la trasmissione Unmarshalling: processo inverso di decodifica di argomenti e risultati ricevuti In Java questo problema è risolto attraverso la semplice serializzazione, fatta in maniera trasparente dal supporto (copia dei dati trasformati in una sequenza e un messaggio e trasferiti tra le JVM) Serializzazione: trasformazione di complessi in semplici sequenze di byte oggetti => metodo writeObject() su uno stream di output Deserializzazione: decodifica di una sequenza di byte e costruzione di una copia dell’oggetto originale => metodo readObject() da uno stream di input Utilizzo: - storage - trasmissione (parametri e valori di ritorno in RMI) Reti di calcolatori, Java RMI - 12 Interazione con stream per TX/RX Esempio Esempio di storage Record record = new Record(); FileOutputStream fos = new FileOutputStream(“data.ser”); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(record); FileInputStream fis = new FileInputStream(“data.ser”); ObjectInputStream ois = new ObjectInputStream(fis); record = (Record)ois.readObject(); Riprendendo il server di echo Ö messaggio come oggetto anziché come stringa public class Message implements Serializable { String content; // … altri eventuali campi Si possono serializzare soltanto istanze di oggetti serializzabili, ovvero che: - public Message(String msg) { content=msg; } implementano l’interfaccia Serializable contengono esclusivamente oggetti (o riferimenti a oggetti) serializzabili public String toString() { return content; } NOTA BENE: NON viene trasferito l’oggetto vero e proprio ma solo le informazioni che caratterizzano l’istanza => no metodi, no costanti, no variabili static, no variabili transient } Al momento della deserializzazione sarà ricreata una copia dell’istanza “trasmessa” usando il .class (che deve quindi essere accessibile!!!) dell’oggetto e le informazioni ricevute. Reti di calcolatori, Java RMI - 13 Reti di calcolatori, Java RMI - 14 Stub e Skeleton Stub e Skeleton garantiscono una parziale trasparenza e sono oggetti generati dal compilatore RMI che gestiscono il supporto RMI via serializzazione/deserializzazione e comunicazione tra client e server Procedura di comunicazione: 1. il client ottiene un’istanza dello stub 2. il client chiama metodi sullo stub 3. lo stub: - crea una connessione con lo skeleton (o ne usa una già esistente) - fa la serializzazione delle informazioni per la chiamata (id del metodo e argomenti) - invia le informazioni allo skeleton 4. lo skeleton: - fa deserializzazione dei dati ricevuti - effettua la chiamata sull’oggetto che implementa il server - fa serializzazione del valore di ritorno e invio allo allo stub 5. lo stub: - fa deserializzazione del valore di ritorno e restituzione del risultato al client Reti di calcolatori, Java RMI - 15 Stub Estende java.rmi.server.RemoteStub Implementa java.rmi.Remote InterfacciaRemotaServer (es. EchoInterface) Realizza le chiamate ai metodi dell’interfaccia remota del server usando il metodo invoke(…) di java.rmi.server.RemoteRef public final class EchoRMIServer_Stub extends java.rmi.server.RemoteStub implements EchoInterface, java.rmi.Remote { … // implementazione di getEcho public Message getEcho(Message message) throws java.rmi.RemoteException { try { … // creazione della chiamata java.rmi.server.RemoteCall remotecall = super.ref.newCall(this, operations, 0, 0xca41fff3e3260b7aL); // serializzazione dei parametri try { ObjectOutput objectoutput = remotecall.getOutputStream(); objectoutput.writeObject(message); } // cattura eccezioni di marshalling … Reti di calcolatori, Java RMI - 16 Stub segue Skeleton Implementa // invio della chiamata super.ref.invoke(remotecall); // deserializzazione del valore di ritorno Message message1; try { ObjectInput objectinput = remotecall.getInputStream(); message1 = (Message)objectinput.readObject(); } // cattura eccezioni di unmarshalling … // segnalazione chiamata andata a buon fine finally { super.ref.done(remotecall); } // restituzione del risultato return message1; } // cattura varie eccezioni … } … } Reti di calcolatori, Java RMI - 17 java.rmi.server.Skeleton Inoltra le richieste al server usando il metodo dispatch(…) public final class EchoRMIServer_Skel implements Skeleton { … public void dispatch(Remote remote, RemoteCall remotecall, int opnum, long hash) throws Exception { // validazione e verifica di errori … EchoRMIServer echormiserver = (EchoRMIServer)remote; switch(opnum) { case 0: // '\0' Message message; try { // deserializzazione dei parametri di invocazione ObjectInput objectinput = remotecall.getInputStream(); message = (Message)objectinput.readObject(); } catch(IOException ioexception1) { throw new UnmarshalException("error unmarshalling arguments", ioexception1); } catch(ClassNotFoundException classnotfoundexception) { throw new UnmarshalException("error ...” unmarshalling arguments", classnotfoundexception); } finally { remotecall.releaseInputStream(); } Reti di calcolatori, Java RMI - 18 Skeleton segue // effettiva invocazione del metodo sul server Message message1 = echormiserver.getEcho(message); try { // serializzazione del valore di ritorno ObjectOutput objectoutput = remotecall.getResultStream(true); objectoutput.writeObject(message1); } catch(IOException ioexception) { throw new MarshalException("error marshalling return", ioexception); } break; default: throw new UnmarshalException("invalid ..."); } } … } Chi genera i thread che devono operare sull’oggetto server, se non lo skeleton? Questo si lega al fatto che skeleton e stub sono generati non come sorgenti, ma come class? Interazione Client/Server: Socket e Thread Client L’invocazione di un metodo remoto implica una connessione TCP (Socket stream) con l’oggetto remoto Condivisione delle connessioni tra la JVM client e la JVM server: richiesta di connessione per una stessa JVM se c’è una connessione aperta Ö riuso se non c’è una connessione aperta Ö nuova Al completamento di una operazione la connessione viene liberata e rimane attiva fino allo scadere di un intervallo di timeout RMI permette a più thread clienti di accedere ad uno stesso oggetto server gestendo in modo automatico la concorrenza delle operazioni Ö l’implementazione del cliente non deve preoccuparsi In caso di richieste da parte della stessa JVM, il canale di comunicazione unico può produrre effetti di sequenzializzazione (era ciò che accadeva nelle prime implementazioni) Reti di calcolatori, Java RMI - 19 Reti di calcolatori, Java RMI - 20 Server RMI Registry Dipende tutto dall’implementazione. Nel seguito consideriamo una possibile e semplice implementazione concorrente. L’esportazione di un oggetto remoto provoca la creazione di • diversi thread per la gestione delle richieste (Server Socket) • un thread che riceve le richieste (Listener Thread) associati all’oggetto remoto Localizzazione del servizio: un client in esecuzione su una macchina ha bisogno di localizzare un server a cui vuole connettersi, che è in esecuzione su un’altra macchina. Tre possibili soluzioni: • Il client conosce staticamente dov’è il server • L’utente dice all’applicazione client dov’è il server • Un servizio standard (naming service) in una locazione ben nota, che il client conosce, funziona come sistema di nomi Condivisione delle server socket e dei listener: server in esecuzione nella stessa JVM possono condividere la porta d’ascolto (soluzione di default se non la specificano) e il listener che ne gestisce le richieste Java RMI utilizza un naming service: RMI Registry Ogni richiesta viene gestita da • un thread che esegue la richiesta (Server Thread) Mantiene un insieme di coppie {name, reference} Name: stringa arbitraria non interpretata Condivisione dei server thread: allocazione di un pool di thread; all’accettazione di una nuova richiesta un Server Thread viene estratto ed utilizzato. E se i thread del pool vengono tutti allocati? Soluzioni possibili? In ogni caso: RMI permette a più thread di accedere ad uno stesso oggetto server (gestione concorrente delle richieste) Ö l’implementazione dei metodi remoti deve essere thread-safe Reti di calcolatori, Java RMI - 21 Name Echo Server Reference Echo Daytime Server Daytime Login Login Server Trasparenza alla locazione?!? Reti di calcolatori, Java RMI - 22 Operazioni Implementazione del Registry Metodi della classe java.rmi.Naming: Il Registry è un server RMI public public public public public Classe d’implementazione: sun.rmi.registry.RegistryImpl static static static static static void bind(String name, Remote obj) void rebind(String name, Remote obj) void unbind(String name) String[] list(String name) Remote lookup(String name) name -> combina la locazione del registry e il nome logico del servizio, nel formato: Interfaccia: java.rmi.registry.Registry public interface Registry extends Remote { public static final int REGISTRY_PORT = 1099; //registryHost:port/logical_name public Remote lookup(String name) throws RemoteException, NotBoundException, AccessException; public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException; public static void rebind(String name, Remote obj) throws RemoteException, AccessException; public static void unbind(String name) throws RemoteException, NotBoundException, AccessException; public static String[] list(String name) throws RemoteException, AccessException; A default: registryHost = macchina su cui esegue il programma che invoca il metodo port = 1099 Il Registry è un server RMI in esecuzione su registryHost in ascolto su port } Ognuno di questi metodi crea una connessione (socket) con il registry identificato da host e porta Remote è il riferimento remoto Perchè usare anche la classe Naming? Reti di calcolatori, Java RMI - 23 Reti di calcolatori, Java RMI - 24 BOOTSTRAP Uso del Registry Problema di bootstrapping anche per localizzare il registry Ö uso delle classi Naming e LocateRegistry Due possibilità: • Naming: metodi statici, no istanza • LocateRegistry implementa i metodi public static Registry createRegistry(int port) public static Registry createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) public static Registry getRegistry() public static Registry getRegistry(int port) public static Registry getRegistry(String host) public static Registry getRegistry(String host, int port) public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf) 1. Usare il programma rmiregistry di Sun, lanciato specificando o meno la porta: rmiregistry rmiregistry 10345 Ö istanza separata della JVM Ö struttura e comportamento standard 2. Creare all’interno del codice un proprio registry: public static Registry createRegistry(int port) Ö stessa istanza della JVM Ö struttura e comportamento personalizzabile (es. organizzazione gerarchica) Soluzione al bootstrapping per il registry: 1. Naming.”metodo()” con host e porta 1. Invocazione di LocateRegistry.getRegistry() che restituisce lo stub del registry 2. Invocazione di “metodo()” sullo stub Reti di calcolatori, Java RMI - 25 Reti di calcolatori, Java RMI - 26 Sicurezza del registry Organizzazione gerarchica Registry Problema: accedendo al registry (individuabile interrogando tutte le porte di un host) è possibile ridirigere per scopi maliziosi le chiamate ai server RMI registrati (es. list()+rebind()) Un registry è un server RMI => è possibile registrarlo in un altro registry Soluzione: i metodi bind(), rebind() e unbind() sono invocabili solo dall’host su cui è in esecuzione il registry Ö non si accettano modifiche della struttura client/server da nodi esterni Registry ourRegistry = LocateRegistry.createRegistry(OUR_PORT); Registry preexistingRegistry = LocateRegistry.getRegistry(1099); preexixtingRegistry.rebind (“secondary registry”, ourRegistry); Printer Registry Name Reference Primary Registry Name Ö sull’host in cui vengono effettuate le chiamate al registry deve essercene almeno uno in esecuzione Registro Stampanti Registro Installazioni ... Reference Postscript Pdf ... Program Registry Name Reference Antivirus IDE ... ... Ma… • Tutti i registry si trovano sulla stessa macchina dove è attivato il primary registry • Lato client devo gestire una duplice interrogazione • RMI registry non supporta in alcun modo la negoziazione dei servizi Reti di calcolatori, Java RMI - 27 Reti di calcolatori, Java RMI - 28 Distribuzione delle classi Classpath ed Esecuzione In una applicazione RMI è necessario che siano disponibili gli opportuni file .class nelle località che lo richiedono (per l’esecuzione o per la deserializzazione) Rmiregistry, server e client devono poter accedere alle classi necessarie per l’esecuzione. Si presti quindi particolare attenzione al direttorio dove vengono lanciati l’rmiregistry, il server e il client Il Server deve poter accedere a: • • • • interfacce che definiscono il servizio implementazione del servizio stub e skeleton delle classi di implementazione altre classi utilizzate dal server Il Client deve poter accedere a: • • • • interfacce che definiscono il servizio stub delle classi di implementazione del servizio classi del server usati dal client (es. valori di ritorno) altre classi utilizzate dal client È necessario: 1. localizzare il codice (in locale o in remoto) 2. effettuare il download (se in remoto) 3. eseguire in modo sicuro il codice scaricato Reti di calcolatori, Java RMI - 29 In particolare, ipotizzando di avere tutti i file .class nel direttorio corrente (“.”), e di lanciare rmiregistry, client, e server dal direttorio corrente, bisogna aggiungere al CLASSPATH tale direttorio Sotto Linux: ciò è possibile aggiungendo nella propria directory HOME il file ".profile" (creandolo se non esiste). In particolare, il file .profile deve contenere le seguenti linee per aggiungere il direttorio corrente al CLASSPATH: CLASSPATH=.:$CLASSPATH export CLASSPATH E se volessimo lanciare il client, il server, e l’rmiregistry in direttori diversi? Si noti che questa è la modalità standard in Linux per aggiungere/modificare una variabile di ambiente. Nelle FAQ del corso, si veda anche il caso della variabile di ambiente PATH Reti di calcolatori, Java RMI - 30 Localizzazione del codice Utilizzo del codebase Sia il cliente sia il servitore devono avere a disposizione le classi di supporto per la interazione Il codebase viene usato dal client per scaricare le classi necessarie relative al server (interfaccia, stub, oggetti restituiti come valori di ritorno) Le informazioni relative a dove reperire il codice delle classi - se non disponibile localmente - sono memorizzate sul server e passate al client by need ⇒ server RMI mandato in esecuzione specificando nell’opzione java.rmi.server.codebase l’URL da cui prelevare le classi necessarie. L’URL può essere: una directory del file system locale (file://) l’indirizzo di un server ftp (ftp://) l’indirizzo di un server http (http://) Il codebase viene usato dal server per scaricare le classi necessarie relative al client (oggetti passati come parametri nelle chiamate) Il codebase è una proprietà del server che viene annotata nel reference pubblicato sul registry Le classi vengono cercate sempre prima nel CLASSPATH locale, solo in caso di insuccesso vengono cercate nel codebase Reti di calcolatori, Java RMI - 31 Reti di calcolatori, Java RMI - 32 CODEBASE ESEMPIO Sia il server che il client devono essere lanciati specificando il codebase che indica dove sono disponibili le rispettive classi Esempio con client e server sullo stesso host e classi in direttori diversi: java –Djava.rmi.server.codebase = file://c:\…\RMIdir\ServerDir\ EchoRMIServer java –Djava.rmi.server.codebase = file://c:\…\RMIdir\ClientDir\ EchoRMIClient localhost Client e Server su host diversi e classi scaricabili via http: java –Djava.rmi.server.codebase = http://server_host_name/…/RMIdir/ServerDir EchoRMIServer java –Djava.rmi.server.codebase = http://client_host_name/…/RMIdir/ClientDir EchoRMIClient server_host_name server_host client_host JVM 1 RMIregistry RMIregistry RMI RMIClient Client RMI RMIServer Server server&client_host JVM 1 rmiregistry rmiregistry EchoRMIServer_stub EchoInterface JVM 3 JVM 2 JVM 2 JVM 3 EchoRMIServer_stub EchoInterface RMI RMIClient Client RMI RMIServer Server Message EchoRMIServer_stub Web Webserver server Message Web Webserver server EchoRMIServer_stub c:\…\ServerDir\ c:\…\ClientDir\ File System Reti di calcolatori, Java RMI - 33 Reti di calcolatori, Java RMI - 34 Ambienti separati e protetti Ogni JVM definisce ambienti di esecuzione differenziati e protetti per diverse parti, in particolare da remoto Si usano ClassLoader come separatori associati a SecurityManager per la specifica di protezione Download del codice Utilizzo di RMIClassLoader usato dal supporto RMI per caricare le classi FILE di POLICY Il file di policy a cui il Secutiry Manager fa riferimento per il controllo degli accessi contiene le autorizzazioni consentite Esempio: grant { permission java.net.SocketPermission "*:1024-65535", "connect, accept, resolve"; Esecuzione del codice Utilizzo di RMISecurityManager permission java.net.SocketPermission "*:80", "connect"; Istanziato all’interno dell’applicazione RMI, sia client che server, come controllore degli accessi, per bloccare quelli non autorizzati permission java.io.FilePermission "/home/lfoschini/*", "read"; if (System.getSecurityManager() == null) { System.setSecurityManager( new RMISecurityManager()); } Sia il server che il client devono essere lanciati specificando il file con le autorizzazioni che il security manager deve caricare Esempio java -Djava.security.policy = echo.policy EchoRMIServer java -Djava.security.policy = echo.policy EchoRMIClient remoteHost Reti di calcolatori, Java RMI - 35 }; Il primo permesso consente al client e al server di instaurare le connessioni necessarie all’interazione remota (host qualunque, porta utente qualunque, primitive consentite) Il secondo permesso consente di prelevare il codice da un server http (host qualunque, porta 80, connect) Il terzo permesso consente di accedere in lettura ad un qualsiasi direttorio del file system locale che sia sottodirettorio di “/home/lfoschini” (concessa solo lettura) Reti di calcolatori, Java RMI - 36 Bibliografia Sito della Sun: http://java.sun.com/products/jdk/rmi/ W.Grosso, “Java RMI”, Ed. O’Reilly (2002) E. Pitt, K. McNiff, “java.rmi, The Remote Methode Invocation Guide”, Ed. Addison Wesley (2001) R. Öberg, “Mastering RMI, Developing Enterprise Applications in Java and EJB”, Ed. Wiley (2001) Reti di calcolatori, Java RMI - 37