Esercizio1: Variante dell'esempio client server "Hello World"del tutorial della Sun.


Costruiremo un semplicissimo server remoto HelloServer. Tale server ha un solo metodo remoto
sayHello()
  la cui invocazione da parte di un client ritorna una frase. In questo esercizio
viene data particolare enfasi al fatto che il client carica dinamicamente la classe stub relativa
all'oggetto remoto (e non la copia brutalemente dal server) in maniera del tutto trasparente.

La programmazione distribuita client/server prevede che il codice del server e quella del client
vadano eseguite su due macchine diverse. In questo esercizio lavorerete in coppia su due macchine
differenti: uno di voi si occupera del server e l'altro del client.

Seguiamo allora i seguenti passi:

1) Sia il client che il server devono creare nella rispettiva home-directory una directory javarmi
    dove terremo tutti i nostri esercizi in Java RMI. All'interno di tale directory entrambi creino una directory
    hello che fungera'da package ed in cui mettere tutto il codice relativo all'esempio dell'esercizio
    corrente.

2) Poiche' la classe stub (prodotte con rmic) deve essere caricata dinamicamente dal client, la
    metteremo in un'area di memoria del server che possa essere acceduta anche dal client.
    Ad esempio la mettiamo nella directory public_html del server che puo' essere acceduta dal
    client attraverso un protocollo http:// oppure ftp://.   Per comodita' creiamo all'interno di
    ~/public_html una directory common, accertandoci di dare a tutti i diritti di lettura ed
    esecuzione della directory.

3) All'interno della directory del ~/javarmi/ del client e del server inseriamo il file di policy:

grant {
            // Allow everything for now
            permission java.security.AllPermission;
    };


     per fornire al client i permessi necessari per avere l'accesso dinamico alle classi stub.
     Il file di policy, in generale, serve per deteminare i permessi delle applicazioni distribuite.
     Per adesso, per semplicita', il file di  policy dato sopra fornisce tutti i diritti possibili.  
     In seguito, ovviamente, saremo molto accurati nel definire il file di policy, includendo
     solo la quantita' minima di permessi necessatri all'applicazione per il suo funzionamento.


4) Nella directory ~/javarmi/hello/ del server mettiamo i due codici sorgenti java:

     4.1) Hello.java                                           (vale a dire l'interfaccia remota)

      package hello;
      import java.rmi.Remote;
      import java.rmi.RemoteException;

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


 
    4.2) HelloImpl.java                                    (vale a dire l'implementazione del server remoto)

    package hello;
    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() throws RemoteException {
            super();
        }

        public String sayHello() {
            return  "Hello World:  questa frase proviene dall'invocazione del metodo sayHello
                       del server remoto";
        }

        public static void main(String args[]) {

            // Crea ed installa un security manager.
            if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
            }
            try {
                Hello obj = new HelloImpl();                          
                // Creo un'istanza del server remoto

                Naming.rebind("rmi://localhost:2378/HelloServer", obj);  

                // Il registro RMI viene lanciata dal localhost (per motivi di sicurezza non puo'
                //essere lanciato da nessun altro) alla porta 2378.
                //Se il numero di porta non fosse stato indicato, di default sarebbe
                // stato lanciato, per default, alla porta 1099.
               
// Nel registro RMI il nome "HelloServer" viene associato allo stub dell'istanza dell'oggetto remoto creato.

                System.out.println("L'oggetto remoto HelloServer e' stato registrato nel registro RMI");
               } catch (Exception e) {
                System.out.println("HelloImpl err: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }



5) Compiliamo i sorgenti del server.

     cd ~/javarmi/hello                           (andiamo nella directory dove risiedono i sorgenti del server)
     javac Hello.java HelloImpl.java     (compiliamo l'interfaccia remota e l'implementazione)


6) Generazione di stub e skeleton.

     Generiamo adesso le classi stub e skeleton. I files di stub e skeleton vengono creati attraverso il tool rmic:

      cd  ~/javarmi/    (NOTA che ci mettiamo in javarmi e non in hello, per via dei packages)
      rmic -d  ~/public_html/common/ hello.HelloImpl

      tale fase di compilazione da' luogo alla produzione delle classi   HelloImpl_Skel.class  e
      HelloImpl_Stub.class
che,  per via della clausola -d, vengono messe nella directory
      ~/public_html/common/ all'interno di un package hello creato da rmic stesso.
     Verificate che cio sia veramente accaduto.

7) Lanciamo il registro RMI sulla macchina del server sulla porta utilizzata dall'oggetto remoto
    (in questo esempio stiamo usando la 2378, ma potrebbe essere benissimo un'altra).  Il comando
    per lanciare il registro RMI (che e' esso stesso un oggetto remoto) e':

    rmiregistry 2378 &

    per lanciare il registro alla porta 2378. Se la porta viene omessa, il registro viene lanciato per
    default alla porta 1099. Comunque, lanciare il registro RMI  e' un'operazione abbastanza
    delicata che e se non effettuata correttamente e' fonte di errori di non facile interpretazione.
    Teniamo presente alcune cose.

     7.1) IMPORTANTE: Il registro RMI deve poter accedere all'interfaccia remota per poter 
            inserire correttamente lo stub al suo interno. Cio' vuol dire che quando viene lanciato
            deve poter accedere alla classe Hello.class, o direttamente oppure settando appropriatamente
            la variabile CLASSPATH.

     7.2) IMPORTANTE: In caso di caricamento dinamico degli stub da parte dei clients (come avviene
            nella maggior parte dei casi) il registro RMI non deve poter accedere alle classi stub generate con
            rmic, altrimenti il meccanismo di caricamento dinamico degli stub da parte del client non
            funziona correttamente.
     
    Di conseguenza un modo per far funzionare correttamente il vostro server e' quello di posizionarsi
    nella directory javarmi e digitare:

       unnset CLASSPATH      (oppure unsetenv CLASSPATH - per resettare la variabile CLASSPATH).
       rmiregistry 2378 &         (per lanciare il registro RMI alla porta 2378) .


    Notate che poiche' programmate con i packages,  per vedere la classe Hello.class dovete lanciare il registro in javarmi e non
    nel package hello.  Ovviamente potete lanciare il registro in altri luoghi settando opportunamente la variabile CLASSPATH in
    modo che si abbia accesso all'interfaccia remota e non agli stub.


8)  Finalmente lanciamo il server dalla directory javarmi. Digitiamo i seguenti comandi.

       cd ~javarmi/
       java -classpath  :~/public_html/common/
                -Djava.rmi.server.codebase=file:///........./public_html/common/
               
-Djava.security.policy=/....../javarmi/policy
                hello.HelloImpl

      Discutiamo le vari clausole.
       (i) La -classpath dice all'interprete dove trovare i file *.class necessari per l'esecuzione. Con i due punti
            indichiamo che oltre a considerare la directory corrente (cioe' ~/javarmi/) dobbiamo aggiungere la
            directory common in cui si trovano gli stub che devono essere acceduti dall'implementazione per
            effettuare la bind (o rebind) nel registro RMI.
       (ii) La -Djava.rmi.server.codebase=file:///......./public_html//common/ definisce il protocollo attraverso
             cui il client puo' caricare dinamicamente la classe HelloImpl_Stub.class. In questo caso usiamo il
             protocollo "file:// "che e' utilizzabile solo se client e server hanno accesso allo stesso file
             system (come nel vostro caso, poiche' tutte le macchine del laboratorio condividono il medesimo file
             system).  Al posto di ..... dovete mettere il path del vostro file system per arrivare a /public_html/common/ .
             Un'altra possibilita' era quella di usare il protocollo "http://" utilizzando un webserver.
        (iii) La -Djava.security.policy=/......./javarmi/policy e' la clausola di policy
              attraverso cui si specificano i permessi di cui necessita il codice per l'esecuzione.

Se tutto va correttamente,  sul vostro schermo apparira' la scritta:

"L'oggetto remoto HelloServer e' stato registrato nel registro RMI"

La quale vi comunica che l'oggetto remoto,che abbiamo chiamato HelloServer, e' stato registrato nel registro RMI.
Piu' precisamente che la stringa HelloServer e' stata associata ad una referenza  remota all'oggetto.



9) Passiamo al codice del Client. Cioe' vediamo quello che deve essere fatto presso la macchina client.
    Nella directory ~/javarmi/hello mettiamo i due seguenti codici Hello.java (l'interfaccia remota esattamente uguale
    a quella del server) e il codice del Client HelloClient.java che riportiamo sotto.

    package hello;

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

    public class HelloClient {

        public static void main(String args[]) {

             // "obj" is the identifier that we'll use to refer
             // to the remote object that implements the "Hello"
             // interface
             Hello obj = null;
             String str = "";

             // Create and install a security manager
             if (System.getSecurityManager() == null) {
                 System.setSecurityManager(new RMISecurityManager());

             try {
                 String name = "//" + args[0]  + "/HelloServer";
                 System.out.println("1) Il client sta facendo una lookup per ottenere una referenza all'oggetto remoto all'indirizzo: " + name);
                 obj = (Hello)Naming.lookup(name);
                 System.out.println("2) La lookup ha avuto successo");
                 str = obj.sayHello();
                 System.out.println("3) Il Client ha invocato il metodo sayHello dell'oggetto remoto con successo ");
                 System.out.println(str);
                 System.out.println("4) Il Client ha stampato il risultato dell'invocazione del metodo remoto. Fine.");
             } catch (Exception e) {
                 System.out.println("HelloClient exception: " +
                      e.getMessage());
                 e.printStackTrace();
             }
        }
    }
    }

Notate come siano stati aggiunti nel codice delle linee di stampa per agevolare eventuale debug e farvi capire,
in caso di errore, dove il programma si e' arrestato.


10) Compiliamo il codice del Client.

        cd   ~/javarmi/hello/
        javac  Hello.java HelloClient.java


        Nota che le classi Hello.class e HelloClient.class vengono create localmente nella directory 'hello' del client.
        Infatti sono delle classi che il client non deve condividere con nessuno. Similmente, nel server, abbiamo messo
        le classi Hello.class ed HelloImpl.class nel package hello e non in common perche' non devono essere conosciute
        dal client.


11) Lanciamo il programma del Client.
       

        Supponendo che il server sia stato lanciato sulla macchina "delta05" di seguito riportiamo i comandi per
        lanciare l'applicazione del client client:


        cd  ~/javarmi/
        java  -Djava.security.policy=/home/massimo/javarmi/policy
                 hello.HelloClient
                 delta05:2378


       Percio' andiamo nella directory client e lanciamo il codice del client.
Se tutto va come previsto al client

1) Il client sta facendo una lookup per ottenere una referenza all'oggetto remoto all'indirizzo: //...../HelloServer
2) La lookup ha avuto successo
3) Il Client ha invocato il metodo sayHello dell'oggetto remoto con successo
Hello World:  questa frase proviene dall'invocazione del metodo sayHello del server remoto
4) Il Client ha stampato il risultato dell'invocazione del metodo remoto. Fine.

     IMPORTANTE: Le uniche informazioni in possesso del client per invocare un metodo dell'oggetto remoto sono:

      Niente altro!!!!

      IMPORTANTE:  Il client non ha alcuna idea di dove sia l'area condivisa usata dal server per tenere
      il codice da caricare dinamicamente (cioe' gli stub) durante la sua esecuzione. Questa mobilita' di codice server-client
      e' completamente trasparente al client. E' il protocollo RMI che si prende cura di fornire tale informazione al client.

     IMPORTANTISSIMO: Quando il Server non vi servira' piu' non dimenticate di uccidere il processo
     del registro RMI che gira in background. 


12) ESERCIZI/ESPERIMENTI:
   
a) fermate il server ed uccidete il registro RMI. Provate allora a:
    a1) Lanciare il registro RMI da una locazione dove non vede l'interfaccia remota Hello.class e gli
          stub. In tal caso quando provate a lanciare il server, l'interporete vi dira' che il registro RMI
          non riesce a trovare hello.Hello.class.
    a2) Uccidete server e registro RMI. Copiate la classe stub che e' in ~/public_html/common/hello/
          dentro ~/javarmi/hello. E lanciate adesso il registro RMI dalla directory javarmi. Quando lancerete
          il server non noterete alcun problema. Ma quando lancerete il client, l'interprete vi segnala problemi
          nell'accedere allo stub. Tali problemi derivano dal fatto che quando avete lanciato il registro RMI
          questi aveva accesso llo stub e lo ha caricati prima di quando doveva.  Rivedremo tale problema
          piu' in dettaglio.
    a3) Provate adesso a copiare la classe stub, dall'area common del server, al package hello del client.
          Lanciate nuovamente il client e noterete che tutto funziona magnificamente. Solamente che facendo
          cosi' avete 'barato'perche' il client non carica dinamicamente dall'area condivisa common ma utilizza
          lo stub che trova localemente. E questa non e' programmazione distribuita!

b) Provate a lanciare il client senza indicare la porta del registro RMI.

c)  Provate a lanciare il Client senza la clausola di policy.

d)  Provate a lanciare il Server senza la clausola di policy.

e)  Provate a togliere dal Server il SecurityManager ed a lanciarlo senza clausola di policy.

f) provate a lanciare il server con un codebase sbagliato, ovvero che non sia in grado di rintracciare
    l'area common col package hello in cui vi sono gli stub. Un effetto simile lo si puo' ottenere
    lasciando il codebase invariato e togliendo i diritti di lettura nella directory common.