1. Introduction

Ce premier article a pour objectif de fournir une introduction à la technologie OSGi (Open Service Gateway Initiative), technologie qui après avoir été longtemps utilisée dans le monde de l'embarqué est de plus en plus mise en oeuvre dans les applications classiques et serveurs.

Nous verrons dans un premier temps les différents concepts relatifs à OSGi et détaillerons à quelles problématiques cette technologie s'adresse tout en soulignant ses apports en terme d'architecture et de structuration des applications.

Nous rentrerons par la suite dans les aspects techniques d'OSGi afin de détailler ses caractéristiques et de voir concrètement comment mettre en oeuvre cette technologie au sein d'applications Java. Nous laisserons le soin à un prochain article de décrire la manière d'utiliser OSGi dans des applications Java EE tout en se fondant sur des bibliothèques et frameworks Java EE.

2. Programmation orientée composant et architecture orientée service avec OSGi

Avant d'entrer dans le coeur de la technologie, nous allons détailler les caractéristiques de la programmation orientée composant et des architectures orientée service afin d'identifier les problématiques qu'elles visent à résoudre. Nous verrons alors que la technologie OSGi adresse ces deux types de technologies.

Tout d'abord, la programmation orientée composant [1] vise à adresser les limitations de la programmation orientée objet [2]. Cette dernière offre d'intéressants mécanismes afin de modulariser les traitements et les rendre réutilisables mais n'apporte aucun support afin de mettre en relation les classes. De plus, plus les traitements de l'application augmentent et se complexifient, plus ces limitations sont visibles et pénalisantes au niveau de la maintenabilité et de l'évolutivité de l'application.

Ainsi, cet aspect peut devenir très problématique lors de la mise en oeuvre de réseaux complexes d'objets. En effet, cela se traduit très souvent par des couplages forts entre objets, ce point nuisant énormément à la réutilisabilité des traitements. Chaque développeur a alors le choix des outils afin d'adresser au mieux au sein de ses applications ces aspects. L'approche la plus adaptée consiste en la mise en oeuvre du patron de conception injection de dépendance par l'intermédiaire de frameworks tels que Spring [3] utilisés conjointement avec la programmation par interface.

Nous retrouvons ce problème notamment au niveau des instanciations des objets, directement ou par l'intermédiaire de l'introspection. En effet, même avec la programmation par interface, la classe appelante est liée à la classe appelée. Les patrons de conception tels que les fabriques [4] et l'injection de dépendance [5] permettent d'adresser cette problématique en déléguant les traitements d'instanciation à une entité autonome (respectivement la fabrique et le conteneur).

En parallèle de la programmation orientée composant, les architectures orientées service peuvent être mises en oeuvre afin de mettre à disposition des traitements tout en diminuant les couplages entre les briques techniques. La SOA (Service Oriented Architecture) [6] n'implique pas nécessairement l'utilisation des services Web avec les technologies SOAP [7] et WSDL [8] et peut être mise en oeuvre au sein d'un même processus Java notamment avec des conteneurs légers (entités mettant en oeuvre l'injection de dépendances) tels que Spring[3] ou Hivemind [9]. Dans ce contexte, nous parlons de fournisseur de services et de consommateurs de services, les fournisseurs mettant à disposition des composants. La programmation orientée composant [1] et les architectures orientées service peuvent donc être mises en oeuvre de manière complémentaire afin de bénéficier des avantages des deux types de technologies.

La figure suivante illustre différents composants exposant des services par l'intermédiaire d'interfaces et reliés entre eux par des services:

Image non disponible

Il est à noter que des composants OSGi peuvent être également reliés entre eux par l'intermédiaire des packages, packages qu'ils exportent et importent. Dans ce cas, les services ne sont donc pas l'unique manière de les relier.

Dans ce contexte, la technologie OSGi vise à adresser les différentes problématiques vues précédemment, problématiques récapitulées ci-dessous:

  • Adresser les limitations de la programmation orientée objet;
  • Permettre la gestion des applications complexes et de taille importante;
  • Améliorer la qualité de service des applications en permettant une administration à chaud;
  • Permettre la mise en oeuvre d'architectures orientées service légères.

Une des autres caractéristiques de la technologie OSGi est sa portabilité puisqu'elle peut être mise en oeuvre aussi bien dans des terminaux de manière embarquée que dans des applications classiques ou serveurs. Le premier aspect a été à la base d'OSGi, les seconds types d'application s'étant développés ces dernières années par l'intermédiaire d'outils tels qu'Eclipse [10]. Le fait qu'OSGi repose sur la technologie Java pour son exécution a été également un important facteur de portabilité.

3. Architecture OSGi

Dans cette section, nous allons décrire les caractéristiques des conteneurs et composants OSGi. Le composant correspond à l'entité centrale de la technologie et désignée par le terme bundle avec la terminologie de cette dernière. Nous verrons également les spécificités du cycle de vie des bundles, la manière de les gérer au niveau du conteneur ainsi que la façon de les configurer par l'intermédiaire de leur descripteur de déploiement.

La figure suivante décrit les différentes briques de l'architecture de la technologie OSGi, briques que nous allons détaillées conceptuellement tout au long de cette section puis techniquement dans le reste de l'article:

Image non disponible

La couche Module adresse la gestion des bundles, aussi bien au niveau du chargement des classes (classloading) que de la gestion de leurs visibilités et de leurs versions. La couche Cycle de vie (life cycle) prend quant à elle en charge les états des bundles supportés par le conteneur ainsi que les API correspondantes. Pour finir, la couche Services offre la possibilité de mettre à disposition des services au sein d'une même machine virtuelle tout en masquant leurs implémentations à leurs utilisateurs. Les applications OSGi peuvent se fonder et tirer parti de ces différentes couches afin de mettre en oeuvre des composants et des services.

3.1. Gestion des composants OSGi

La couche module permet la mise en oeuvre des bundles dans le conteneur. Elle a la responsabilité de la gestion des différents chargeurs de classes et des versions des dépendances.

En effet, une des caractéristiques de la technologie consiste en l'isolation des classloaders des composants. En effet, chaque composant ou bundle OSGi dispose d'un classloader indépendant pour ses traitements. Cet aspect permet par exemple la mise en oeuvre dans deux bundles différents d'une même bibliothèque avec deux versions différentes et incompatibles.

Cet aspect offre la possibilité à OSGi de complètement maîtriser les classes et packages du composant visibles depuis l'extérieur et visibles par les autres composants. Par défaut, la stratégie est la plus restrictive possible, à savoir que rien n'est visible. Par contre, lors de la mise à disposition d'un package d'un composant, un numéro de version spécifique peut être spécifié. Ainsi, deux versions de packages peuvent coexister dans le conteneur OSGi sans aucun problème.

3.2. Cycle de vie des bundles

Comme dans tout conteneur, les éléments contenus dans ce dernier possèdent un cycle de vie bien particulier puisque le conteneur OSGi a la responsabilité de les gérer. Nous pouvons distinguer deux grandes parties dans les différents états supportés:

  • Non opérationnel. Les états correspondent à la présence du bundle dans le conteneur, mais ce dernier n'est pas utilisable par les autres bundles pour leurs traitements;
  • Opérationnel. Les états de ce type correspondent aux moments où le bundle est utilisable ou en phase de l'être ou de ne plus l'être.

La figure suivante illustre les différents états de gestion des bundles par le conteneur ainsi que leurs enchaînements possibles:

Image non disponible

Nous verrons par la suite que le conteneur OSGi offre une API standardisée afin de gérer le cycle de vie des composants. Veuillez vous reporter à la section 5.2 BundleContext pour plus de précisions. De plus, ces différents états sont mis en oeuvre par le bundle manager du conteneur qui a la responsabilité de gérer la plateforme.

Le tableau suivant récapitule les différents états supportés par le bundle manager des conteneurs OSGi ainsi que leurs principales caractéristiques:

Etat Descriptif
Installé (installed) Etat dans lequel se trouve un bundle juste après avoir été installé, la résolution des dépendances n'ayant pas encore été réalisée.
Résolu (resolved) Etat dans lequel se trouve un bundle après avoir été installé, la résolution des dépendances ayant juste été réalisée.
En train de démarrer (starting) Etat dans lequel se trouve un bundle lorsqu'il est en train d'être démarré. Cet état correspond à un état transitoire entre les événements Résolu et Actif.
Actif (active) Etat dans lequel se trouve un bundle lorsqu'il a été démarré avec succès. Le bundle ainsi que les services qu'il expose sont disponibles pour les autres bundles.
En train de s'arrêter (stopping) Etat dans lequel se trouve un bundle lorsqu'il est en train d'être arrêté. Cet état correspond à un état transitoire entre les événements Actif et Résolu.
Désinstallé (uninstalled) Etat dans lequel se trouve un bundle une fois qu'il a été désinstallé.

Nous pouvons noter qu'une entité peut être associée au cycle de vie des bundles et correspond à l'entité d'activation, entité appelée lors du démarrage et de l'arrêt d'un bundle. Cette entité peut être mise en oeuvre par l'intermédiaire de l'interface BundleActivator et configurée avec l'en-tête Bundle-Activtator. Nous reviendrons plus en détail sur ce mécanisme dans la section 5.1 BundleActivator.

Nous pouvons noter que la plupart des conteneurs OSGi fournissent des outils afin de gérer le cycle de vie des bundles et de visualiser leurs états. Dans cette optique, le conteneur OSGi
Felix [11] offre une console d'administration en ligne de commande possédant les commandes suivantes:

Commande Descriptif
install Installation de bundle(s).
ps Affichage de la liste des bundles installés.
refresh Rafraichissment des packages des bundles.
resolve Tentative de résolution des bundles spécifiés.
services Affichage de la liste des services enregistrés ou utilisés.
start Démarrage de bundle(s).
stop Arrêt de bundle(s).
uninstall Désinstallation de bundle(s).
update Mise à jour d'un bundle.

Avec la console de cet outil, le conteneur peut être complètement administré à chaud en ligne de commandes. Le scénario suivant peut être mis en oeuvre afin d'installer un bundle, de le démarrer, puis l'arrêter avec les commandes respectives install, start et stop. Le code suivant illustre concrètement l'enchaînement de ces différentes commandes:

 
CacherSélectionnez
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
-> install /home/templth/developpez/OSGi/simplebundle.jar
Bundle ID: 3
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
[   3] [Installed  ] [    1] Simple Bundle (1.0.1)
-> start 3
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
[   3] [Active     ] [    1] Simple Bundle (1.0.1)
-> stop 3
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.0.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
[   3] [Resolved   ] [    1] Simple Bundle (1.0.1)

3.3. Services

La couche Services offre la possibilité de mettre à disposition certains traitements des composants par l'intermédiaire de services. Nous désignons par le terme service des traitements structurés de la manière suivante. Il s'agit d'une entité dont le contrat est clairement défini par l'intermédiaire d'une interface Java. Seule cette interface est connue par les consommateurs du service. La ou les implémentations du service doivent rester internes aux composants et implémenter la précédente interface.

Le mécanisme de gestion des services est complètement dynamique. En effet, dès qu'un service est enregistré, il est automatiquement utilisable par tous les composants disponibles dans le conteneur OSGi. Comme nous le verrons par la suite, une API est disponible afin d'enregistrer des implémentations de services. De plus, par convention, il convient de faire correspondre le nom du service au nom de l'interface implémentée.

Dans le cadre de la technologie OSGi, l'architecture orientée services a la caractéristique d'être très "légère" et de pouvoir fonctionner de manière autonome dans un seul processus Java. Elle correspond à un modèle de services dans une unique machine virtuelle (in-VM).

Nous verrons dans la section 5.1 comment mettre en oeuvre concrètement des services dans le cadre de la technologie OSGi.

3.4. Conclusion

L'architecture de la technologie OSGi permet de mettre en oeuvre des composants et de mettre à disposition des services applicatifs. Elle offre également un cycle de déploiement de services très rapides par l'intermédiaire du cycle de vie des bundles tout en offrant une meilleure structuration des fondations des applicatifs.

Nous pouvons remarquer que la sécurité est prévue en standard puisqu'elle fait partie intégrante de la spécification OSGi. Elle offre la possibilité de spécifier des permissions d'accès aux entités gérées par le conteneur. Une de ses principales caractéristiques consiste dans le fait que sa configuration est complètement dynamique et ne nécessite aucun redémarrage après une modification.

Rentrons maintenant dans le détail technique de la technologie OSGi et voyons concrètement comment la mettre en oeuvre.

4. Bundle OSGi

Dans cette section, nous allons détailler les différentes caractéristiques de l'entité centrale d'OSGi, le composant ou bundle. Nous verrons comment le mettre en oeuvre par l'intermédiaire d'un simple fichier JAR de Java.

4.1. Caractéristiques d'un bundle

Avec la technologie OSGi, le concept de composant est mis en oeuvre par l'intermédiaire des bundles, un bundle correspondant à un composant. Ces derniers permettent de mettre en oeuvre les différents concepts des composants et ce sont eux qui sont déployés dans les conteneurs OSGi.

Avec OSGi, un composant (bundle) est simplement stocké dans un fichier JAR de Java. Les informations de déploiement sont spécifiées par l'intermédiaire du fichier standard MANIFEST.MF présent dans le répertoire META-INF. En effet, OSGi reprend ce fichier en y ajoutant différents en-têtes afin de configurer le composant.

Il n'est pas nécessaire d'ajouter d'autres éléments au fichier JAR si ce n'est les classes bien sûr et les éventuelles bibliothèques utilisées, uniquement les bibliothèques n'étant pas définies en tant que dépendances. Nous pouvons constater que cet aspect favorise de manière importante la simplicité de mise en oeuvre de composants dans des conteneurs de ce type. Les conteneurs OSGi sont de ce fait très légers. N'oublions pas qu'OSGi était à l'origine utilisé dans des systèmes embarqués donc très soucieux de la consommation mémoire.

Les conteneurs possèdent diverses caractéristiques quant à la gestion des composants. Tout d'abord, ils ont la particularité de permettre la gestion des dépendances entre composants. En effet, comme nous l'avons vu précédemment, un composant s'appuie la plupart du temps sur d'autres afin de réaliser ses traitements et de fonctionner correctement. Pour ce faire, OSGi offre la possibilité de rendre visible dans le conteneur les packages de composants de manière très fine. La gestion des versions des dépendances est également supportée.

4.2. en-têtes OSGi du fichier MANIFEST.MF

Comme nous l'avons précisé dans la précédente section, le fichier MANIFEST.MF correspond au descripteur de déploiement du bundle. Ce fichier est lu par le conteneur OSGi afin de configurer le bundle.

La spécification OSGi définit un ensemble d'en-têtes standards pour un composant, en-têtes utilisables dans le fichier MANIFEST.MF et dont la liste des principales est récapitulée dans le tableau suivant:

En-tête Descriptif
Bundle-ManifestVersion Correspond à la version de fichier MANIFEST du bundle
Bundle-SymbolicName Spécifie l'identifiant symbolique du bundle
Bundle-Name Spécifie le nom du bundle
Bundle-Version Spécifie la version du bundle
Bundle-DocURL Permet de préciser l'adresse de la documentation du bundle.
Bundle-Category Spécifie la catégorie du bundle.
Import-Package Spécifie les noms et les versions des packages utilisés par le bundle.
Export-Package Spécifie les noms et les versions des packages mis à disposition par le bundle.
DynamicImport-Package Spécifie les noms et les versions des packages utilisés par le bundle. Cet en-tête se différencie de Import-Package par le fait qu'il ne soit pas nécessaire que les dépendances soient présentes au démarrage du bundle. Il suffit qu'elles le soient au moment de l'exécution.
Bundle-NativeCode Spécifie la liste des bibliothèques natives présentes dans le bundle.
Require-Bundle Spécifie les identifiants symboliques des bundles nécessaires au bon fonctionnement du bundle.
Bundle-Activator Spécifie le nom de la classe dont les traitements sont exécutés lors du démarrage et de l'arrêt du bundle. Cette classe doit être présente dans le bundle et implémenter l'interface BundleActivator.
Bundle-Classpath Spécifie le classpath du bundle. Par défaut, la valeur implicite correspond à . et il faut veiller à ne pas l'oublier lorsque des bibliothèques sont spécifiées explicitement.

Le code suivant illustre la configuration d'un composant OSGi simple dans le fichier MANIFEST.MF. Ce bundle est identifié par simple.bundle, possède le nom "Simple Bundle", exporte le package org.developpez.osgi.service et utilise l'entité d'activation org.developpez.osgi.SimpleActivator.

 
TéléchargerCacherSélectionnez
Manifest-Version = 1
Bundle-Activator = org.developpez.osgi.SimpleActivator
Bundle-SymbolicName = simple-bundle
Bundle-Name = Simple Bundle
Bundle-Description = Simple Bundle.
Import-Package = org.osgi.framework;version=1.3
Export-Package = org.developpez.osgi.services
Bundle-Version = 1.0.1
Bundle-License = http://www.apache.org/licenses/LICENSE-2.0.txt

5. Interactions entre bundles

Comme nous l'évoquions dans la section précédente, un conteneur OSGi offre la possibilité de gérer la visibilité des ressources en composant. Par défaut, un composant est complètement opaque depuis l'extérieur et rien n'est accessible. Cet aspect implique de préciser toutes les dépendances utilisées par un composant et tous les packages mis à disposition.

La spécification OSGi offre la possibilité de jouer sur cet aspect par l'intermédiaire d'en-têtes dans le fichier MANIFEST.MF, fichier contenant les données relatives au déploiement et décrit dans la précédente section. Détaillons maintenant les spécificités de ces en-têtes et la manière de les mettre en oeuvre.

Notons qu'il existe une manière complémentaire de faire interagir les bundles entre eux par l'intermédiaire des services. Nous décrirons cet aspect dans une prochaine section.

5.1. Mise à disposition de packages

Afin de mettre à des dispositions des packages (ou exporter) d'un bundle, l'en-tête Export-Package doit être mise en oeuvre dans le fichier MANIFEST.MF du composant. Cet en-tête contiendra la liste des packages pour lesquels l'accès est possible.

Il est à noter que la spécification d'un package dans ce cadre autorise l'accès à toutes les classes du package tout en gardant inaccessible les classes contenues dans les sous packages du package.

Lors de l'exportation d'un package, OSGi offre la possibilité d'ajouter des informations complémentaires telles que la version et dont la liste est récapitulée dans le tableau ci-dessous:

Paramètre Descriptif
uses Spécifie la liste des packages utilisés par le package exporté.
mandatory Spécifie une liste de paramètres obligatoires à spécifier lors de l'importation du package exporté.
include Spécifie une liste de packages devant être visibles par le bundle important le package.
exclude Spécifie une liste de packages devant être invisibles par le bundle important le package.
version Spécifie la version avec laquelle est mis à disposition le package.

La syntaxe complète d'une valeur pour la propriété Export-Package est donc la suivante:

 
TéléchargerCacherSélectionnez
Export-Package: nom-package;parametre:=valeur,nom-package;parametre:=valeur,...

Notons que, si la valeur d'un paramètre est une liste, les éléments doivent être séparés par des virgules et la valeur globale doit être entourée par des guillemets.

Le code suivant correspond à un extrait de la valeur de l'en-tête Export-Package du fichier MANIFEST.MF d'un bundle OSGi pour Hibernate 3 [12]. Nous remarquons la présence des paramètres uses et version pour le packageorg.hibernate.stat.

 
TéléchargerCacherSélectionnez
Export-Package: org.hibernate.stat;uses:="org.hibernate.util,org.hibernate.cache,
 org.apache.commons.logging,org.hibernate.engine";version=3.1.3,
 org.hibernate.cache;uses:="org.hibernate.util,net.sf.swarmcache,
 org.hibernate.cfg,org.hibernate,net.sf.ehcache,org.hibernate.impl,...

5.2. Importation de packages

La technologie OSGi offre la possibilité de spécifier les packages utilisés afin qu'ils soient visibles dans le bundle. Une fois spécifiées, les classes sont utilisables dans les classes implémentées par le bundle. Cet aspect se configure dans le fichier MANIFEST.MF par l'intermédiaire des en-têtes Import-Package et DynamicImport-Package. Détaillons tout d'abord l'usage du premier en-tête.

Lors de l'importation d'un package, OSGi offre la possibilité d'ajouter des informations complémentaires telles que la version et le mode de résolution dont la liste simplifiée est récapitulée dans le tableau ci-dessous:

Paramètre Descriptif
resolution Spécifie le type de résolution du package. La valeur par défaut est mandatory.
version Spécifie la version du package à utiliser. Une plage de versions peut être spécifiée.

La syntaxe complète d'une valeur pour la propriété Import-Package est donc la suivante:

 
TéléchargerCacherSélectionnez
Import-Package: nom-package;parametre:=valeur,nom-package;parametre:=valeur,...

Notons que, si la valeur d'un paramètre est une liste, les éléments doivent être séparés par des virgules et la valeur globale doit être entourée par des guillemets.

Le code suivant correspond à un extrait de la valeur de l'en-tête Import-Package du fichier MANIFEST.MF du bundle OSGi de Spring AOP 2.0.5. Nous remarquons la présence des paramètres resolution pour le package com.jamonapi et version pour le package org.springframework.aop.

 
TéléchargerCacherSélectionnez
Import-Package: com.jamonapi;resolution:=optional,net.sf.cglib.core;resolution:=optional,
 net.sf.cglib.proxy;resolution:=optional,net.sf.cglib.transform.impl;resolution:=optional,
 ...
 org.springframework.aop;version=2.0.5,org.springframework.aop.aspectj;version=2.0.5,...

Dans le cas de l'importation de packages, l'en-tête DynamicImport-Package est également disponible. Il se différencie de la précédente par le fait que la résolution des dépendances spécifiées à ce niveau n'est réalisée qu'au moment du chargement des classes et non lors du passage du bundle de l'état Installé à Résolu.

L'en-tête fonctionne sinon de la même manière que l'en-tête Import-Package, si ce n'est qu'il ne supporte logiquement pas le paramètre resolution.

6. Principales API OSGi

Par cette section, nous allons introduire deux interfaces des APIs de la technologie OSGi relatives respectivement à l'entité d'activation et au contexte des bundles. Elles permettent notamment d'initialiser et de finaliser les ressources des bundles et d'interagir avec le conteneur OSGi.

L'entité relative au contexte OSGi nous sera utile dans la section relative aux services puisque cette interface met à disposition des méthodes afin de manipuler et d'avoir accès aux services présents dans le conteneur OSGi.

6.1. BundleActivator - Entité d'activation

La technologie OSGi offre la possibilité de spécifier une entité appelée lors de l'activation (état Démarrage) et de la désactivation (état En cours d'arrêt) d'un composant, c'est-à-dire lorsque ce dernier est démarré ou arrêté. La classe implémentant cette entité doit nécessairement posséder l'interface BundleActivator et être configurée par l'intermédiaire de l'en-tête Bundle-Activator dans le fichier MANIFEST.MF.

L'interface BundleActivator définit la structure d'une classe d'activation en définissant les signatures des méthodes appelées lors des différents événements précédemment cités:

  • Méthode start, appelée lors le démarrage du composant (état Démarrage);
  • Méthode stop, appelée quant à elle lors de l'arrêt du composant (état En cours d'arrêt).

Ces deux méthodes prennent en paramètre le contexte OSGi du composant par l'intermédiaire de l'interface BundleContext, interface permettant d'interagir aussi bien avec le conteneur qu'avec le composant. Le contenu de l'interface BundleActivator est décrit ci-dessous:

 
TéléchargerCacherSélectionnez
public interface BundleActivator { 
    void start(BundleContext context);
    void stop(BundleContext context);
}

Le code suivant illustre la mise en oeuvre d'une entité d'activation pour un bundle par l'intermédiaire d'une implémentation simple appelée ici SimpleActivator:

 
CacherSélectionnez
public class SimpleActivator implements BundleActivator {
    private ServiceRegistration serviceRegistration;

    public void start(BundleContext context) {
        // Enregistrement d'un service
        SimpleService service  = new SimpleServiceImpl();
        this.serviceRegistration = context.registerService(
                      SimpleService.class.getName(), service, null);
    }

    public void stop(BundleContext context) {
        // Désenregistrement d'un service
        if( this.serviceRegistration!=null ) {
            this.serviceRegistration.unregister();
        }
    }
}

Le code suivant décrit la configuration de cette entité par l'intermédiaire de l'en-tête Bundle-Activator dans le fichier MANIFEST.MF du bundle:

 
CacherSélectionnez
Manifest-Version = 1
Bundle-Activator = org.developpez.OSGi.SimpleActivator
Bundle-SymbolicName = simple-bundle
Bundle-Name = Simple Bundle
Bundle-Description = Simple Bundle.
Import-package = org.OSGi.framework;version=1.3
Bundle-Version = 1.0.1
Bundle-License = http://www.apache.org/licenses/LICENSE-2.0.txt

6.2. BundleContext - Contexte de composant

Nous avons vu dans la précédente section que l'entité d'activation s'appuie sur le contexte du composant. Détaillons maintenant les spécificités de l'interface BundleContext, interface relative au contexte du composant. Cette dernière permet d'interagir aussi bien au niveau du conteneur OSGi lui-même que du bundle dans lequel il est utilisé. Elle offre la possibilité de réaliser les différentes opérations suivantes:

Opération Descriptif
Manipulation des bundles Permet de récupérer les instances de bundles du conteneur et d'installer de nouvelles instances.
Manipulation des services Permet de récupérer les instances de services du conteneur et d'installer de nouveaux. Nous détaillerons cet aspect dans la section suivante.
Observateurs Permet de spécifier et de supprimer des observateurs à différents niveaux.

Le contenu de l'interface BundleContext est décrit dans le code ci-dessous:

 
CacherSélectionnez
public interface BundleContext { 
    void addBundleListener(BundleListener listener); 
    void addFrameworkListener(FrameworkListener listener); 
    void addServiceListener(ServiceListener listener); 
    void addServiceListener(ServiceListener listener,
                            Java.lang.String filter); 
    Filter createFilter(Java.lang.String filter); 
    ServiceReference[] getAllServiceReferences(Java.lang.String clazz,
                                               Java.lang.String filter); 
    Bundle getBundle(); 
    Bundle getBundle(long id); 
    Bundle[] getBundles(); 
    Java.io.File getDataFile(Java.lang.String filename); 
    Java.lang.String getProperty(Java.lang.String key); 
    Java.lang.Object getService(ServiceReference reference); 
    ServiceReference getServiceReference(Java.lang.String clazz); 
    ServiceReference[] getServiceReferences(Java.lang.String clazz,
                                            Java.lang.String filter); 
    Bundle installBundle(Java.lang.String location); 
    Bundle installBundle(Java.lang.String location,
                         Java.io.InputStream input); 
    ServiceRegistration registerService(Java.lang.String[] clazzes,
                                        Java.lang.Object service,
                                        Java.util.Dictionary properties); 
    ServiceRegistration registerService(Java.lang.String clazz,
                                        Java.lang.Object service,
                                        Java.util.Dictionary properties); 
    void removeBundleListener(BundleListener listener); 
    void removeFrameworkListener(FrameworkListener listener); 
    void removeServiceListener(ServiceListener listener); 
    boolean ungetService(ServiceReference reference); 
}

Détaillons maintenant les spécificités relatives aux bundles et aux observateurs. Cette interface offre tout d'abord la possibilité de gérer et récupérer les instances des bundles présents dans le conteneur. Tout d'abord, l'installation de bundles se réalise par l'intermédiaire des méthodes installBundle, méthode prenant en paramètre le chemin du fichier JAR du bundle. Les méthodes getBundle et getBundles retournent quant à elle une ou plusieurs instances de bundles. Sont supportées les récupérations du bundle courant, d'un bundle en se fondant sur son identifiant dans le conteneur ou de tous les bundles du conteneur.

Le code suivant illustre la mise en oeuvre de ces méthodes dans une entité ayant accès au contexte OSGi:

 
CacherSélectionnez
// Installation d'un nouveau bundle
Bundle bundleInstalle = contexte.installBundle(
            "file:///home/templth/developpez/OSGi/simplebundle.jar");
long idBundle = bundleInstalle.getBundleId();

// Récupération d'une instance d'un bundle présent dans le conteneur
Bundle bundle = contexte.getBundle(idBundle);
int etatBundle = bundle.getState();

Comme vous pouvez le constater, le code précédent repose sur l'interface Bundle correspondant à l'entité relative à un composant OSGi. Cette dernière permet de récupérer des informations sur le bundle mais également de gérer son état. Le code suivant décrit le contenu de l'interface Bundle:

 
CacherSélectionnez
public interface Bundle {
    public static final int UNINSTALLED = 0x00000001;
    public static final int INSTALLED = 0x00000002;
    public static final int RESOLVED = 0x00000004;
    public static final int STARTING = 0x00000008;
    public static final int STOPPING = 0x00000010;
    public static final int ACTIVE = 0x00000020;

    Enumeration findEntries(String path, String filePattern, boolean recurse)
    BundleContext getBundleContext()
    long getBundleId()
    URL getEntry(String path)
    Enumeration getEntryPaths(String path)
    Dictionary getHeaders()
    Dictionary getHeaders(String locale)
    long getLastModified()
    String getLocation()
    ServiceReference[] getRegisteredServices()
    URL getResource(String name)
    Enumeration getResources(String name)
    ServiceReference[] getServicesInUse()
    int getState()
    String getSymbolicName()
    boolean hasPermission(Object permission)
    Class loadClass(String name)
    void start()
    void start(int options)
    void stop()
    void stop(int options)
    void uninstall()
    void update()
    void update(InputStream in)
}

Il est ainsi possible par la programmation, après l'installation d'un bundle, de réaliser le démarrage du bundle et de récupérer les différents services mis à disposition par le bundle puis de l'arrêter, comme l'illustre le code suivant:

 
CacherSélectionnez
// Installation d'un nouveau bundle
Bundle bundleInstalle = contexte.installBundle(
            "file:///home/templth/developpez/OSGi/simplebundle.jar");

// Démarrage du bundle (état installed vers active)
bundle.start();

// Récupération des services mis à disposition
ServiceReference[] serviceReferences = bundle.getRegisteredServices();

// Arrêt du bundle (état active vers resolved)
bundle.stop();

Pour finir, le contexte offre la possibilité de spécifier des observateurs d'événements sur le conteneur OSGi lui-même, sur les bundles et sur les services. Cet aspect est couramment utilisé dans les bonnes pratiques de mise en oeuvre de la technologie OSGi, notamment par l'intermédiaire du patron de conception Extender [13] au niveau des bundles.

Le principe consiste en la mise en oeuvre d'un observateur utilisant les données des bundles afin de réaliser des traitements. Ce patron permet de mieux gérer les états des bundles lors de traitements généraux, traitements pouvant être réalisés de manière paresseuse. Cette approche est mise en oeuvre dans des outils tels que Declarative Services [14], iPOJO[15], Spring Dynamic Modules [16].

Un observateur de bundles peut être mis en oeuvre par l'intermédiaire des interfaces BundleListener et SynchronousBundleListener, respectivement déclenchées de manière asynchrone et synchrone. Les deux interfaces mettent en oeuvre la méthode bundleChanged afin de notifier un changement pour un bundle. Cette méthode prend en paramètre un objet de type BundleEvent, objet permettant d'avoir accès au bundle impacté et à l'événement déclencheur dans le cycle vie. Le code suivant illustre le contenu de l'interface BundleListener:

 
CacherSélectionnez
public interface BundleListener extends EventListener {
    void bundleChanged(BundleEvent event);
}

La mise en oeuvre d'un observateur de bundles se réalise de la manière suivante:

 
CacherSélectionnez
// Enregistrement de l'observateur
BundleListener observateur = new BundleListener() {
    public void bundleChanged(BundleEvent evenement) {
        // Récupération du bundle impacté
        Bundle bundle = evenement.getBundle();
        int etat = evenement.getType();
        (...)
    }
};

context.addBundleListener(observateur);

7. Mise en oeuvre de services

Comme nous l'avons évoqué précédemment, la technologie OSGi offre la possibilité de mettre des services au niveau des composants. Elle permet de bien séparer le contrat du service (interface) de la ou des implémentations fournies par les composants. Les consommateurs du service n'ont connaissance que de son interface. De plus, les services mis en oeuvre avec OSGi n'ont aucune adhérence avec les API de conteneurs et consistent en de simples POJOs [17].

La technologie OSGi, par l'intermédiaire de l'interface BundleContext, offre la possibilité à un composant de mettre à disposition des services par l'intermédiaire de la méthode registerService. Le premier paramètre de cette méthode correspond au nom du service, nom visible au niveau du conteneur. Un usage courant consiste à spécifier le nom de l'interface du service comme nom du service.

Le code suivant décrit la manière d'enregistrer un service dans un conteneur OSGi par l'intermédiaire du contexte OSGi:

 
CacherSélectionnez
SimpleService service  = new SimpleServiceImpl();
ServiceRegistration serviceRegistration = context.registerService(
                               SimpleService.class.getName(), service, null);

Le désenregistrement d'un service OSGi se réalise par l'intermédiaire de la méthode unregister de l'instance de ServiceRegistration renvoyée précédemment par la méthode register. Une bonne pratique consiste donc à le garder en variable d'instance (de l'activateur par exemple). Le code suivant illustre la mise en oeuvre d'un désenregistrement de service:

 
CacherSélectionnez
ServiceRegistration serviceRegistration = (...)
serviceRegistration.unregister();

Différentes méthodes sont également fournies afin d'avoir accès aux services afin de les utiliser. La récupération d'une instance de service se réalise en deux étapes. La première consiste en la récupération d'une instance de l'interface ServiceReference pour le service par l'intermédiaire de la méthode getServiceReference ou getServiceReferences. L'utilisation de la méthode getService avec en paramètre l'instance précédente permet d'avoir accès à l'instance du service et de l'utiliser.

Le code suivant décrit la manière d'avoir accès à une instance d'un service à partir de son nom et par l'intermédiaire du contexte OSGi:

 
CacherSélectionnez
// Récupération de la référence du service
ServiceReference reference = context.getServiceReference(
                                        SimpleService.class.getName());

// Récupération de l'instance du service
SimpleService service = context.getService(reference);
(...)

Il est à noter que, dans le cas d'une utilisation d'un service en dehors du conteneur, le transtypage dans le type du service ne fonctionnera pas. En effet, les classes et interfaces gérées par le conteneur ne sont visibles et utilisables qu'en son sein. Un service OSGi reste néanmoins utilisable en se fondant sur l'introspection.

Nous pouvons également noter la présence d'une méthode ungetService au niveau du contexte afin de libérer l'instance du service référencée par l'instance de ServiceReference correspondante.

8. Conclusion

Dans ce premier article, nous avons introduit la technologie OSGi et les différentes problématiques qu'elle vise à adresser. Nous avons également décrit les caractéristiques de la technologie et comment mettre en oeuvre des composants OSGi, composants désignés par le terme bundle dans la terminologie d'OSGi. Nous avons ainsi abordés les différentes couches structurant les conteneurs de ce type, à savoir les couches Module, Cycle de vie et Service.

Dans le prochain article, nous verrons comment mettre concrètement en oeuvre des composants OSGi dans un conteneur embarqué dans un processus Java. Pour ce faire, nous utiliserons le conteneur OSGi libre Felix. Nous verrons également comment tirer partie de l'environnement Eclipse afin de développer des bundles OSGi.

9. Bibliographie