Esercizio 2.1: Gestione del Registro RMI attraverso la classe Naming. 


L'esercizio consiste nel registrare cinque oggetti remoti su due registri RMI che girano
su porte  differenti. Dopodiche', un applicazione ListRegistry si preoccupera' di
stampare il contenuto dei due registry. Iniziamo col vedere le operazioni da fare nel server.

1) Nella directory  ~/javarmi del server create una directory registry e ricopiate al suo interno l'interfaccia
remota Hello.java simile a quella vista nell'esercizio precedente in cui cambiate il  nome del package:

package registry;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String sayHello() throws RemoteException;
}


2) Nella stessa directory registry ricopiate una variante dell'implementazione HelloImpl.java
dell'esercizio precedente che si occupa di creare 5 oggetti remoti che vengono registrati su due registri
RMI differenti; il primo registro si suppone lanciato alla porta 2345 mentre il secondo alla porta 2333
(usate altre porte se lo preferite). Nel primo registro vengono registrati 2 oggetti mentre nel secondo
registro vengono registrati i rimanenti 3 oggetti. Nota che gli oggetti vengono esportati su porte prestabilite
dall'utente, nel nostro caso scegliamo le porte, la 1111, 2222, 3333, 4444, 5555. Cio' e fatto specificando
nel costruttore dell'oggetto UnicastRemoteObject la porta sulla quale si vuole che venga esportato l'oggetto.

package registry;

    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.RMISecurityManager;
    import java.rmi.server.UnicastRemoteObject;

    public class HelloImpl extends UnicastRemoteObject
        implements Hello {

        public HelloImpl(int port) throws RemoteException {
            super(port);  // l'oggetto viene esportato alla porta 'port'.
        }

        public String sayHello() {
            return  "Hello World: I am your remote object!";
        }

        public static void main(String args[]) {

            // Crea e installa un security manager
            if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
            }
            try {
                Hello obj1 = new HelloImpl(1111);
                // Faccio il bind di questo server sul registro RMI alla porta 2345.
                //Il server remoto lo chiamo "HelloServer1"
                System.out.println("Esportato HelloServer1 alla porta 1111: "+ obj1);
                Naming.rebind("//:2345/HelloServer1",obj1);
                System.out.println("UnicastRemoteObject HelloServer1 registrato nel registro RMI alla porta 2345");
                Hello obj2 = new HelloImpl(2222); 
                // Faccio il bind di questo server sul registro RMI alla porta 2345.
                //Il server remoto lo chiamo "HelloServer2"
                System.out.println("Esportato HelloServer2 alla porta 2222: "+ obj2);
                Naming.rebind("//:2345/HelloServer2",obj2);
                System.out.println("UnicastRemoteObject HelloServer2 registrato nel  registro RMI alla porta 2345");
                Hello obj3 = new HelloImpl(3333); 
                //  stavolta il server creato lo registriamo nel registro RMI alla porta 2333
                System.out.println("Esportato HelloServer3 alla porta 3333: "+ obj3);
                Naming.rebind("//:2333/HelloServer3",obj3);
                System.out.println("UnicastRemoteObject HelloServer3 registrato nel  registro RMI alla porta 2333");
                Hello obj4 = new HelloImpl(4444); 
                System.out.println("Esportato HelloServer4 alla porta 4444: "+ obj4);
                Naming.rebind("//:2333/HelloServer4",obj4);
                System.out.println("UnicastRemoteObject HelloServer4 registrato nel  registro RMI alla porta 2333");  
                HelloImpl obj5 = new HelloImpl(5555);
                 // NOTA: qui ho dichiarato obj5 di tipo HelloImpl e non Hello. Cosa cambia?
                System.out.println("Esportato HelloServer5 alla porta 5555: "+ obj5);
                Naming.rebind("//:2333/HelloServer5",obj5); 
                System.out.println("UnicastRemoteObject HelloServer5 registrato nel  registro RMI alla porta 2333");   
                }
                catch (Exception e) {
                System.out.println("HelloImpl err: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }


3) Nella directory registry aggiungete il file di policy utilizzato nell'esercizio precedente.


4) Compilate i sorgenti nella directory registry col comandojavac Hello.java HelloImpl.java

5) Create le referenze remote (gli stub) che saranno necessari quando andremo a registrare
gli oggetti nei registri. Percio' andate nella directory javarmi, ed eseguite il comando:
    rmic -d  ~/public_html/common/ registry.HelloImpl

6) A questo punto lanciate i due registry RMI. Prima di lanciare i registri usate le accortezze
spiegate al punto 7 dell'esercizio 1.

     rmiregistry 2345 &
     rmiregistry 2333 &


7) A questo punto siete pronti a lanciare l'implementazione. Andate in javarmi ed eseguite il seguente comando:
       
       
java -classpath  :/...../public_html/common/
                -Djava.rmi.server.codebase=file:///........./public_html/common/
                -Djava.security.policy=policy
                registry.HelloImpl

dove al posto di ...... dovete mettere il path della vostra home, oppure potete usare un indirizzo http.
Se tutto va come desiderato, sul vostro schermo apparira' una videata del seguente tipo:

Esportato HelloServer1 alla porta 1111: registry.HelloImpl[RemoteStub [ref: [endpoint:[157.27.252.10:1111](local),objID:[0]]]]
UnicastRemoteObject HelloServer1 registrato nel registro RMI alla porta 2345
Esportato HelloServer2 alla porta 2222: registry.HelloImpl[RemoteStub [ref: [endpoint:[157.27.252.10:2222](local),objID:[1]]]]
UnicastRemoteObject HelloServer2 registrato nel  registro RMI alla porta 2345
Esportato HelloServer3 alla porta 3333: registry.HelloImpl[RemoteStub [ref: [endpoint:[157.27.252.10:3333](local),objID:[2]]]]
UnicastRemoteObject HelloServer3 registrato nel  registro RMI alla porta 2333
Esportato HelloServer4 alla porta 4444: registry.HelloImpl[RemoteStub [ref: [endpoint:[157.27.252.10:4444](local),objID:[3]]]]
UnicastRemoteObject HelloServer4 registrato nel  registro RMI alla porta 2333
Esportato HelloServer5 alla porta 5555: registry.HelloImpl[RemoteStub [ref: [endpoint:[157.27.252.10:5555](local),objID:[4]]]]
UnicastRemoteObject HelloServer5 registrato nel  registro RMI alla porta 2333


Che vi segnala che i primi due oggetti (HelloServer1 ed HelloServer2) sono stati registrati nel registro
alla porta 2345, mentre gli altri tre nel registro alla porta 2333.  Notate che stampando le referenze ai
server remot appare l'IP dell'host (in questo caso quello di profs) che li ha esportati, la porta a cui sono esportati,
il fatto che sono referenze locali al server remoto, ed infine l'identificativo del server per il sistema RMI.

8) Adesso scriviamo un'applicazione sulla macchina client che usando la classe Naming legga
il contenuto di questi due registry. Anche nel client creiamo una directory registry
sotto javarmi. Aggiungiamo in tale directory l'interfaccia remota Hello.java ed il file di policy.

Nella directory registry copiamo anche il programma ListRegistry.java con il seguente codice:

package registry;

import java.rmi.*;
import java.util.*;

// List the names and objects bound in RMI Registries.
public class ListRegistry {
   public static void main(String[] args) {
   // Crea e installa un security manager
   if (System.getSecurityManager() == null) {
       System.setSecurityManager(new RMISecurityManager());
   }
   for (int i = 0; i < args.length; i++) {
      try {
        String[] list = Naming.list(args[i]);
        System.out.println("Contenuto del registro RMI presso "+args[i]+ " ("+list.length+" entries)");
        for (int j = 0; j < list.length; j++) {
        Remote remote = Naming.lookup(list[j]);
        // NOTATE: che a differenza dell'esercizio precedente non faccio il cast sull'interfaccia remota Hello.
         // remember that the lookup method return a remote reference, that is a stub.
          System.out.println((j+1)+".\tname="+list[j]+"\n\tremote="+remote);
        }
     }
     catch (java.net.MalformedURLException e) {
     System.err.println(e);
     }
     catch (NotBoundException e) {
     // name vanished between list and lookup - ignore
     }
     catch (RemoteException e) {
     System.err.println(e);
     }
  }
 }
}


9) Osservando il programma capirete che esso ricevera' in ingresso un certo numero di indirizzi rmi dove trovare dei registri.
Tali URL sono memorizzati in args che e' un array. Dopodiche', per ciascun elemento di args (cioe' per ciascun registro)
esegue una operazione Naming.list per ottenere un array dei nomi di oggetti che sono registrati nel registro, tale array
viene memorizzato nella variabile list. Per ciascuno di questi oggetti viene fatta una lookup. L'operazione di lookup  ritorna lo stub,
la referenza remota,  all'oggetto in questione. Tale stub viene allora stampato insieme al nome dell'oggetto. Il risultato
finale sara' una videata in cui per ogni registro viene riportato il suo contenuto: cioe' la coppia (nome oggetto, referenza remota).

10) Compilate ListRegistry.java andando in registry e digitando javac Listregistry.java.

11) A questo punto potete lanciare il bytecode di ListRegistry andando in javarmi digitando:

       java -Djava.security.policy=registry/policy registry.ListRegistry //remotehost:2345 //remotehost:2333

       (dove remotehost e' l'host dove sono stati lanciati i registri).

12) Il risultato di tale applicazione dovrebbe essere una videata del seguente tipo:

Contenuto del registro RMI presso //profs.sci.univr.it:2345 (2 entries)
1.      nome=rmi://profs.sci.univr.it:2345/HelloServer1
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:2222](remote),objID:[31f71a:fc0857d64d:-8000, 0]]]]
2.      nome=rmi://profs.sci.univr.it:2345/HelloServer2
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:3333](remote),objID:[31f71a:fc0857d64d:-8000, 1]]]]
Contents of registry at //profs.sci.univr.it:2333 (3 entries)
1.      nome=rmi://profs.sci.univr.it:2333/HelloServer3
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:4444](remote),objID:[31f71a:fc0857d64d:-8000, 2]]]]
2.      nome=rmi://profs.sci.univr.it:2333/HelloServer4
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:5555](remote),objID:[31f71a:fc0857d64d:-8000, 3]]]]
3.      nome=rmi://profs.sci.univr.it:2333/HelloServer5
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:6666](remote),objID:[31f71a:fc0857d64d:-8000, 4]]]]

Analizzate questa videata. Per ciascun registro viene stampato il suo contenuto. Ad esempio, il registro alla porta 2345
ha due entries. La prima e' costituita da un oggetto che sia chiama HelloServer1. Guardando al suo stub notiamo che il
server remoto a cui si riferisce e' in esecuzione all'indirizzo IP 257.27.252.10 (in questo caso quello di profs) alla porta
2222. Inoltre, il suo identificativo RMI e' [31f71a:fc0857d64d:-8000, 0].  Notate anche che vi viene segnalato il fatto
che sono referenze remote.

IMPORTANTE:  Come avete potuto notare la classe client non nomina l'interfaccia Hello. Questo perche' la lookup
e' fatta in maniera generica aspettandosi solamente un oggetto di tipo Remote (la superclasse delle interfacce
remote). Ne consegue che potreste compilare il codice del client senza possedere il codice dell'interfaccia Hello.java.
Pero' a tempo di esecuzione, quando si tenta di fare una lookup, il client ha bisogno del bytecode dell'interfaccia
remota (ovvero Hello.class). Tale bytecode (cioe' Hello.class), non deve necessariamente
essere disponibile presso il client, ma potrebbe anche essere messo a disposizione nell'area comune dal server,
ed essere caricato dinamicamente dal clientattraverso il codebase.
Provatelo. Cioe' eliminate la classe Hello.class dal client e spostate la classe Hello.class del server
nell'area condivisa usata dal codebase. Dopodiche' compilate il client e rilanciatelo.

Esercizio 2.2.  Gestione del registro RMI attraverso la classe Registry. 

1) In questo esercizio invece di usare la classe Naming userete l'interfaccia Registry per creare una videata
simile a quella vista sopra. Sostituiremo ListRegistry.java con un altro sorgente java che fa uso di Registry.
Chiameremo l'applicazione ListRegistry2.java. Il funzionamento del programma e' simile a ListRegistry.
Notate che il server e' sempre lo stesso ed i registry vengono creati con rmiregistry e la bind viene
fatta usando la classe naming.  Vediamo invece che il nuovo codice del client usa l'interfaccia Registry.

package registry;

import java.rmi.*;
import java.rmi.registry.*;
import java.util.*;

public class ListRegistry2 {
  public static void main(String[] args){
    if (System.getSecurityManager() == null) {
        System.setSecurityManager(new RMISecurityManager());
    }
    for (int i = 0; i < args.length; i++) {
      try {
          String host = args[i];
          int  port = Registry.REGISTRY_PORT;
          if (i+1 < args.length) {
             try {
                 port = Integer.parseInt(args[i+1]);
                 i++;
             }
             catch (NumberFormatException e){
               // next arg isn't a port number
             }
          }
          Registry registry = LocateRegistry.getRegistry(host,port);
          // if the try construct failed we have in the variable port
          //the standard port number 1099 from Registry.REGISTRY_PORT
          System.out.println("Contenuto del registro RMI presso "+host+":"+port);
          String[] list = registry.list();
          for (int j = 0; j < list.length; j++) {
            Remote remote = registry.lookup(list[j]);
            // remember that the lookup method return a remote reference, that is a stub.
            System.out.println((j+1)+".\tnome="+list[j]+"\n\tstub="+remote);
      }
      }
      catch (Exception e) {
         e.printStackTrace();
      } 
    }
    // A questo punto cerchiamo di ottenere uno stub ad un registro alla porta 1250
    // Dove in realta' non vi e' nessun registro. Allora noterete che la getRegistry
    // ritorna normalmente uno stub ipotetico di un server che non esiste.
    // Possiamo pure stampare tale stub. Ma appena tentiamo di usarlo ecco che
    // viene fuori un'eccezione remota.
    try {
        Registry reg = LocateRegistry.getRegistry(".....remotehost....",1250);  
        System.out.println();
        System.out.println("Stub di defalt del registro RMI alla porta 1250: \n"  + reg);
        System.out.println("Cerco di fare una list sul registro 1250 non ancora creato:");
        String[] lista = reg.list();
    }
    catch (Exception e) {
         e.printStackTrace();
    }
  }
}



2)Notate come la variabile port assuma inizialmente ad ogni iterazione  il valore Registry.REGISTRY_PORT
 che e' il valore 1099 di default. Tale valore servira' quando il programma viene invocato con un nome di host
 ma senza fornire la porta. Gli argomenti forniti al programma saranno della forma localhost 2345 localhost localhost 2333.
Cioe' un'aternarsi di stringhe che il programma deve riconoscere se si riferiscono ad host oppure a porte.
Se il numero di porta e' omesso viene utilizzata quella standard.


3) Compiliamo questo nuovo sorgente digitando javac ListRegistry2.java.

4) Per rendere le cose piu interessanti lanciamo presso il server un altro registro alla porta di default (1099)
con il comando rmiregistry &.

5) A questo punto ritorniamo nella directory javarmi e lanciamo il nuovo codice con

java -Djava.security.policy=policy registry.ListRegistry2 remotehost 2345 remotehost remotehost 2333


Notate che in questo modo, il programma chiede informazioni su ben tre registri. Il primo alla porta 2345, il secondo
 alla porta di default (visto che il numero di porta e' omesso) e il terzo alla porta 2333.  Notate infine che il programma
ricava uno stub ad un registro inesistente alla porta 1250. L'operazione di get funziona senza problemi, ma quando
si tenta di accedere il registro per fare una list abbiamo un'eccezione remota.

L'invocazione di ListRegistry2 presso il client produce una schermata del seguente tipo.


Contenuto del registro RMI presso profs.sci.univr.it:2345
1.      nome=HelloServer1
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:2222](remote),objID:[31f71a:fc088ea76d:-8000, 0]]]]
2.      nome=HelloServer2
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:3333](remote),objID:[31f71a:fc088ea76d:-8000, 1]]]]
Contenuto del registro a profs.sci.univr.it:1099
Contenuto del registro a profs.sci.univr.it:2333
1.      nome=HelloServer3
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:4444](remote),objID:[31f71a:fc088ea76d:-8000, 2]]]]
2.      nome=HelloServer4
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:5555](remote),objID:[31f71a:fc088ea76d:-8000, 3]]]]
3.      nome=HelloServer5
         stub=registry.HelloImpl_Stub[RemoteStub [ref: [endpoint:[157.27.252.10:6666](remote),objID:[31f71a:fc088ea76d:-8000, 4]]]]

+ una serie di eccezioni dovute all'accesso ad un registro non esistente sulla porta 1250.

Anche qui studiate attentamente il codice per capire esattamente come venga creata tale schermata.
Notate che la referenza remota di ciascun oggetto sostanzialemente contiene informazioni su: (i) l'host dove
 l'oggetto remoto e' in esecuzione; (ii) la porta in cui l'oggetto (e non il registro) e' in ascolto; (iii) l
'identificatore di oggetto interno al sistema RMI.



Esercizio 2.3. 

In questo esercizio riscriveremo un nuovo server che si cura di lanciare un registro RMI da programma
attraverso l'interfaccia Registry. Il codice del nuovo server HelloImpl2.java  e':

package registry;

    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.RMISecurityManager;
    import java.rmi.server.UnicastRemoteObject;
    import java.rmi.registry.*;
    import java.util.*;

    public class HelloImpl2 extends UnicastRemoteObject
        implements Hello {

        public HelloImpl2(int port) throws RemoteException {
            super(port);  // l'oggetto viene esportato alla porta 'port'.
        }

        public String sayHello() {
            return  "Hello World: I am your remote object!";
        }

        public static void main(String args[]) {

            // Crea e installa un security manager
            if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
            }     
            try {
                // Creiamo il registro RMI da programma e lo mettiamo sulla porta 2345
                Registry reg = LocateRegistry.createRegistry(2345);
                System.out.println("Creato il registro RMI alla porta 2345: \n" + reg);
                Hello obj1 = new HelloImpl2(1111);
                //Il server remoto lo chiamo "HelloServer1"
                System.out.println("Esportato HelloServer1 alla porta 1111: \n"+ obj1);
                // Faccio il bind di questo server sul registro RMI alla porta 2345.
                reg.rebind("HelloServer1",obj1);
                System.out.println("UnicastRemoteObject HelloServer1 registrato nel registro RMI alla porta 2345");
                Hello obj2 = new HelloImpl2(2222); 
                // Faccio il bind di questo server sul registro RMI alla porta 2345.
                //Il server remoto lo chiamo "HelloServer2"
               System.out.println("Esportato HelloServer2 alla porta 2222: \n"+ obj2);
                reg.rebind("HelloServer2",obj2);
                System.out.println("UnicastRemoteObject HelloServer2 registrato nel  registro RMI alla porta 2345");
                Hello obj3 = new HelloImpl2(3333); 
                System.out.println("Esportato HelloServer3 alla porta 3333: \n"+ obj3);
                reg.rebind("HelloServer3",obj3);
                System.out.println("UnicastRemoteObject HelloServer3 registrato nel  registro RMI alla porta 2345"); 
               } catch (Exception e) {
                  System.out.println("HelloImpl2 err: " + e.getMessage());
                 e.printStackTrace();
                 }
        }
    }


Lanciate il server come prima ma senza lanciare il registro prima (accertatevi di aver ucciso i registri usati precedentemente).
Notate con attenzione la videata prodotta ed in particolare le referenze remote. Infine lanciate su un client la ListRegistry2
 per interrogare il registro.

Provate a vedere cosa succede nel caso in cui tentiate di creare piu' di un registro RMI all'interno del medesimo codice server.

IMPORTANTE:  fate attenzione che i metodi bind (cosi' come rebind e unbind) della classe Naming hanno
una sostanziale differenza rispetto ai corrispondenti metodi della classe Registry.  Infatti con la classe Naming
il server viene individuato attraverso un URL della forma //remotehost:port/Server dove remotehost e port sono
opzionali. Invece con la classe Registry il server e' individuato da una stringa in cui non ha senso indicare una
porta. Piu' precisamente se avessi scritto: 

reg.rebind("//:2333/HelloServer3",obj3);

cio' sarebbe voluto dire che faccio la bind di obj3 nell registro RMI creato con Registry (che nel nostro esercizio sta alla porta 2345)
e l'oggetto lo chiamo  "//:2333/HelloServer3",  cioe' 2333 non viene intesa come porta ma come parte del nome dell'oggetto!!!

Esercizio 2.4. 


Adesso potreste provare a rifare l'esercizio 2.1 in maniera distribuita. Cioe', ad esempio, 
potreste riunirvi in gruppi di tre persone. Due fanno i server e lanciano i loro registri in cui registrano degli oggetti.
Il terzo di voi fa il client e cerchera' di eseguire la ListRegistry per ottenere il contenuto dei registri sulle altre due macchine.


Esercizio 2.5. 


Ricordate che il metodo Naming.bind (o Naming.rebind)  puo' essere eseguito correttamente solo se
il registro e l'applicazione che fa il binding girano sullo stesso host (questo per motivi di sicurezza).
E' pero' possibile che l'applicazione esegua il bind di un oggetto remoto che e' stato esportato da un altro
server. Di tale oggetto remoto l'applicazione (che esegue il binding) e' ovviamente in possesso solo di
una referenza remota.

Scrivere allora una variante del server  base HelloImpl che si occupa di registrare nel registro RMI
un oggetto remoto esportato  da un'altro server.