TP nº1: Découverte d'OSGi

Étape nº1: Installation de Knopflerfish

L'installation des Knopflerfish est assez some. Connectez votre navigateur Web sur http://www.knopflerfish.org et allez à la page de téléchargement. Je recommande de télécharger la version complète (environ 7 Mo, y compris toutes les sources et la documentation). Nous supposerons aussi que vous avez accès à un IDE tels que Eclipse (en passant, Eclipse est également un cadre OSGi). Ensuite, ouvrez le fichier JAR vous venez de télécharger avec WinZip (ou autre). Vous pouvez également changer tout simplement l'extension du fichier .jar en .zip et puis extraire le fichier dans un nouveau répertoire.
Pour démarrer l'environnement d'exécution Knopflerfish, vous pouvez maintenant double-cliquer simplement sur le fichier framework.jar que vous trouverez dans ce répertoire:

/knopflerfish.org/OSGi/framework.jar


Je recommande de créer un fichier startup.sh. Il suffit de copier la ligne suivante dans un fichier appelé startup.sh et le placer dans le même répertoire que le fichier framework.jar:

java -jar framework.jar


Vous devriez voir une fenêtre de commande qui s'ouvre et peu de temps après cela le Knopflerfish OSGi Desktop démarre.

Pasted Graphic
La fenêtre de commande de Knopflerfish.

Pasted Graphic 1
L'interface Knopflerfish OSGi Desktop.


Étape nº2: Création de votre premier bundle

Dans la programmation OSGi, les éléments qui peuvent être installés dans un cadre sont appelés bundles. Les bundles sont simplement des fichiers .jar, qui contiennent généralement les fichiers de classes Java du service, les interfaces, leur mise en oeuvre et quelques informations supplémentaires dans un méta fichier METAINF/manifest.mf. Les services sont des interfaces Java et une fois que votre bundle a enregistré un service dans l'environnement OSGi, d'autres bundles peuvent utiliser votre service publié. Dans un premier temps, votre premier bundle se contentera de créer un thread d'arrière-plan qui affiche "Hello, World!" toutes les 5 secondes.

Création d'un projet pour votre bundle

Ouvrez votre IDE préféré (Eclipse, NetBeans...) et créez un nouveau projet Java. Nommez le simplebundle. Assurez-vous d'importer le fichier framework.jar dans le classpath de votre projet (sinon vous n'aurez pas accès aux classes et interfaces fournies par OSGi).

Création du fichier manifest.mf

Ensuite, ajoutez un répertoire META-INF à votre projet. Ce répertoire contiendra le fichier manifest.mf qui décrira le contenu de votre bundle. C'est ce fichier qui est ensuite utilisé par l'environnement d'exécution OSGi pour récupérer les informations nécessaires à son déploiement. Créez le fichier manifest.mf avec les informations suivantes:

Manifest-Version: 1.0
Bundle-Name: simplebundle
Bundle-SymbolicName: simplebundle
Bundle-Version: 1.0.0
Bundle-Description: Demo Bundle
Bundle-Vendor: University of Lille 1
Bundle-Activator: simplebundle.impl.Activator
Bundle-Category: example
Import-Package: org.osgi.framework


Les propriétés les plus importantes sont Bundle-Activator et Import-Package. Bundle-Activator indique à l'environnement d'exécution quelle est la classe d'activation du bundle à exécuter, c'est l'équivalent de la méthode main() d'une application Java. Dans ce TP, nous allons ensuite créer la classe d'activation simplebundle.impl.Activator qui sera appelée par l'environnement d'exécution OSGi lors du déploiement de notre bundle.
La propriété Import-Package indique à l'environnement d'exécution que notre bundle nécessite d'accéder aux classes du paquetage org.osgi.framework, ce qui est le cas généralement pour tous les bundles OSGi que vous développerez. L'utilisation de cette propriété protège votre environnement de déclencher des exceptions du type ClassNotFoundException.

Création du processus de compilation Ant

Nous utiliserons Ant pour compiler le projet (mais il est tout à fait possible d'utiliser Maven pour réaliser la compilation des bundles). Créez un fichier build.xml à la racine de votre projet et ajoutez y les cibles suivantes:

<?xml version="1.0"?>
<project name="simplebundle" default="all">
<target name="all" depends="init,compile,jar"/>
<target name="init">
<mkdir dir="./classes"/>
<mkdir dir="./build"/>
</target>
<target name="compile">
<javac destdir = "./classes" classpath="PATH_TO_JAR/framework.jar" debug = "on" srcdir = "./src"/>
</target>
<target name="jar">
<jar basedir = "./classes" jarfile = "./build/simplebundle.jar"
compress = "true" includes = "**/*" manifest = "./meta-inf/MANIFEST.MF"/>
</target>
<target name="clean">
<delete dir = "./classes"/>
<delete dir = "./build"/>
</target>
</project>


Création de la classe d'activation

La plupart des bundles ont une classe d'activation spécifiée dans le fichier manifest.mf du bundle. Cette classe d'activation doit implémenter l'interface BundleActivator fournie par l'environnement OSGi. Cette interface impose notamment l'implémentation de deux méthodes start() et stop(), qui seront appelées par l'environnement d'exécution pour contrôler l'exécution du bundle.
Créez le paquetage simplebundle.impl. OSGi encourage à séparer les interfaces de leurs implémentations. Comme notre premier bundle ne va enregistrer aucun service pour l'instant, le paquetage simplebundle sera vide. Le sous-paquetage impl contiendra la class Activator qui se chargera de démarrer et stopper le bundle.
Créez la classe Activator qui implémente l'interface BundleActivator:

package simplebundle.impl;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {
public static BundleContext bc = null;

public void start(BundleContext bc) throws Exception {
Activator.bc = bc;
}
public void stop(BundleContext bc) throws Exception {
Activator.bc = null;
}}


Notez que les méthodes start() et stop() reçoivent un objet BundleContext en paramètre. Vous devez systématiquement stocker cet objet dans votre classe d'activation et relâcher sa référence lorsque le bundle est stoppé. Ensuite nous pouvons créer une sous-classe de Thread qui se chargera d'afficher le message «Hello, World!» toutes les 5 secondes:

package simplebundle.impl;

public class HelloWorldThread extends Thread {
private boolean running = true;

public void run() {
while (running) {
System.out.println("Hello, World!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("HelloWorldThread ERROR: " + e);
}}}

public void stopThread() {
this.running = false;
}}


Enfin, il ne nous reste plus qu'à créer un nouveau thread lorsque le bundle est démarré (via la méthode start()). Ce thread sera terminé quand le bundle sera arrêté (via la méthode stop()). Les messages supplémentaires permettent de visualiser l'appel aux méthodes start() et stop():

package simplebundle.impl;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {
public static BundleContext bc = null;

private HelloWorldThread thread = null;

public void start(BundleContext bc) throws Exception {
System.out.println("SimpleBundle starting...");
Activator.bc = bc;
this.thread = new HelloWorldThread();
this.thread.start();
}
public void stop(BundleContext bc) throws Exception {
System.out.println("SimpleBundle stopping...");
this.thread.stopThread();
this.thread.join();
Activator.bc = null;
}}


Compilation et Installation de votre bundle

Compilez votre projet en utilisant Ant. Vous devez maintenant avoir un fichier simplebundle.jar dans le répertoire build de votre projet. Ouvrez l'interface Knopflerfish OSGi Desktop et sélectionner le menu File > Open Bundle. Sélectionner le bundle que vous venez de compiler et installez le. Le bundle est automatiquement démarré et vous devez voir une icône apparaître dans la partie gauche de la fenêtre. Toutes les 5 secondes, le bundle affiche un nouveau message «Hello, World!». Essayez d'arrêter et redémarrer le bundle en utilisant les boutons du menu de l'interface graphique.

Pasted Graphic 2
Interface Knopflerfish OSGi Desktop après l'installation du bundle.

Étape nº3: Création de votre premier service

Cette étape va vous permettre d'expérimenter la mise en œuvre d'un service OSGi. Pour cela, nous devons créer un bundle pour ce service, sauf que cette fois le paquetage associé contiendra non seulement la définition d'une interface mais aussi son implémentation.
Dans un premier temps, créez une copie du bundle que vous avez développé à l'étape suivante. Assurez-vous de le renommer en DateBundle puisque ce service nous permettra de formater un object Date et de le retourner via le service OSGi. Vérifiez que le fichiers build.xml et manifest.mf ont été correctement renommés.

Mise à jour du fichier manifest.mf

Le fichier manifest.mf doit être légèrement modifié pour publier le service que vous allez développer. Vous devez ajouter la propriété Export-Package à la fin du fichier. Cette propriété est nécessaire pour rendre publique l'interface du service de manière à ce que d'autres services puissent l'utiliser. Le contenu de votre fichier manifest.mf doit donc ressembler à:

Manifest-Version: 1.0
Bundle-Name: DateBundle
Bundle-SymbolicName: DateBundle
Bundle-Version: 1.0.0
Bundle-Description: Demo Bundle
Bundle-Vendor: University of Lille 1
Bundle-Activator: datebundle.impl.Activator
Bundle-Category: example
Import-Package: org.osgi.framework
Export-Package: datebundle



Création de l'interface du service

Créez une interface DateService dans le paquetage datebundle avec la définition suivante:

package datebundle;

import java.util.Date;

public interface DateService {
String getFormattedDate(Date date);
}


Création de la classe d'implantation du service

Ensuite, vous pouvez implémenter le service DateService en créant la classe DateServiceImpl dans le paquetage impl. La mise en œuvre du service est isolée de la définition du service pour éviter que les autres bundles n'aient un accès direct à l'implantation de notre service (principe d'encapsulation).

package datebundle.impl;

import java.text.DateFormat;
import java.util.Date;
import datebundle.DateService;

public class DateServiceImpl implements DateService {
public String getFormattedDate(Date date) {
return DateFormat.getDateInstance(DateFormat.SHORT).format(date);
} }


L'implantation de ce service est relativement triviale mais c'est suffisant pour les besoins de cette étape.

Création de l'activateur de service

Finallement, il est nécessaire d'enregistrer la référence de notre service dans l'environnement d'exécution OSGi pour qu'il soit utilisable. Cette procédure est réalisée durant l'appel à la méthode start() de la classe d'activation du bundle. En particulier, nous devons d'abord créer une instance du service et ensuite enregistrer sa référence sous le nom de l'interface. Les procédures d'enregistrement sont gérées via les méthodes fournies par l'objet BundleContext. Cet objet joue donc le rôle d'interface entre le bundle et l'environnement d'exécution OSGi.

package datebundle.impl;

import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import datebundle.DateService;

public class Activator implements BundleActivator {

public static BundleContext bc = null;

public void start(BundleContext bc) throws Exception {
System.out.println(bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+" starting...");
Activator.bc = bc;
DateService service = new DateServiceImpl();
ServiceRegistration registration = bc.registerService(DateService.class.getName(), service, new Hashtable());
System.out.println("Service registered: DateService");
}

public void stop(BundleContext bc) throws Exception {
System.out.println(bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+" stopping...");
Activator.bc = null;
} }


La méthode registerService de l'objet requiert 3 paramètres: le premier paramètre correspond à l'identifiant de l'interface du service. Le second paramètre correspond à la référence du service à enregistrer. Le dernier paramètre permet d'attacher des propriétés supplémentaires sous la forme de clés/valeurs et décrivant des caractéristiques du service.

Compilation et installation du service

Comme lors de l'étape précédente, compilez et installez le bundle DateBundle. Installez-le ensuite via l'interface Knopflerfish OSGi Desktop. Vous devez voir apparaître des messages de trace lors de l'installation du bundle. La prochaine étape vous montre comment utiliser ce service.

Étape nº4: Déploiement automatique des bundles OSGi

Développez un nouveau service OSGi qui surveille le contenu d'un répertoire de votre système de fichier dans lequel des bundles sont déposés (via un copier/coller depuis le gestionnaire de fichiers). Quand un bundle est copié dans le répertoire, le service le déploie automatiquement dans l'environnement d'exécution OSGi. Quand le bundle est supprimé du répertoire, le service désinstalle les services associés au bundle. Enfin quand le bundle est remplacé par une nouvelle version, le service met automatiquement à jour le bundle associé.

Cet exercice doit être réalisé individuellement et le code source du bundle est à déposer sur l'interface PROF pour la prochaine séance.