Mise en oeuvre d'un premier projet Jini

Le but du projet est de créer un serveur qui publie un service Jini, nommé Watch pour fournir l'heure de la machine du serveur à tous les clients qui en font la demande.
Le document comprend toutes les étapes depuis l'installation complète du framework jusqu'à la réalisation du projet.

Historique des versions

Version Date Evolutions
0.01 20/02/2010 1ere version à corriger par Mr mourlin.
0.02 28/02/2010 Quelques améliorations par charif.

Pré-requis.

Objectifs

1 - Créer un serveur qui publie un service Jini, nommé Watch pour fournir l'heure de la machine du serveur à tous les clients qui en font la demande.

2 - Faire que si le client le demande, le service peut se déplacer pour fournir l'heure de la machine du client. Au lieu que celle du serveur.

Projet sous Eclipse

Pour commencer, on crée un projet java dans le quel nous allons ajouter un package que nous allons nommer « watch » .cela fait, nous allons ajouter les 3 classes client, service et serveur dont nous avons besoin.

Le code que nous allons utiliser pour programmer nos 3 classes contiens un nombre de classes qui sont définies dans les librairies jini, nous devons donc ajouter les librairies .jar que nous pouvant trouver dans « %JINI_HOME%\lib » dans le « java build path » eclipse a fin de ne pas avoir d'erreur de compilation après les build automatique que fait eclipse

Il nous faut aussi crée un nouveau dossier que nous allons nommer « ressources » dans lequel nous allons crée un fichier texte que nous allons nommer « policy.all » et dont le contenu est le suivant :

grant {
permission java.security.AllPermission "", "";
};

Classe 1 : service

Nous allons commencer par crée le service , un service Jini est identifié par un ServiceID qu'on récupère automatiquement depuis le JoinManager (qui gère l'enregistrement du service une fois que ce dernier a été découvert) a traves un appel callback sur la méthode public void serviceIDNotify(ServiceID sID) définie dans l'interface ServiceIDListener. Un service Jini est aussi administrable, et pour cela nous allons hériter aussi de la classe Administrable. Enfin un service Jini se déplace dans le réseau donc nous allons hériter aussi de la classe Serializable.

Notre service fournira une methode public String getInfo() qui retourne un String qui contiens le nom de la machine sur lequel le code du service a été exécuté, concaténé à la date et l'heure courante.

Le code de la classe watch.WatchService :

package watch;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.Date;

import net.jini.admin.Administrable;
import net.jini.core.lookup.ServiceID;
import net.jini.lookup.ServiceIDListener;

public class WatchService implements ServiceIDListener, Serializable ,Administrable{

    private ServiceID sID;
    @Override
    public void serviceIDNotify(ServiceID sID) {
        this.sID = sID;
    }

    public String getInfo(){
        StringBuffer message = new StringBuffer();
        String hostName = "unknown";
        
         try {
            hostName= InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
        message.append(hostName);
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
        Date today = new Date();
        String value = sdf.format(today);
        message.append(" " + value);
        
        return message.toString();
    }

    @Override
    public Object getAdmin() throws RemoteException {
        return null;
    }
}

Classe 2 : serveur

Maintenant que notre service est prêt, nous avons besoin d'un serveur pour gérer ce service. Nous allons enregistrer le serveur sur le service Lookup, qui nous informera si le service a été découvert par un registrar . Nous allons pour cela hériter de l'interface DiscoveryListener, qui contiens la méthode public void discovered(DiscoveryEvent de) utilisée par le service Lookup pour nous informer qu'une découverte est en cours sur notre service. On profite de cet événement pour récupérer le registrar et publier notre service sur ce dernier.

Notre serveur hérite aussi de l'interface LeaseListener qui permet de gérer le Lease qui est la durée de validité d'un service. Pour l'instant nous allons spécifier que notre service n'expire jamais et nous allons détailler ce concept plus tard.

Le code de la classe watch.WatchServer :

package watch;

import java.io.IOException;
import java.rmi.RemoteException;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.LookupDiscovery ;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.entry.ServiceInfo;

public class WatchServer implements DiscoveryListener , LeaseListener {

    public static void main(String[] args) throws InterruptedException {
        WatchServer serveur = new WatchServer();
        Thread.sleep(Lease.FOREVER);
    }
    
    public WatchServer() {
        LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
            discover.addDiscoveryListener(this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void discarded(DiscoveryEvent arg0) {
        
    }
    
    @Override
    public void discovered(DiscoveryEvent de) {
        ServiceRegistrar[] registrar = de.getRegistrars();
        Object service = new WatchService();
        net.jini.core.entry.Entry[] entries = new net.jini.core.entry.Entry[] {new ServiceInfo()};
        ServiceItem unService = new ServiceItem(null,service,entries);
        LeaseRenewalManager lrm = new LeaseRenewalManager();
        try {
            ServiceRegistration register = registrar[0].register(unService, Lease.FOREVER);
            System.out.println("service publie..");
            lrm.renewUntil(register.getLease(), Lease.FOREVER, this);
        } catch (RemoteException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
    }

    @Override
    public void notify(LeaseRenewalEvent arg0) {
        
    }
}

Classe 3 : Client

Il est temps maintenant de créer un client qui utilise le service que nous venons d'implémenter.

Tout d'abord, nous allons nous récupérer le registrar depuis le service Lookup , ensuite nous allons lancer une recherche depuis le registrar sur tous les services qui correspondent à de certains critères (e.g : le nom de la class service). Une fois qu'on a pu récupérer une référence de l'objet service sur le client, nous pouvons appeler la méthode qui nous intéresse getInfo() et l'afficher dans la console

Le code de la classe watch.WatchServer :

package watch;

import java.io.IOException;
import java.net.MalformedURLException;

import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

public class WatchClient {

    public static void main(String[] args) throws InterruptedException {
        WatchClient client = new WatchClient();
        Thread.sleep(1000);
    }
    public WatchClient() {
        LookupLocator locator = null;
        ServiceRegistrar registrar = null;
        WatchService service = null;
        try {
            locator = new LookupLocator("jini://127.0.0.1/");
                registrar = locator.getRegistrar();
                Class[] criteria = new Class[]{
                WatchService.class};

                ServiceTemplate st = new ServiceTemplate(null,criteria,null);
                Object unObjet = registrar.lookup(st);
                if(unObjet !=null){
                    service = (WatchService) unObjet;
                    System.out.println(service.getInfo());
                }else{
                    System.out.println(\"Service introuvable\");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            
    }
}

Configurations Ant

Vue l'aspect distribuée de Jini, nous allons utiliser deux niveaux de fichier build. Un niveau général qu'on place dans la racine du projet, ce fichier comporte la configuration de l'environnement et comporte aussi une encapsulation des targets des fichiers projets auxquels il est lié. Un deuxième niveau c'est le niveau projet, qui se compose de fichiers décrivant les projets (client et serveurs dans notre cas)

Niveau global

Build.xml que nous devons placer à la racine du projet.

On note qu'il faut changer les propriétés httpd.classes et jini.home pour les adapter à votre system

<project name="Jini watch" default="usage" basedir=".">

<!-- CONFIGURABLE STUFF HERE -->

<property name="jini.home" value="C:Userscharifjini2_1"/>

<property name="localhost" value="127.0.0.1"/>

<!-- END CONFIGURABLE STUFF -->

<!-- Libraries -->

<property name="jini.jars"

value="${jini.home}/lib/jsk-platform.jar;${jini.home}/lib/jsk-lib.jar"/>

<path id="compile.classpath">

<pathelement path="${jini.jars}" />

<pathelement path="build" />

</path>

<!-- Directories -->

<property name="src" value="${basedir}src"/>

<property name="dist" value="${basedir}dist"/>

<property name="build" value="${basedir}build"/>

<property name="res" value="${basedir}/resources"/>

<property name="httpd.classes" value="C:\Program Files\EasyPHP6.0dev\www\classes\"/>

<!-- Show the usage options to the user -->

<target name="usage" >

<echo message=" compile"/>

<echo message=" dist"/>

<echo message=" build"/>

<echo message=" deploy"/>

<echo message=" clean"/>

<echo message=" run -DrunFile='...' [-Dconfig='...']"/>

<echo message=" usage"/>

</target>

<target name="all" depends="init,compile"/>

<!-- CLEAN -->

<target name="clean">

<!-- Delete our the ${build}, and ${dist} directory trees -->

<delete dir="${build}"/>

<delete dir="${dist}"/>

<!-- delete all ~ backup files -->

<delete>

<fileset dir="." defaultexcludes="false" includes="**/*~"/>

</delete>

<!-- delete all .bak backup files -->

<delete>

<fileset dir="." defaultexcludes="false" includes="**/*.bak"/>

</delete>

</target>

<target name="init">

<!-- Create the build directory structure used by compile N deploy -->

<mkdir dir="build"/>

<mkdir dir="dist"/>

</target>

<!-- call "compile" target in all build files in "antBuildFiles" dir -->

<target name="compile" depends="init">

<subant target="compile" inheritall="true">

<fileset dir="antBuildFiles"

includes="*.xml"/>

</subant>

</target>

<!-- call "dist" target in all build files in "antBuildFiles" dir -->

<target name="dist" depends="compile">

<subant target="dist" inheritall="true">

<fileset dir="antBuildFiles"

includes="*.xml"/>

</subant>

</target>

<!-- call "deploy" target in all build files in "antBuildFiles" dir -->

<target name="deploy" depends="dist">

    <subant target="deploy" inheritall="true">

    <fileset dir="antBuildFiles"

    includes="*.xml"

    />

    </subant>

    </target>

    <target name="build" depends="dist,compile"/>

    <!-- call "run" on antfile determined by "runFile" property -->

    <target name="run">

        <parallel>
            

        <ant

            antfile="antBuildFiles/watch.WatchServer.xml"

            target="run"/>

        <ant

            antfile="antBuildFiles/watch.WatchClient.xml"

            target="run"/>

            </parallel>

            </target>

    </project>

Niveau projet

Comme dit plut haut, les fichiers de niveau projet sont contenu dans un répertoire crée a la racine du projet et qui se nomme « antBuildFiles » .ces fichier doivent avoir le même nom que la main classe qui devra être exécuté. Ces fichier hérite de toutes les propriétés défini dans le fichier global designer par « ..\build.xml »

Dans le cadre de notre tutoriel nous allons avoir besoin de deux fichier au sein du répertoire « antBuildFiles » , un premier « watch.WatchServer.xml » qui sert a compiler le serveur et a mettre le .jar contenant les classe du service dans le répertoire «classes » au sein de serveur http de manière a qu'il soit accessible par http://localhost/classes/ et cela pour permettre au client d' accéder a ces classes. Un deuxième fichier que nous allons nommé « watch.WatchClient.xml » qui nous sert a compiler et a exécuter le client.

Source du fichier : Watch.WatchServer.xml

<!--

Project name must be the same as the filename which must

be the same as the main.class. Builds jar files with the

same name

-->


<project name="watch.WatchServer">



<!-- Inherits properties from ../build.xml:

jini.home

jini.jars

src

dist

build

httpd.classes

localhost

-->



<!-- files for this project -->

<!-- Source files for the server -->

<property name="src.files"

value="

watch/WatchServer.java,

    watch/WatchService.java

"/>



<!-- Class files to run the server -->

<property name="class.files"

value="

    watch/WatchServer.class,

    watch/WatchService.class

    "/>



<!-- Class files for the client to download -->

<property name="class.files.dl"

value="

    watch/WatchService.class

"/>



<!-- Uncomment if no class files downloaded to the client -->

    <!-- <property name="no-dl" value="true"/> -->



<!-- derived names - may be changed -->

<property name="jar.file"

value="${ant.project.name}.jar"/>

<property name="jar.file.dl"

value="${ant.project.name}-dl.jar"/>

<property name="main.class"

value="${ant.project.name}"/>

<property name="codebase"

value="http://${localhost}/classes/${jar.file.dl}"/>



<!-- targets -->

<target name="all" depends="compile"/>



<target name="compile">

    <javac destdir="${build}" srcdir="${src}"

     classpath="${jini.jars}"

includes="${src.files}">

</javac>

</target>



<target name="dist" depends="compile"

description="generate the distribution">

    <jar jarfile="${dist}/${jar.file}"

basedir="${build}"

     includes="${class.files}">

<manifest>

<attribute name="Main-Class" value="${ant.project.name}"/>

</manifest>

</jar>



<antcall target="dist-jar-dl"/>

</target>



<target name="dist-jar-dl" unless="no-dl">

    <jar jarfile="${dist}/${jar.file.dl}"

basedir="${build}"

     includes="${class.files.dl}"/>

</target>



<target name="build" depends="dist,compile"/>



<target name="run" depends="build,deploy">

    <java classname="${ant.project.name}"

fork="true"

     classpath="${jini.jars}:${dist}/${jar.file}">

<jvmarg value="-Djava.security.policy=${res}/policy.all"/>

<jvmarg value="-Djava.rmi.server.codebase=${codebase}"/>

</java>

</target>



<target name="deploy" depends="dist" unless="no-dl">

<copy file="${dist}/${jar.file.dl}"

todir="${httpd.classes}"/>

</target>

</project>

Source du fichier : Watch.WatchClient.xml

<!--

Project name must be the same as the filename which must

be the same as the main.class. Builds jar files with the

same name

-->

<project name="watch.WatchClient">


<!-- Inherits properties from ../build.xml:

jini.home

jini.jars

src

dist

build

httpd.classes

localhost

-->

<!-- files for this project -->

<!-- Source files for the client -->

<property name="src.files"

value="

watch/WatchClient.java

"/>

<!-- Class files to run the client -->

<property name="class.files"

value="

    watch/WatchClient.class,

    watch/WatchService.class

    "/>

<!-- Uncomment if no class files downloaded to the client -->

<!-- <property name="no-dl" value="true"/> -->

<!-- derived names - may be changed -->

<property name="jar.file"

value="${ant.project.name}.jar"/>

<property name="jar.file.dl"

value="${ant.project.name}-dl.jar"/>

<property name="main.class"

value="${ant.project.name}"/>

<property name="codebase"

value="http://${localhost}/classes/${jar.file.dl}"/>

<!-- targets -->

<target name="all" depends="compile"/>

<target name="compile">

    <javac destdir="${build}" srcdir="${src}"

     classpath="${jini.jars}"

includes="${src.files}">

</javac>

</target>

<target name="dist" depends="compile"

description="generate the distribution">

    <jar jarfile="${dist}/${jar.file}"

basedir="${build}"

     includes="${class.files}">

<manifest>

<attribute name="Main-Class" value="${ant.project.name}"/>

</manifest>

</jar>

</target>


<target name="build" depends="dist,compile"/>

<target name="run" depends="build">

    <java classname="${main.class}"

fork="true"

     classpath="${jini.jars}:${dist}/${jar.file}">

<jvmarg value="-Djava.security.policy=${res}/policy.all"/>

<jvmarg value="-Djava.rmi.server.codebase=${codebase}"/>

</java>

</target>

<target name="deploy" depends="dist" unless="no-dl">

</target>

</project>

Première exécution

Avant de commencer l'execution , nous allons copier les librairies contenu dans « %JINI_HOME%\lib-dl » dans le répertoire «classes » au sein de serveur http de manière a qu'il soit accessible par http://localhost/classes/

Nous allons lancer le programme %JINI_HOME%\installverify\support\lunchAll.exe ce qui lancera les principaux services indispensables a l'exécution Jini

Et pour lancer notre serveur et notre client , il nous faut exécuter le target run du fichier global build.xml

Ce document intitulé « Mise en oeuvre d'un premier projet Jini » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous