ADELE/LIG OSGi en bref Version 1.0.2 Eric SIMON OSGi en bref de Eric Simon est mis à disposition selon les termes de la licence Creative Commons Paternité 3.0 non transcrit. 1 But du document Ce document a pour but d’expliquer brièvement les mécanismes dans OSGi qui permettent le chargement et le déchargement dynamiques de modules ainsi que de la mise en place d’applications dites dynamiques. 2 OSGi : Histoire et Motivation Le succès d’OSGi ne cesse de croître. Cependant les buts actuels d’OSGi ne correspondent plus au but initial. JSR 008 A l’origine, le projet de la plate-forme OSGi est issu de la JSR 008 (décembre 1998) (cf. [1.]) qui spécifie une passerelle de service appelée OSG pour Open Service Gateway. Cette passerelle est destinée à fournir une capacité de pontage entre un réseau interne et un réseau externe, par exemple entre un réseau SOHO1 et internet ou encore entre un réseau domestique et internet (cf. Figure 1). Figure 1 Topologie ciblée par la JSR 008 Cette passerelle a deux buts : Servir de point d’accès à des partis tiers pour fournir des services ; Centraliser la gestion des différents équipements sur le réseau. De plus les différents éléments sur cette passerelle doivent fournir des API pour pouvoir les lier entre eux. Et d’autre part ils devraient suivre le principe de « zéro-administration ». Un exemple de scénario parmi d’autres visés par la JSR 008 Pour bien comprendre l’intérêt, prenons un exemple concret : EDF cherche à éviter la surcharge de la consommation nationale en fournissant un service de facturation incitatif. C’est-à-dire que le prix du kW/h varie un jour sur l’autre dans une fourchette préétablie. EDF s’engage à fournir la grille tarifaire 5 jours à l’avance au client. Par exemple un dimanche au printemps ne nécessite pas de chauffage électrique ni de climatisation, pas d’évènement particulier qui consomme de l’énergie au niveau national. Par conséquent le prix du 1 Small Office Home Office 2 kW/h sera faible ; les personnes devraient par conséquent faire leur lessive… Inversement imaginons un lundi particulièrement froid en hiver, la consommation nationale en électricité est élevée : chauffage, travail… Dans ce cas le prix du kW/h sera élevé, de cette manière les gens ne laisseront pas le chauffage allumé alors qu’ils seront au travail, ils reporteront leur lessive et le sèche-linge à un autre jour. Pour définir un tel scénario il faut trois éléments : Des sondes sur le panneau électrique qui remontent la consommation journalière ; Une infrastructure de remontée de données des consommations ; L’envoi de la grille tarifaire des 5 prochains jours au client en fonction de leur abonnement et de leur géolocalisation. Dans ce scénario une passerelle OSG est parfaite car la passerelle récupère les données sur les sondes et envoie périodiquement les données à EDF avec le numéro d’abonnement. Chaque jour EDF envoie les grilles tarifaires aux passerelles. Les clients accèdent aux tarifs en se connectant sur l’interface de la passerelle. Par conséquent, la passerelle embarque un module de communication pour les sondes, un module de communication pour la remontée de données vers EDF, un module d’interface pour les abonnés et finalement un ou plusieurs composants métier. Dans l’exemple ci-dessus, la passerelle sert de pont entre le compteur électrique et la remontée de données d’EDF. Elle fournit un point d’accès à EDF pour déployer des composants qui vont lui permettre de remonter des données ou de lui en envoyer. Et finalement, la passerelle permet de centraliser l’ensemble des composants nécessaires à l’abonné. Et OSGi dans tout ça ? OSGi est un projet issu de la JSR 008. Le consortium OSGi Alliance (cf. [2.]) a été fondé en mars 1999. Il est chargé de spécifier et de standardiser les fonctionnalités et les services des plates-formes d’exécution OSGi. Bien que déjà décrit dans la JSR, OSGi met l’accent sur la modularité des éléments qui constituent l’application aussi bien au niveau classe qu’au niveau objet, pour pouvoir reconfigurer l’architecture en exécution sans avoir à arrêter la passerelle. La modularité et la reconfigurabilité sont des propriétés recherchées dans des domaines autres que celui des passerelles : par exemple pour les serveurs d’applications. Conclusion Nous nous intéresserons dans ce document aux aspects de modularité et de reconfigurabilité fournis par OSGi. 3 3 La plate-forme OSGi OSGi spécifie une plate-forme d’exécution Java qui supporte le déploiement d’applications extensibles et téléchargeables nommées : bundle. Les équipements compatibles OSGi peuvent télécharger et installer ces bundles et les désinstaller lorsqu’ils n’en ont plus besoin. Cette plate-forme repose principalement sur trois couches en interaction (cf. Figure 2) : Bundle Bundle La couche module définit un modèle de modularisation pour Java ; La couche cycle de vie est basée sur la couche module. Elle définit l’API d’administration des bundles ainsi que les différents états dans lesquels peut être un bundle; La couche service est basée sur la couche cycle de vie. Cette couche définit un modèle de programmation ainsi que les mécanismes pour la conception, le développement et l’exécution d’applications « dynamiques » basées sur l’approche orientée service. Enregistre, Désenregistre,… Service Démarre, Arrête Cycle de vie Installe, Désinstalle Charge les classes Module Figure 2 Interactions entre les différentes couches Nous résumerons dans cette section ces trois couches. 3.1 Couche Module : Bundle La couche Module définit le concept de bundle à la fois comme unité de déploiement et comme unité de composition dans la plate-forme d’exécution. Dans OSGi le concept de bundle et celui de module sont similaires et sont donc interchangeables. Un bundle est une archive Java (Jar) qui peut contenir, outre des classes Java, différentes ressources. Ces ressources peuvent être diverses (cf. Figure 3) : GIF, PNG, fichier de propriétés ou d’autres conteneurs comme des archives Java (Jar) ou des fichiers Zip. De plus un bundle peut contenir aussi des librairies de code natif comme des dll ou des fichiers so… Il faut cependant différencier les classes Java, les archives Java et les librairies de code natif des autres ressources car celles-ci sont prises en charge par la plate-forme, alors que la gestion des autres ressources est à la charge du développeur du bundle. Dernier point : un bundle contient un ensemble de métadonnées utilisées par la plate-forme pour qu’elle puisse prendre en charge les différents aspects du bundle (cf. section 3.1.1). 4 Rôle d’unité de déploiement Un point important est que le bundle est une unité de déploiement, c’est-à-dire que c’est un élément « tangible » qui d’une part va pouvoir être copié et transféré ; mais qui d’autre part va servir à empaqueter les classes qui pourront ainsi être partagées, chargées et utilisées. .class .class Rôle d’unité de composition .dll, .so L’autre aspect d’un bundle est qu’il est utilisé comme unité de composition. C’est-à-dire qu’il va être utilisé avec d’autres pour définir une ou plusieurs MANIFEST.MF applications. Dans OSGi, cette composition peut se faire à deux niveaux : niveau classe et niveau objet. Dans cette section nous allons nous intéresser au niveau classe, le niveau objet sera quant à lui traité Figure 3 Module OSGi : Bundle dans les autres couches. Au niveau classe le Bundle permet de cloisonner les classes entre ce qui est propre (privé) à l’exécution du Bundle de ce qui sera partagé dans la composition. Cet aspect est basé sur les métadonnées fournies par le Bundle. Bundle Nous allons, dans un premier temps, aborder les métadonnées définies par OSGi, puis la prise en charge des classes et des librairies de code natif. 3.1.1 Métadonnées d’un bundle OSGi spécifie une spécialisation de l’archive Java pour son contexte d’exécution. De ce fait il réutilise le fichier de métadonnées (Manifest) défini par Java (cf. encadré ci-contre) pour y inscrire ses propres métadonnées. Les Manifests dans Java Les archives Java supportent de nombreuses fonctionnalités, comme les signatures électroniques, le contrôle de version et bien d’autres aspects. Ces fonctionnalités nécessitent des informations incluses dans l’archive Java : c’est le rôle du Manifest. Le Manifest (fichier MANIFEST.MF) est un fichier de métadonnées dans le répertoire META-INF de l’archive Java. Ce fichier contient au moins l’information de version du Manifest. Par défaut Java définit un ensemble de métadonnées : comme par exemple le nom du vendeur ou la version de l’archive. La plupart des métadonnées dépendent du contexte d’exécution et de la nature de l’archive. Pour plus d’information, référez-vous à http://java.sun.com/developer/Books/javapro gramming/JAR/basics/manifest.html. OSGi définit un certain nombre de métadonnées (cf. section 3.2.1 de http://www.osgi.org/download/r4v43/r4.core.pdf). Dans cette section nous n’allons pas décrire l’ensemble des propriétés, mais seulement celles qui sont liées au cycle de vie et à la gestion des classes/code. Gestion des classes/code Bundle-ClassPath : cette propriété est utilisée pour indiquer les chemins (path) vers les archives Java contenues dans le bundle. De cette manière, les classes et les ressources de ces archives embarquées pourront être utilisées. Par exemple : /lib/jms.jar. Nous aborderons à nouveau cette propriété dans la section 3.1.2. 5 Bundle-NativeCode : comme vu précédemment, un bundle OSGi peut embarquer du code natif. Cette propriété sert à la fois pour définir les chemins vers les librairies de code natif, ainsi que dans quels contextes d’exécution (e.g. : système d’exploitation, processeur…). Exemple : LibusbJava.dll;osname=WindowsXP;processor=x86. Nous aborderons à nouveau cette propriété dans la section 3.1.3. Bundle-RequiredExecutionEnvironnement : cette propriété indique le ou les environnements d’exécution qui doivent être présents sur la machine. Exemple : Java SE 6. Export-Package : cette propriété désigne les packages appartenant au bundle qui seront exportés. Nous aborderons à nouveau cette propriété dans la section 3.1.2. Import-Package : cette propriété désigne les packages requis par le bundle pour son utilisation. Nous aborderons à nouveau cette propriété dans la section 3.1.2. DynamicImport-Package : cette propriété désigne les packages qui seront chargés non pas à l’installation du Bundle mais au moment de leur utilisation. Par conséquent il se peut qu’il ait des dépendances de packages non résolues à l’exécution. Il faut donc utiliser cette propriété en dernier recours. Cycle de vie Bundle-Activator : cette propriété spécifie le nom de la classe utilisée pour l’activation et la désactivation du bundle. Nous aborderons à nouveau cette propriété dans la section 3.2.1. Bundle-ActivationPolicy : cette propriété définit la politique d’activation que doit suivre la plate-forme pour activer le bundle. Par défaut il existe deux politiques : o Par défaut eager : démarre le bundle et charge les classes lors de l’activation. o Lazy : diffère l’activation et le contexte d’exécution du bundle jusqu’à sa première utilisation (déclenchée par le chargement d’une ou plusieurs classes spécifiées). Bundle-UpdateLocation : cette propriété permet de spécifier une URL, à partir de laquelle on devrait (SHOULD) pouvoir mettre à jour le bundle. Export-Service : cette propriété est dépréciée (deprecated). Elle permettait d’indiquer les services que fournissait un bundle. Import-Service : cette propriété est dépréciée (deprecated). Elle permettait d’indiquer les services que requérait un bundle. Provided-Capability (R4.3) : Cette propriété signifie que le bundle fournit un ensemble de « capacités ». Require-Capability (R4.3) : Cette propriété signifie que le bundle requiert d’autres bundles pour fourni une « capacité ». Cette dépendance vers d’autres bundles est définie via un filtre LDAP. Require-Bundle : cette propriété spécifie que tous les packages exportés par un bundle ciblé doivent être importés. Fragment-Host : cette propriété définit quels sont les bundles « hôtes » pour ce fragment. 6 3.1.2 Gestion des classes dans OSGi L’un des principaux attraits d’OSGi est la modularité qui permet l’installation et la désinstallation de modules JAVA sans interruption de la plate-forme. Cette fonctionnalité est permise grâce à une gestion avancée des classes. Dans cette section nous allons nous intéresser à la gestion des class loaders (rappel : voir encadré ci-contre). OSGi base son mécanisme pour cacher et partager les packages sur deux points : un réseau de délégation de chargement de classes (cf. Figure 4) et des règles de visibilité de package. Réseau de délégation de class loader Chaque bundle a un unique class loader. Ce class loader peut charger des classes à partir de trois différentes sources de ressources : Boot class path : il contient les packages java.* et leurs implémentations ; Framework class path : la plate-forme OSGi a généralement un class loader séparé pour les classes d’implémentation de la plate-forme ainsi que pour certaines interfaces de service clé ; Espace de Bundles : cet espace consiste en l’ensemble des archives Java liées au bundle par une relation d’import ou de fragment. Qu’est-ce qu’un class loader en Java ? Rôle : La machine virtuelle Java (JVM) accède aux classes via les chargeurs de classe (class loader). Le but d’un class loader est de charger les classes à partir d’une source. De ce fait la machine virtuelle Java ignore la localisation des ressources : c’est aux class loaders d’aller les chercher dans les différentes ressources d’un système. Pour cela, Java se base sur un arbre de délégation de class loader dont les règles sont les suivantes : Tout class loader a un parent (sauf le chargeur initial) ; Tout class loader va d’abord chercher les classes à partir de son parent avant d’aller les chercher dans le CLASSPATH. Par défaut, la machine virtuelle Java possède trois class loaders hiérarchiques : 1. Le class loader initial : charge les classes standards de la machine virtuelle (rt.jar, core.jar…) 2. Le class loader d’extension : charge les classes du répertoire d’extension : /jre/lib/ext ; 3. Le class loader applicatif : charge les classes à partir des archives Java définies dans le CLASSPATH. Il est possible d’ajouter d’autres class loader pour prendre en charge des aspects particuliers dans le système (e.g. : URLClassLoader) Règles : a. Une classe est liée à son class loader et ne peut en changer ; b. Une classe est identifiée par son nom et son class loader ; c. Il ne peut pas y avoir deux classes ou plus ayant le même nom dans un class loader ; d. Une même classe peut être chargée par plusieurs class loaders ; e. Une classe ne peut être déchargée que si son class loader n’est plus référencé ; c’est-à-dire qu’il n’existe plus d’instance de cette classe. Bundle Class Loader Bundle Class Loader Framework Class Loader System Class Loader Bundle Class Loader Bundle Class Loader Import de Figure 4 Modèle de délégation de class loader 7 Visibilité des packages Le deuxième mécanisme mis en place dans OSGi est un mécanisme de visibilité de package. En effet, comme vu dans la sous-section Gestion des classes de la section 3.1.1, OSGi définit des métadonnées pour la gestion des packages qui sont : Ces métadonnées définissent deux types de visibilité : publics Import Package ; Export Package ; Private Package ; Bundle B V1.0 privés publics Bundle A Publique : les packages exportés ; Privée : les packages déclarés comme privés. Les packages publics peuvent donc être importés et utilisés par d’autres bundles. Par conséquent un bundle a accès à la fois aux classes de ses packages qu’ils soient publics ou privés, mais aussi à toutes les classes des packages publics qu’il importe. privés Espace de Classe de A publics privés Bundle C privés publics Espace de Classe de D Bundle D publics privés Bundle B V2.0 Prenons l’exemple de la Figure 5, le bundle A accède à la fois à ses classes mais aussi aux classes des packages publics Figure 5 Visibilité et espace de classe importés par A. Cet ensemble de classes auquel peut avoir accès le class loader du bundle A s’appelle espace de classe. Versionnement Le fait d’avoir un class loader par bundle permet d’avoir à l’exécution plusieurs versions d’un même package dans l'environnement d'exécution. La seule contrainte est bien évidemment qu’un bundle ne peut avoir accès qu’à une seule version (règle C des class loader dans Java, encadré précédent). OSGi suppose que les bundles ont une compatibilité ascendante et cherchera donc par défaut à résoudre les dépendances de packages à l’aide des dernières versions. Cependant il est possible de spécifier dans les métadonnées les versions que l’on souhaite utiliser pour résoudre la dépendance de package : Import-Package : org.apache.felix.ipojo;version= 1.4.0 Cela signifie que le bundle requiert le package org.apache.felix.ipojo ayant une version au moins égale à 1.4.0. Pour définir une version explicite il faut définir la propriété de la manière suivante : 8 Import-Package : org.apache.felix.ipojo;version= [1.4.0, 1.4.0] Cela signifie que le package doit avoir une version supérieure ou égale à 1.4.0 et être inférieure ou égale à 1.4.0 : donc être égale à 1.4.0. De manière plus générale OSGi définit la politique de version de la manière suivante : [Min,Max] [Min,Max) (Min,Max) (Min,Max] Version Min <= x <= Max Min <=x< Max Min < x < Max Min < x <= Max Version <= x En conclusion, comme le montre la Figure 5, il est possible d’avoir plusieurs versions d’un même package dans OSGi : le bundle A utilise les packages du bundle B version 1 alors que le bundle D utilise la version 2. Cependant, les bundles A et B ne doivent pas s’échanger des instances des classes venant respectivement des bundles B. Communication inter-bundle Rappelons une règle des class loaders : une classe est identifiée par son nom et son class loader. S’il existe une même classe dans plusieurs class loaders alors ces mêmes versions de classe seront considérées comme étant distinctes. Par conséquent pour pouvoir communiquer, deux bundles doivent se partager une même classe. C’est à ce moment que le mécanisme de réseau de délégation de class loader entre en jeu : un class loader va chercher dans son parent les classes à charger avant de les rechercher dans son classpath. Dans le cas d’OSGi, un bundle va d’abord chercher les classes à partir du class loader parent c’est-à-dire le système Java et ses extensions, puis à partir des class loaders des différents bundles appartenant à son espace de bundles (relation d’import/export de packages à l’exécution). Grâce à ce mécanisme d’import/export de package, les bundles peuvent s’échanger des classes provenant d’un unique class loader permettant ainsi la communication entre les bundles. Complétons l’exemple de la Figure Class loader C 5 à l’aide de la Figure 6: une instance de classe du bundle A envoie des messages loadClass(fr.imag.Bob.getName()) à une instance de classe du bundle D. Par conséquent A et D doivent avoir le Class loader A Class loader D même type de message et venir du même class loader. Dans le cas présent, les instances respectives de A et D vont loadClass(fr.imag.Bob.getName()) loadClass(fr.imag.Bob.getName()) charger la classe du message Figure 6 Exemple de délégation de chargement dans OSGi fr.imag.Bob à partir de leur class loader respectif qui délèguera au class loader du bundle C. Dynamicité Nous aurions pu avoir le cas, comme dans l’exemple de la Figure 7, où les classes servant à la communication appartiennent à l’un des deux bundles. Ce cas de figure est à éviter car nuit à la dynamicité de la plate-forme. 9 Explication : une instance de A souhaite communiquer avec une loadClass(fr.imag.Bob.getName()) instance de D, pour cela elle a chargé la classe Bob à partir de D. Maintenant on souhaite désinstaller D et installer un Class loader A Class loader D autre bundle qui fournit les mêmes fonctionnalités que D. il fournit aussi la classe Bob. Cependant le bundle A a loadClass(fr.imag.Bob.getName()) loadClass(fr.imag.Bob.getName()) déjà chargé la classe Bob et il n’est Figure 7 Exemple de délégation à éviter possible de le décharger que si on relâche le class loader de A ce qui revient à redémarrer le bundle A. La communication avec les instances du nouveau bundle n’est pas non plus possible car il utilisera sa classe Bob qui vient d’un autre class loader et qui par conséquent est un autre objet. Par conséquent dans OSGi, on sépare l’API pour la communication et son implémentation dans des bundles différents. 3.1.3 Gestion du code natif Java ne permet pas d’accéder à tous les aspects d’un système. Par conséquent OSGi spécifie la possibilité d’embarquer du code natif dans un bundle. L’idée sous-jacente est qu’il est parfois nécessaire d’utiliser des drivers natifs à un système. Cependant les programmes Java sont souvent portables entre les systèmes, ce qui n’est pas le cas du code natif. Sélection des librairies OSGi spécifie un mécanisme de sélection de librairie de code natif en fonction du système. Cette sélection se base sur cinq propriétés : Le nom du système d’exploitation (osname) : par exemple WindowsXP, Linux, Solaris ; La version du système d’exploitation (osversion) : par exemple 5.1 ; Le processeur (processor) : par exemple x86, mips, sparc ; Le langage (language) : par exemple en, fr ; Un filtre de sélection. Prenons l’exemple d’un bundle qui fait le pont d’USB2 vers OSGi. Le manifest de ce bundle contient la métadonnée suivante : Bundle-NativeCode : lib/LibusbJava.dll;osname=WindowsXP;osname=windows vista;osname=Windows 7;processor=x86, lib/libusbJava.so;osname=Linux;processor=x86 La métadonnée suivante dit que la librairie LibusbJava.dll présente dans le répertoire lib du bundle sera chargée si le système d’exploitation est un Windows soit XP, soit vista soit 7 et si c’est un processeur x86. Si le système d’exploitation est un Linux sur un processeur x86 alors la librairie libusbJava.so sera chargée. 2 http://websvn.ow2.org/filedetails.php?repname=chameleon&path=%2Fsandboxes%2Fmehdi%2FUSB-BridgeBundle%2Fpom.xml 10 Utilisation du code natif Grâce au mécanisme précédent il est possible d’utiliser du code natif en fonction du système sous-jacent. Cependant ces librairies ne seront accessibles qu’à partir de la plate-forme OSGi. En effet le but n’est pas de déployer ou de mettre à jour des pilotes ou des fonctionnalités du système sous-jacent, mais d’accéder et d’exploiter des ressources du système qui nécessitent des pilotes en code natif. L’exemple précédent en est une parfaite illustration : l’accès aux équipements USB n’est pas pris en charge par Java et nécessite des pilotes dépendants du système. Le but du bundle est de fournir un service d’annuaire d’équipements USB basé sur les pilotes USB pour Windows et Linux. Pour cela les deux librairies sont interfacées à l’aide de JNI (voir encadré Java Native Interface). En fait, lorsque le code appelle le système pour rechercher la librairie, OSGi retourne la librairie comme si elle était installée dans le système. Il y a tout de même une limitation : seul un unique class loader, donc un bundle, doit charger la librairie native pour pouvoir préserver la séparation des espaces de nom. En effet si plusieurs class loaders chargent la librairie alors il y aura un recouvrement des espaces de noms et causera donc une erreur de liaison. Java Native Interface (JNI) (http://java.sun.com/docs/books/jni/download/jni.pdf) Le but de JNI est de pouvoir incorporer du code natif écrit en C ou en C++ dans du code JAVA. JNI est fournie par défaut dans le JDK de Sun. La machine virtuelle Java (JVM) fournit donc JNI pour interfacer du code natif et du Java. Cette interface est bidirectionnelle : elle permet à des applications Java d’appeler du code natif et inversement. L’interface bidirectionnelle JNI peut supporter deux types de code natif : les libraires et les applications. JNI peut être utilisée pour écrire des méthodes natives qui seront appelées par du code Java. Inversement, JNI peut être utilisée pour embarquer une application Java et la JVM dans une application native. Il faut toutefois remarquer que dans une application Java dépendante de librairie native, seule la partie purement Java est portable d’un système à un autre. Il faudra donc interfacer pour chaque système les librairies natives correspondantes. Il faut donc bien circonscrire les librairies natives dans l’architecture de l’application et en limiter l’utilisation. 3.1.4 Conclusion La couche module définit le concept de bundle. Ce concept est à la base de la modularité et de la dynamicité de la plate-forme d’exécution OSGi. En effet chaque bundle a son class loader, ce qui fait que chaque bundle peut être vu comme étant une plate-forme ayant son espace de classe et son espace d’exécution. Cependant, c’est le mécanisme de délégation entre ces class loaders définis par OSGi qui permet le partage de classe et la communication entre ces espaces d’exécutions. En d’autres termes, OSGi se distingue des autres plates-formes d’exécution (serveur JavaEE…) par son mécanisme de réseau de délégation de class loaders qui permet la modularité et une dynamicité des applications. 11 3.2 Couche cycle de vie La couche cycle de vie dans OSGi fournit les fonctionnalités de sécurité et les opérations d’administration des bundles. Cette couche est basée sur celle de module. Dans cette section nous allons nous intéresser au cycle de vie d’un bundle, c’est-à-dire décrire les différents états par lesquels va passer un bundle de son installation à sa désinstallation, ainsi que les différentes opérations d’administration qui permettent ces changements. Nous allons dans un premier temps aborder les états et les opérations d’administration d’un bundle avant de décrire un certain nombre de mécanismes qui interviennent durant le cycle de vie. 3.2.1 États et opérations d’administration d’un bundle La Figure 8 définit le cycle de vie d’un bundle sous la forme d’un diagramme d’état. En phase de démarrage installe démarre désinstalle Désinstallé Installé désinstalle / rafraichit / met à jour Résolu résout rafraichit / met à jour Activé arrête En phase d’arrêt Figure 8 Cycle de vie du Bundle (Diagramme d'état) Installé Si un bundle est dans l’état installé c’est que l’installation a été effectuée avec succès. C’est-àdire que le bundle est valide (cf. section 3.12 de la spécification d’OSGi R4.3) et que la plate-forme lui a assigné un identifiant unique. Cette opération d’installation est atomique et persistante. A la fin de cette opération, un objet bundle est créé dans la plate-forme et servira à administrer le bundle jusqu’à sa désinstallation. Désinstallé Un bundle est désinstallé si sa représentation physique (persistance) a bien été supprimée et que ses différentes ressources logiques ont bien été déchargées de la plate-forme. Résolu Un bundle est résolu si toutes ses dépendances (package, capacité…) décrites dans le manifest ont été pourvues. 12 En phase de démarrage Durant la phase de démarrage d’un bundle, celui-ci est initialisé (appel de la méthode start de l’objet bundle). Une fois initialisé, le bundle sera automatiquement activé si la politique d’activation est eager. Dans le cas où la politique d’activation est lazy, alors l’activation ne se fera pas immédiatement. Une fois l’activation déclenchée, une notification est alors envoyée à la plate-forme OSGi. Dans le cas où une classe d’activation est spécifiée par la propriété Bundle-Activator (cf. section 3.1.1), celle-ci est alors instanciée, déclenchant l’instanciation du class loader du bundle. Une fois l’instance créée la plate-forme appelle la méthode start. Activé Un bundle est activé au moment de l’appel de la méthode start sur l’instance de la classe d’activation (définie par la propriété Bundle-Activator) si celle-ci existe. En phase d’arrêt Un bundle actif peut être désactivé. Lorsque celui-ci est désactivé alors la méthode stop de la classe d’activation est appelée et les contraintes suivantes doivent être respectées : tous les services et autres ressources (sauf les packages), qui étaient utilisés, doivent être relâchés par le bundle ; tous les threads du bundle devraient être immédiatement stoppés ; tous les services et listeners du bundle sont automatiquement désenregistrés de la plateforme OSGi. 3.2.2 Adaptation Depuis la version 4.3 de la spécification, un bundle peut être « adapté » en différent types. Cette fonctionnalité permet d’obtenir une vue spécifique du bundle, comme par exemple les liens entre les capacités des bundles. Le nombre de vues différentes n’est pas fixé par OSGi, cependant la spécification définit cinq vues : BundleRevision : cette vue permet d’accéder aussi bien aux déclarations de capacités fournies et requises, ainsi qu’au BundleWiring associé : c’est-à-dire les liens entre les bundles à l’exécution. Cette vue permet donc d’obtenir le graphe de dépendance à l’exécution en terme de capacité. Notez que d’une part BundleRevision renvoie la révision courante et d’autre part que cette vue n’a pas de sens pour un bundle désinstallé (pas de révision courante). BundleStartLevel : cette vue permet d’accéder et de modifier le niveau de démarrage du bundle. BundleWiring : cette vue permet d’accéder aux liens de capacité d’un bundle à l’exécution. Notez qu’un lien de capacité ne peut exister qu’à partir de l’état résolu (incluant donc les états : en phase de démarrage, en phase d’arrêt et activé). 13 FrameworkStartLevel : cette vue permet d’accéder et de modifier le niveau de démarrage du système. Notez qu’il n’est possible d’adapter à cette vue que le bundle système. FrameworkWiring : cette vue permet d’accéder aux dépendances des bundles. Elle permet notamment d’obtenir la fermeture transitive des dépendances d’un bundle donné en exécution. 3.2.3 Contexte du Bundle (Bundle Context) Comme vu dans la couche bundle (cf. section 3.1), un bundle possède son propre class loader, son espace de classe et d’une certaine manière son propre environnement d’exécution. Le BundleContext représente cet environnement d’exécution et agit comme un proxy de la plate-forme OSGi. Le BundleContext permet donc : D’installer de nouveaux bundles ; D’accéder aux informations des autres bundles ; De rechercher un service (cf. section 3.3) ; D’exposer un service (cf. section 3.3) ; De souscrire aux différentes sources d’événements de la plate-forme OSGi. Le BundleContext est créé à l’initialisation du bundle à la phase d’activation. 3.2.4 Événement Le niveau cycle de vie de la plate-forme OSGi définit deux types de notifications : Les événements relatifs aux bundles (BundleEvent) : la plate-forme OSGi émet des notifications à chaque changement d’état d’un bundle. Les événements relatifs à la plate-forme (FrameworkEvent) : la plate-forme OSGi émet des notifications pour chaque changement d’état du bundle système (la plate-forme elle-même), ainsi que pour chaque occurrence de logue (ERROR, INFO et WARNING) de la plate-forme. Comme dit précédemment les éléments d’un bundle peuvent souscrire à ces deux sources d’événements au travers du BundleContext. 3.2.5 Conclusion La couche cycle de vie définit les différentes opérations d’administration que l’on peut effectuer sur un bundle. Elle décrit les différents états par lesquels un bundle peut passer de son installation à sa désinstallation et des notifications associées. La version 4.3 de la spécification ajoute la notion de lien à l’exécution. Une partie de ces liens est basée sur les capacités définies dans la couche module. Ce nouvel aspect est très intéressant car cela permet de tracer le graphe de dépendance entre les éléments du système à l’exécution variant en fonction du cycle de vie des bundles. 14 3.4 Couche service Les deux couches précédentes : module et cycle de vie, permettent d’avoir une gestion « dynamique » des bundles ; c’est-à-dire qui ne nécessite pas le redémarrage de la plateforme. Cependant cette dynamicité des modules ne concerne que les classes. Ces deux couches ne sont pas suffisantes pour définir à elles seules des applications dynamiques ; en d’autres termes une gestion dynamique des bundles n’implique pas nécessairement une gestion dynamique des applications (niveau instance). Dans OSGi la couche service va permettre la définition d’applications dites dynamiques. Pour cela un bundle va exposer ses fonctionnalités (niveau instance) à d’autres bundles et inversement pouvoir utiliser celles des autres. Pour conserver la dynamicité il est nécessaire d’avoir un couplage lâche dans les dépendances de fonctionnalités. L’un des aspects les plus importants dans l’approche orientée service (cf. encadré ci-dessous) est le contrat (description du service). Le fournisseur utilise ce contrat pour définir ses fonctionnalités Approche Orientée Service L’approche orientée services [3.] est basée sur l’idée qu’une application peut être réalisée par composition des services logiciels mis à disposition par des fournisseurs divers ([4.] [5.] appellent ce modèle « service-based model of software »). Bien qu’il n’y ait pas de réel consensus sur la définition du concept de Service, les différentes définitions s’accordent sur le patron d’interaction. Il faut cependant remarquer que ce patron d’interaction n’est pas une réelle innovation en soi. En effet, il existait déjà, dans les applications distribuées, un niveau d’indirection entre les clients et les serveurs, où les clients passent par le biais d’un intermédiaire qui traduit le nom logique du serveur cible par l’adresse physique (cf. DNS, les courtiers ODP [6.]). Cependant l’approche orientée service pousse le raisonnement plus loin : on ne recherche plus un serveur/élément logiciel par rapport à son nom mais par rapport aux fonctionnalités qu’il fournit. En d’autres termes on recherche une fonctionnalité (un service) et non un fournisseur. Le patron d’interaction du SOC est basé sur trois acteurs : Les fournisseurs de services qui offrent des services Les consommateurs de services qui requièrent et utilisent des services offerts par des fournisseurs Un registre de services permettant aux fournisseurs de publier leurs services et aux consommateurs de découvrir et de sélectionner les services qu’ils veulent utiliser. Dans ce patron d’interaction, le fournisseur de service publie sa spécification auprès d’un courtier/registre de services. Lorsqu’un Consommateur de service requiert un service, celui-ci recherche, auprès d’un ou plusieurs registres de service, un service conforme à ses besoins ; une fois le service sélectionné, le consommateur interagit avec le fournisseur de ce dernier (cf. figure ci-dessous). Notons que seule la spécification du service est partagée entre les acteurs. 2. Découverte et sélection Client de service Registre de services 1. Publication Description de Service 3. Liaison Fournisseur de service 15 ainsi que les propriétés non-fonctionnelles associées. Les clients se basent sur les contrats pour sélectionner le ou les services qui conviennent à leurs besoins. Cet aspect de contrat de service permet de définir une frontière claire et propre entre les différents éléments logiciels de l’application. Par conséquent, puisque la recherche et l’utilisation d’un service se fait via le contrat, il est possible de substituer l’implémentation par une autre tant que ce dernier fournit au moins le contrat recherché. Par conséquent, OSGi utilise l’approche orientée service pour définir les applications : car d’une part cette approche fournit un couplage lâche entre le consommateur et le fournisseur de service, et d’autre part le registre permet la re-sélection et donc l’adaptation (substitution) à l’exécution de l’application. Dans cette section, nous aborderons le registre de services OSGi, puis les événements liés aux services, avant de nous intéresser à la définition d’application dans OSGi. Finalement nous aborderons brièvement les mécanismes spécifiés pour les services distants avant de conclure. 3.4.1 Registre de services La couche module permet de partager les classes entre les différents bundles, alors que la couche service va permettre de partager les instances des fonctionnalités (service) entre bundles. Ce partage d’instances est basé sur un registre de services partagé entre tous les bundle contexts. Un bundle peut être fournisseur et/ou consommateur de service. Nous allons dans la suite aborder les aspects fournisseur avant de voir les aspects consommateur Fournisseur de Service Pour enregistrer un service, un bundle doit fournir au registre les éléments suivants : La description des fonctionnalités : interface Java ; Le(s) point(s) d’invocation des fonctionnalités : la référence de la classe d’implémentation ; Les propriétés non-fonctionnelles : Tableau de propriétés Java (Properties.class). Lorsqu’un élément d’un bundle enregistre un service, celui-ci récupère une référence de l’enregistrement (ServiceRegistration). Cette référence sert à administrer le service : modifier les propriétés non-fonctionnelles et désenregistrer un service. Soit l’exemple de code suivant : Properties properties = new Properties(); //... ServiceRegistration serviceRegistration = bundleContext .registerService(MonInterfaceDeService.class.getName(), MonObjetDImplementationDuService, properties); Le développeur du bundle doit désenregistrer son ou ses services lorsqu’il est désactivé. Noter que certaines implémentations de plate-forme OSGi automatisent ce désenregistrement. Consommateur de Service Pour utiliser un service, un consommateur doit le rechercher. Il est possible de rechercher un service selon deux modes : actif ou passif. 16 En mode actif, le consommateur fait un appel explicite au registre de services pour récupérer une ou plusieurs références de services présents au moment de l’appel. Soit l’exemple de code suivant : ServiceReference[] refs = context.getServiceReferences(MonInterfaceDeService.class.getName(), null); En mode passif, le consommateur souscrit aux événements de la plate-forme concernant l’apparition, la disparition et la modification des services. De cette façon, le consommateur peut découvrir, sélectionner et utiliser un service au moment où il devient disponible ; ou sélectionner un nouveau service si le premier vient à ne plus être disponible. Soit l’exemple de code suivant : context.addServiceListener(this); //... public void serviceChanged(ServiceEvent event) { //... switch (event.getType()) { case ServiceEvent.REGISTERED: ServiceReference serviceRef = event.getServiceReference(); //... break; //... Dans la pratique il est conseillé d’utiliser les deux modes : à l’activation du bundle, résoudre par une recherche active les différents services requis, et souscrire aux notifications des services pour, d’une part, compléter les dépendances non résolues et d’autre part, détecter le départ d’un service. Lorsqu’un service est désenregistré, il est impératif que les consommateurs de ce service le relâchent (cf. encadré stale reference). Par conséquent ils doivent écouter les notifications de Stale Reference la plate-forme pour relâcher les services qui Une stale reference ou référence obsolète souhaitent partir. en français désigne dans OSGi une référence Java sur un objet qui soit appartient à un bundle stoppé, soit provient d’un service désenregistré ; dans les deux cas cet objet doit être déréférencé. Cependant Java ne fournit pas de mécanisme générique pour nettoyer les références obsolètes. Le déréférencement de ces objets est à la charge du développeur du bundle. Les références obsolètes sont potentiellement dangereuses car elles peuvent bloquer le déchargement des class loaders. Elles entrainent aussi une augmentation de la mémoire ainsi que des problèmes de mise à jour. Pour éviter de tels problèmes, il est fortement recommandé d’utiliser des intergiciels qui automatisent la dépendance des instances (cf. section 4). Référence de service Dans les deux exemples précédents, la plateforme OSGi retourne une référence de service et non le service lui-même. En effet rechercher un service ne signifie pas nécessairement vouloir l’utiliser, ce mécanisme permet donc d’éviter des dépendances de service non nécessaires. Pour utiliser un service, le consommateur demande au registre, via le contexte du bundle, le service ciblé par la référence. Soit l’exemple de code suivant : Objet service = bundlecontext .getService(maReferenceDeService); 17 Une fois que le service n’est ou ne doit plus être utilisé, le consommateur de service doit relâcher ce dernier. C’est-à-dire notifier au contexte du bundle que le service ne sera plus utilisé et supprimer les références Java conservées. Soit l’exemple de code suivant : bundlecontext.ungetService(maReferenceDeService); this.service = null; Au final que ce soit pour la recherche ou l’utilisation de service, il est recommandé, voir nécessaire, de souscrire aux notifications de la plate-forme : en effet la plate-forme OSGi permet la dynamicité des modules et des services. 3.4.2 Service Distant Jusqu’à la version 4.3 de la spécification d’OSGi, les services étaient considérés comme étant centralisés. Toutefois, certains mécanismes de distribution avaient été spécifiés dans le compendium (compilation des services standardisés pour OSGi). Cependant, la spécification de la plate-forme OSGi 4.3 définit des propriétés et des mécanismes que devraient implémenter les « fournisseurs de distribution » dans le cadre d’une distribution des services. Un fournisseur de distribution implémente un ou plusieurs protocoles de communication (e.g. : RMI, CORBA, SOAP…). Ces fournisseurs permettent à la fois d’exposer des services OSGi locaux sur le réseau, mais aussi de créer des proxies /représentations des services distants exposés par d’autres plates-formes OSGi. Après, le principe est relativement simple : un service OSGi expose dans ses propriétés les protocoles par lesquels il souhaiterait être exporté. Par exemple le service Bob voudrait être exporté en RMI et en SOAP, par conséquent il expose la propriété suivante : service.exported.configs=net.rmi, net.soap Ces propriétés sont utilisées par les « fournisseurs de distribution » pour les exposer. Ces services exposés peuvent donc être finalement « importés » par les autres. Il est à noter cependant certains points : Il est possible d’accéder à un service distant par plusieurs protocoles à la fois mais pas deux fois par le même. Par conséquent, soit les différents fournisseurs de distribution présents sur la même plate-forme implantent des protocoles distincts, soit il existe un mécanisme de gestion de conflits entre les fournisseurs de distribution. Le cycle de vie des services importés doit être synchronisé ou cohérent avec le cycle de vie du service correspondant. OSGi ne définit pas dans la spécification de la plate-forme les mécanismes de découverte des services distants, ni la liste des protocoles de communication autorisés et ni comment implémenter un fournisseur de distribution. En d’autres termes, OSGi ne spécifie que les grandes lignes / les principes de la distribution des services. 3.4.3 Application OSGi Dans les sections précédentes nous avons vu que la modularisation était aussi bien en terme de classes qu’en terme d’instances. Cette modularisation est à la base de la dynamicité fournie par la plate-forme. Vient donc la question légitime : comment définir des applications et leur dynamicité (niveau instance) dans une plate-forme OSGi? 18 Une caractéristique importante d’OSGi est qu’il n’y a pas de modèle d’application. En effet, Une question, voire une affirmation, qui l’utilisation d’un modèle d’application comme, par revient souvent est : OSGi est-il un modèle à exemple, un ADL (architecture description composant ? En effet bundle et composant language) se prête mal à l’informatique pervasive et partagent certaines similarités : ils modularisent le code, s’auto-décrivent, au cas d’utilisation abordé à l’origine par OSGi (cf. explicitent leurs dépendances… section 2 : OSGi : Histoire et Motivation). En effet, Cependant il faut bien voir qu’un bundle et dans ces cas d’utilisation, on cherche à réifier des un composant ont des natures et des buts bien services/équipements disponibles sur le réseau différents. Un bundle est au niveau classe : domestique ou d’entreprise dans la plate-forme ; or module, package, dépendance de classe… alors qu’un composant est du niveau instance : un ADL définit statiquement et par extension dépendance de fonctionnalité… l’architecture de l’application. Par conséquent si De plus : dans OSGi les dépendances de l’on souhaite prendre en charge la disponibilité de services sont implicites (les propriétés ces équipements, cela implique la modification de exported-package et imported-package ne sont pas en pratique utilisées) et d’autre part l’ADL. Or si c’est le contexte d’exécution qui définit le cycle de vie des services n’est pas équivalent le modèle de l’application alors ce modèle ne définit au cycle de vie de leur bundle. pas l’application qui doit s’exécuter mais celle qui En simplifiant, au niveau service/instance, est en train de s’exécuter : dans ce cas l’ADL n’est un bundle : pas un ADL mais une modélisation de l’architecture n’explicite pas ses dépendances ; ne fournit pas les interfaces de en exécution. De manière plus générale il est contrôle du cycle de vie des services ; difficile de définir un modèle par extension d’une n’a pas nécessairement un cycle de vie application car cela va à l’encontre d’une passerelle équivalent à celui de ses services. à service ouverte (open service gateway). Par En résume [7.], il faut penser qu’un bundle conséquent, la façon de définir une application traite de code statique et de dépendances à la compilation, alors qu’un composant traite du suivant la philosophie d’OSGi s’apparente à une niveau instance et de dépendances à composition par contrat où le dynamisme des l’exécution. dépendances est géré de façon programmatique dans le bundle. Conséquemment, on peut définir la notion d’application à deux niveaux : au niveau module et au niveau plate-forme. OSGi : modèle à composant ? Si on raisonne au niveau module, alors chaque bundle définit une micro-application qui peut interagir avec d’autres ; cette interaction est contractualisée et donc ne dépend pas d’une autre micro-application en particulier mais d’une fonctionnalité. De ce point de vue il y a autant d’applications que de bundles. Si on raisonne au niveau plate-forme, alors l’application peut être vue comme étant l’ensemble des bundles et des équipements réifiés en cours d’exécution. Selon ce point de vue, il n’y a qu’une et unique application sur la plate-forme. Nous pensons que la notion d’application dans OSGi se situe entre ces deux extrêmes. Dans tous les cas, dans OSGi la modification dynamique des dépendances se fait au niveau module et non au niveau passerelle. 3.5 Conclusion En conclusion, OSGi spécifie une plate-forme d’exécution pour des applications dynamiques. Cette plate-forme est basée sur trois couches (cf. Figure 9): 19 Bundle Bundle 1. La couche module définit le concept de bundle ainsi que les mécanismes nécessaires au partage des classes et des ressources. Cet aspect de module est à la base même du dynamisme dans OSGi. 2. La couche cycle de vie définit les différents états 3 par lesquels peux transiter OSGi 1 un bundle ainsi que les Figure 9 Les 3 mécanismes dans OSGi différentes opérations d’administration sur ces derniers. 3. La couche service définit les mécanismes d’interaction des instances entre les bundles. 2 Ces trois couches permettent le partage aussi bien des classes que des instances tout en maintenant un faible couplage entre les modules. Ces mécanismes permettent la modification de l’architecture en cours d’exécution. Les aspects de modularité et de dynamicité influent sur la définition d’une application dans OSGi. Par conséquent dans OSGi, une application est une composition de modules dont les interactions sont définies contractuellement ; de cette façon il est possible de substituer une interaction par une autre. Par contre, autant le partage des classes par la couche module est géré par la plate-forme, autant le partage des fonctionnalités en terme de services est à la charge des développeurs de bundle. Or nous avons vu que la gestion des dépendances par le bundle est un aspect critique pour le dynamisme de l’« application ». Cette gestion manuelle des dépendances est donc une source d’erreur : une erreur de gestion de dépendances d’un bundle (e.g. : stale reference,…) peut nuire à la dynamicité de toute la plate-forme. Il est donc recommandé d’utiliser des outils qui automatisent cette gestion. 20 4 Intergiciels Dans les sections précédentes nous avons abordé les mécanismes fournis par la plate-forme OSGi pour exécuter des applications « dynamiques ». Nous avons observé trois choses : La gestion du partage d’instance de service est manuelle et propre à chaque bundle. Cette gestion est un aspect critique du dynamisme de la plate-forme. Le cycle de vie des services n’est pas nécessairement équivalent au cycle de vie du bundle. En d’autres termes, un service peut apparaitre ou disparaitre alors que son bundle est toujours en état actif. Une application dans OSGi est une composition de bundle en interaction. Le problème soulevé par le premier point peut être résolu en automatisant la gestion des dépendances. Les deuxième et troisième points sont problématiques pour l’adaptation de l’environnement d’exécution, car d’une part il n’est pas possible d’agir sur un service en particulier (création, destruction…) et d’autre part la composition en terme de bundle se prête mal à la modélisation du but d’une application (e.g. : ADL). On aimerait pour cela avoir un modèle similaire au composant qui fournirait les opérations d’administration et un accès au cycle de vie des implémentations de services. L’approche à composant ne peut pas être réutilisée telle quelle car elle possède une limitation non négligeable dans le cas d’OSGi qui est la staticité de l’architecture de l’application. Dans [8.], Humberto Cervantes explique le fait qu’en général les implémentations de l’approche à composant sont limitées vis à vis de la possibilité de supporter la disponibilité dynamique car : Les applications sont assemblées en phase de conception et non à l’exécution. Dans les applications composées de façon déclaratives, le but de l’application est défini par extension et n’autorise donc pas les changements à l’exécution. Dans le cas d’une composition impérative, il est possible de reconfigurer l’application (création et destruction d’instance, établissement de connexion) mais seulement avec des classes de composants déjà disponibles durant la phase de conception. La disparition d’une classe de composant à l’exécution n’est pas une hypothèse de l’approche à composants. Pour combler cela, H. Cervantes propose d’introduire l’approche orientée service pour la communication entre les composants d’un modèle à composant. Il nomme cette approche : composant orienté service (Service-Oriented Component) [8.][9.]. En résumé, nous avons vu que développer directement des applications dynamiques sur OSGi peut être compliqué. Il est donc recommandé d’utiliser des intergiciels qui automatisent la gestion des dépendances. D’autre part, mettre en place des mécanismes d’adaptation est compliqué car l’administration des services est limitée et l’état courant de l’architecture en terme d’instances est incomplet. L’utilisation de l’approche composant orienté service permet de résoudre ces problèmes tout en préservant le dynamisme de la plate-forme. Dans cette section, nous allons brièvement expliquer l’approche à composant orienté service. Puis décrire brièvement trois intergiciels utilisant cette approche : Declarative Services, Blueprint et iPOJO. 21 4.1 COMPOSANT ORIENTE SERVICE L’approche composant orienté service est issue des travaux de H. Cervantes et de R. S. Hall qui le définissent de la manière suivante : “Service-Oriented Component model introduces concepts from service orientation into a component model. The motivation for such a combination emerges from the need to introduce explicit support for dynamic availability into a component model.” (cf. [8.].) La principale caractéristique des modèles à composant orienté service réside dans l’interaction entre les composants. En effet, dans cette approche les composants ne sont pas liés directement entre eux. Ils exposent leurs fonctionnalités sous la forme d’un ou plusieurs services et utilisent les fonctionnalités des autres composants au travers de ces services. En résumé, dans les modèles à composant orienté service le « binding » est remplacé par l’approche à service (publication, recherche, sélection, utilisation). La Figure 10 résume les différentes caractéristiques d’un composant orienté service : Interfaces de Service fournies : les fonctionnalités du composant sont exposées via une description, dans le cas de [8.], cette description est une interface Java. Dépendances de Service requises : les dépendances de fonctionnalité sont déclarées par le composant et prises en charge par le conteneur. La résolution de la dépendance du conteneur se fait suivant l’approche orientée service. Interface et dépendance de contrôle : les interfaces de contrôle permettent aux instances de composants de participer aux activités de reconfiguration dynamique. Propriétés de configuration : propriétés fonctionnelles du service. Propriétés de Service : propriétés exposées avec les interfaces de service Exposition et dépendances de ressources : ce sont des références à des ressources exposées qui peuvent être utilisées par d’autres composants. Dans le cas où elles sont requises, cellesci sont recherchées dans les ressources exposées par les autres composants. Interfaces de contrôle Propriétés de configuration Propriétés de services Interfaces des services requis Composant Interfaces des services fournis Ressources exportées Dépendances de ressource Figure 10 Composant Orienté Service défini par [8.] 22 La combinaison de l’approche à composant et celle des services permet aux développeurs de se concentrer sur le code fonctionnel / métier et de déléguer la gestion de la disponibilité dynamique ou du cycle de vie des services (composants) aux conteneurs. Dans certaines technologies à service comme OSGi, le code qui gère la disponibilité dynamique des services est assez complexe et est à la charge du développeur. L’utilisation de conteneurs inspirée des composants pour gérer des aspects non fonctionnels, va permettre de gérer, entre autres, la disponibilité dynamique, ce qui va simplifier la tâche des développeurs. 4.2 Intergiciels Composant Orienté Service Il existe plusieurs intergiciels au-dessus d’OSGi. Nous allons cependant nous intéresser brièvement à seulement trois d’entre eux qui utilisent l’approche composant orienté service : Declarative Services Un des intergiciels les plus simples. Declarative services a été standardisé dans le compendium d’OSGi dans la version 4. Cette spécification définit une approche pour la gestion des dépendances de service. Cette approche composant orienté service se concentre sur l’injection des dépendances de service et sur le contrôle du cycle de vie des composants. Blueprint Blueprint est issue de la plate-forme à composant Spring Dynamic Module. Blueprint cible les applications orientées entreprises hautement configurables. De fait, cet intergiciel fournit des mécanismes plus avancés que Declarative Services que ce soit pour le cycle de vie du composant (e.g. : politique d’instanciation) ou pour la gestion du dynamisme (e.g. : indirection des dépendances au travers d’un proxy). iPOJO Le modèle composant orienté service iPOJO [10.] pour injected Plain Old Java Object – est développé au-dessus de la plate-forme OSGi. Il est actuellement hébergé par le projet Apache Felix (une implémentation de la plate-forme OSGi). iPOJO est caractérisé principalement par deux aspects : il utilise l’injection de byte code pour instrumenter le conteneur du composant à la compilation ou à l’exécution ; il propose une approche qui permet d’ajouter facilement des aspects non-fonctionnels aux composants sans avoir à modifier le code métier. Notez que le modèle iPOJO applique le principe de la séparation de préoccupations à tous les aspects non-fonctionnels en se servant du concept de conteneur. Comparaison des intergiciels Le tableau ci-dessous (extrait de [7.]) résume les principales différences entre ces trois intergiciels. 23 Fonctionnalité Declarative Services BluePrint iPOJO Oui Non Non Oui Non Non Non Oui Oui Non Oui Oui Oui Non Oui Non Oui Oui Oui Oui Oui Oui Oui Oui Oui Non Non Oui Oui Oui Non Oui Oui Oui Oui Oui Non Non Oui Non Oui Oui Oui Oui Non Oui Oui Oui Non Oui Oui Oui Non Non Oui Non Non Oui Oui Oui Injection de dépendance Injection de « callback » Injection du constructeur Injection d’attribut Injection de « setter » Injection de proxy Injection de liste Injection d’objet nul Cycle de vie « callback » (activation/désactivation) Patron fabrique 3 Damping Synchronisation des attributs Contrôle du cycle de vie du composant Contrôle du cycle de vie du service Configuration Configuration des propriétés Configuration des attributs Administration de la configuration (Config Admin) Service Type des attributs customisable Approche des descriptions XML Annotations Java API 5 Référence [1.] JSR008 - http://jcp.org/en/jsr/detail?id=008 [2.] OSGi Alliance - http://www.osgi.org [3.] M. P. Papazoglou. « Service-oriented computing: concepts, characteristics and directions ». WISE 2003. Proceedings of the Fourth International Conference on Web Information Systems Engineering, 2003, 3-12 [4.] K. Bennett, P. Layzell, D. Budgen, P. Brereton, L. Macaulay et M. Munro. « Service-based software: the future for flexible software ». APSEC 2000. Proceedings. Seventh Asia-Pacific, Software Engineering Conference, 2000. 214-221 [5.] M. Turner, D. Budgen et P. Brereton. « Turning software into a service ». Computer, 2003, 36, 3844 [6.] J. Indulska, K. Raymond et M. Bearman. « A Type Management System for an ODP Trader » Proceedings of the IFIP TC6/WG6.1 International Conference on Open Distributed Processing II, North-Holland Publishing Co., 1994, 169-180 3 Signifie dans ce cas, faire une indirection de la dépendance au travers d’un proxy pour cacher et amortir le dynamisme du service cible. 24 [7.] R. S. Hall, K. Pauls, S. McCulloch et D. Savage. « OSGi in Action : Creating Modular Applications in Java ». Manning Publications. 2011 [8.] H. Cervantes. « Vers un modèle à composants orienté services pour supporter la disponibilité dynamique ». Thèse de l’Université Joseph Fourier, Grenoble, 2004 [9.] H. Cervantes et R. S. Hall. « Autonomous Adaptation to Dynamic Availability Using a ServiceOriented Component Model ». Proceedings of International Conference of Software Engineering (ICSE), IEEE Computer Society, 2004, 614-623 [10.] C. Escoffier. « iPOJO : Un modèle à composant à service flexible pour les systèmes dynamiques ». Thèse de l’Université Joseph Fourier, Grenoble, 2008 25