novembre
2009
Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour.
Dans le billet précédant [step3] nous avons mis en place les 3 bundles OSGi Client org.akrogen.gestcv.simpleosgiclient, Services org.akrogen.gestcv.services et Domain org.akrogen.gestcv.domain. Le service UserService est récupéré via la factory de services ServicesFactory qui est un singleton. OSGi met en avant le fait que l’on puisse lancer/stopper des bundles à chaud sans devoir arrêter le conteneur OSGi. Nous verrons dans ce billet que le lancement/arrêt de nos bundles est à ce stade obsolète et par la suite comment y remédier en utilisant le registre de services OSGi. Autrement dit ce billet aborde le registre de services OSGi en expliquant comment fournir/consommer le service UserService via ce registre. Je vous conseille de lire La plate-forme dynamique de services OSGi pour plus d’informations sur la notion de services OSGi.
Voici un schéma de ce que nous allons effectuer dans ce billet :
Ce schéma montre que :
- le bundle client org.akrogen.gestcv.simpleosgiclient consomme toutes les 5 sec le services UserService via la Thread FindAllUsersThread.
- la factory de services ServicesFactory a disparu. L’instance service UserServive est enregistré et récupéré via le registre de services OSGi (en utilisant ServiceTracker).
Dans ce billet nous montrerons pas à pas comment nous arrivons au choix de conception décrit ci-dessus :
- Dans la section start/stop bundle, le bundle client appele via une Thread toutes les 5 sec le service UserService via la factory ServicesFactory. Nous montrerons qu’avec le singleton ServicesFactory, l’arrêt du bundle services est obsolète. Vous pouvez télécharger l’ensemble des projets expliqués dans cette section sur org.akrogen.gestcv_step4-thread.zip.
- Dans la section OSGi Services Registry, le bundle service et client utilisent le registre de services OSGi pour fournir/consommer le service UserService. Nous montrerons qu’avec l’utilisation du registre de services OSGi, l’arrêt du bundle service a une influence. Vous pouvez télécharger l’ensemble des projets expliqués dans cette section sur org.akrogen.gestcv_step4-osgiservicesregistry.zip.
- Dans la section ServiceTracker, le ServiceTracker OSGi est utilisé dans le bundle client pour consommer le service UserService enregistré (par le bundle service) dans le registre de services OSGi. Ce tracker évite de solliciter tout le temps le registre de services OSGi pour récupérer le service UserService. Vous pouvez télécharger l’ensemble des projets expliqués dans cette section sur org.akrogen.gestcv_step4-servicetracker.zip.
start/stop bundle
Avec OSGi, il est possible de lancer/stopper (commande start/stop), installer/désinstaller (commande install/uninstall) des bundles. A mes débuts d’OSGi je pensais par exemple que l’arrêt (ou la désinstallation) d’un bundle déchargeait les classes et que l’on pouvait ainsi arrêter n’importe quel bundle. Mais ce n’est pas le cas et nous allons montrer par l’exemple ci dessous que l’arrêt ou la desinstallation d’un bundle n’a aucun effet avec notre architecture mise en place à ce stade.
Dans cette section, le bundle client org.akrogen.gestcv.simpleosgiclient va afficher toutes les 5 secondes dans la console OSGi, la liste de Users via le service UserService récupérer par la factory ServicesFactory. Nous verrons que l’arrêt ou la désinstallation du bundle service org.akrogen.gestcv.services n’aurra aucun effet une fois qu’il est lancé (la liste des Users continuera à s’afficher). Vous pouvez télécharger org.akrogen.gestcv_step4-thread.zip qui contient le code expliqué ci-dessous.
Voici un schéma de ce que nous allons effectuer dans cette section :
Ce schéma montre que :
- l’appel de de UserService #findAllUsers() s’effectue via la Thread org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread. Cette Thread appelera toutes les 5 sec le service.
- cette Thread est initialisée et lancée (FindAllUsersThread#start()) dans la méthode start du BundleActivator.
- cette Thread est arrétée (FindAllUsersThread#interrupt()) dans la méthode stop du BundleActivator.
Nous utilisons une Thread pour appeler le service UserService car si nous avions appelé directement le service UserService toutes les 5 sec dans la méthode start du BundleActivator, le bundle serait resté toujours à l’état start.
FindAllUsersThread/ServicesFactory
Créez la classe org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread comme suit :
import java.util.Collection;
import org.akrogen.gestcv.domain.User;
import org.akrogen.gestcv.services.ServicesFactory;
import org.akrogen.gestcv.services.UserService;
public class FindAllUsersThread extends Thread {
private static final long TIMER = 5000;
@Override
public void run() {
while (super.isAlive()) {
try {
// 1. Get UserService
UserService userService = getUserService();
if (userService != null) {
// 2. Display users by using UserServive
displayUsers(userService);
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
sleep(TIMER);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private UserService getUserService() {
System.out
.println("--- Get UserService from singleton ServicesFactory ---");
return ServicesFactory.getInstance().getUserService();
}
private void displayUsers(UserService userService) {
Collection<User> users = userService.findAllUsers();
for (User user : users) {
System.out.println("User [login=" + user.getLogin() + ", password="
+ user.getPassword() + "]");
}
}
}
Dans ce cas ci le service UserService est récupéré par la factory ServicesFactory :
ServicesFactory.getInstance().getUserService()
Activator client – FindAllUsersThread/ServicesFactory
Ici nous allons initialiser et lancer la Thread FindAllUsersThread dans le BundleActivator client. pour cela, modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.Activator comme suit :
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
private FindAllUsersThread findAllUsersThread = null;
public void start(BundleContext context) throws Exception {
System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
// Start Thread which call UserService#findAllUsers();
findAllUsersThread = new FindAllUsersThread();
findAllUsersThread.start();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
// Stop Thread which call UserService#findAllUsers();
findAllUsersThread.interrupt();
findAllUsersThread = null;
}
}
Relancez (via GestCV OSGi ) Equinox et la console OSGi doit afficher toutes les 5 sec ceci :
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Tappez ss (Short Status) dans la console pour afficher les ID des bundles, la console affiche :
0 ACTIVE org.eclipse.osgi_3.5.0.v20090520
...
3 ACTIVE org.akrogen.gestcv.domain_1.0.0.qualifier
4 ACTIVE org.akrogen.gestcv.services_1.0.0.qualifier
5 ACTIVE org.akrogen.gestcv.simpleosgiclient_1.0.0.qualifier
...
Ici le bundle service org.akrogen.gestcv.services_1.0.0.qualifier est associé à l’ID 4. Stoppez ce bundle en saisissant dans la console stop 4, la console affiche :
Stop Bundle [org.akrogen.gestcv.services]
Pour vérifier que le bundle service est bien arrêté, tappez ss, la console affiche :
...
3 ACTIVE org.akrogen.gestcv.domain_1.0.0.qualifier
4 RESOLVED org.akrogen.gestcv.services_1.0.0.qualifier
...
Le bundle org.akrogen.gestcv.services_1.0.0.qualifier est à l’éta RESOLVED, ce qui signifie qu’il n’est pas démarré. Cependant la console OSGi continue à afficher la liste des User toutes les 5 sec, alors que le bundle service est arrêté.
Désinstaller org.akrogen.gestcv.services via la commande uninstall 4. Pour vérifier que le bundle service est bien désinstallé, tappez ss, et vous verrez que le bundle ne s’affiche plus dans la liste.
La console OSGi continue à afficher la liste des User toutes les 5 sec, alors que le bundle service est désinstallé. Ceci montre bien que les commandes stop et uninstall ne décharge pas les classes. Mais à quoi sert les commandes start/stop et install/uninstall?
OSGi Services Registry
A ce stade notre service est récupéré via le singleton ServicesFactory qui rend obsolète l’arrêt ou la désinstallation du bundle services dans lequel il est. OSGi propose un mécanisme de registre de services OSGi ou n’importe quel bundle peut enregistrer (fournir) un service et n’importe quel bundle peut récupérer (consommer) un service. Grossièrement le registre de services OSGi peut être comparé à une Map dans laquelle on peut enregistrer/retrouver un service via un identifiant (généralement le nom de l’interface du service). Les services qui sont enregistrés dans le registre de services OSGi est une classe Java classique.
Dans cette section nous allons utiliser ce registre de services OSGi pour enregistrer l’instance UserServiceImpl avec comme identifiant le nom de l’interface qu’elle implémente UserService.class.getName(). Nous verrons ensuite les avantages d’utiliser ce registre de services OSGi au lieu du singleton ServicesFactory. Spring DM utilise d’ailleurs ce registre de services OSGi pour enregistrer des services déclarés dans un fichier XML spring. Vous pouvez télécharger org.akrogen.gestcv_step4-osgiservicesregistry.zip qui contient le code expliqué ci-dessous.
Voici un schéma de ce que nous allons effectuer dans cette section :
Ce schéma montre que :
- le bundle services joue le rôle de fournisseur de services en enregistrant son instance UserServiceImpl dans le registre de services OSGi.
- le bundle client joue le rôle de consommateur de services en récupérant le service UserService enregistré dans le registre de services OSGi.
- la classe ServicesFactory n’est plus exposée.
Founisseur de services
Dans le bundle services org.akrogen.gestcv.services, la classe ServicesFactory à ce stade est accéssible par n’importe quel bundle pour pouvoir récupérer une instance de UserService. En utilisant le registre de services OSGi, cette classe ne doit plus être accéssible. Pour cela, déplacer la classe ServicesFactory dans le package org.akrogen.gestcv.services.internal.
Nous allons ensuite enregistrer l’instance UserServiceImpl dans le registre de services OSGi via l’identifiant UserService.class.getName(). Pour cela, modifiez la classe org.akrogen.gestcv.services.internal.Activator comme suit :
import org.akrogen.gestcv.services.UserService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public class Activator implements BundleActivator {
private ServiceRegistration reg = null;
public void start(BundleContext context) throws Exception {
System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
// Register UserService instance into OSGi Services Registry
reg = context.registerService(UserService.class.getName(), ServicesFactory.getInstance().getUserService(), null);
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
// Unregister UserService instance from OSGi Services Registry
if (reg != null) {
reg.unregister();
reg = null;
}
}
}
Consommateur de services
Dans le bundle client org.akrogen.gestcv.simpleosgiclient, nous devons récupérer le service UserService dans le registre de services OSGi via l’identifiant UserService.class.getName(). Pour cela, modifiez la méthode start de la classe org.akrogen.gestcv.services.internal.Activator comme suit :
System.out.println("Start Bundle ["+ context.getBundle().getSymbolicName() + "]");
System.out.println("--- Get UserService from OSGi services registry ---");
ServiceReference ref = context.getServiceReference(UserService.class.getName());
if (ref != null) {
UserService userService = (UserService) context.getService(ref);
if (userService != null) {
//Display Users
Collection<User> users = userService.findAllUsers();
for (User user : users) {
System.out.println("User [login=" + user.getLogin() + ", password=" + user.getPassword() + "]");
}
} else {
System.out.println(" Cannot get UserService=> UserService is null!");
}
} else {
System.out.println(" Cannot get UserService=> ServiceReference for UserService is null!");
}
}
Relancez (via GestCV OSGi ) Equinox et la console OSGi affiche ceci :
Start Bundle [org.akrogen.gestcv.services]
Start Bundle [org.akrogen.gestcv.simpleosgiclient]
--- Get UserService from OSGi services registry ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Cette trace montre que le services UserService a bien été consommé par le bundle client.
start/stop bundle – OSGi Services Registry
Ici nous allons ré-utiliser la Thread FindAllUsersThread pour consommer le service UserService récupéré toutes les 5 sec du registre de services OSGi.
FindAllUsersThread – OSGi Services Registry
Nous allons modifier la Thread FindAllUsersThread pour qu’elle recherche l’instance UserService dans le registry de services OSGi. Pour cela modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread comme suit :
import java.util.Collection;
import org.akrogen.gestcv.domain.User;
import org.akrogen.gestcv.services.UserService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
public class FindAllUsersThread extends Thread {
private static final long TIMER = 5000;
private BundleContext context;
public FindAllUsersThread(BundleContext context) {
this.context = context;
}
@Override
public void run() {
while (super.isAlive()) {
try {
// 1. Get UserService
UserService userService = getUserService();
if (userService != null) {
// 2. Display users by using UserServive
displayUsers(userService);
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
sleep(TIMER);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private UserService getUserService() {
System.out.println("--- Get UserService from OSGi services registry ---");
ServiceReference ref = context.getServiceReference(UserService.class.getName());
if (ref != null) {
UserService userService = (UserService) context.getService(ref);
if (userService != null) {
return userService;
}
System.out.println(" Cannot get UserService=> UserService is null!");
} else {
System.out.println(" Cannot get UserService=> ServiceReference for UserService is null!");
}
return null;
}
private void displayUsers(UserService userService) {
Collection<User> users = userService.findAllUsers();
for (User user : users) {
System.out.println("User [login=" + user.getLogin() + ", password=" + user.getPassword() + "]");
}
}
}
La Thread attend dans son constructeur le contexte du Bundle :
this.context = context;
}
qui est ensuite utilisé pour rechercher le service :
ServiceReference ref = context.getServiceReference(UserService.class.getName());
Activator client – FindAllUsersThread/OSGi Services Registry
Modifiez dans le bundle client, la classe org.akrogen.gestcv.simpleosgiclient.internal.Activator pour utiliser la Thread FindAllUsersThread et lui passer le contexte du Bundle :
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
private FindAllUsersThread findAllUsersThread = null;
public void start(BundleContext context) throws Exception {
System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
// Start Thread which call UserService#findAllUsers();
findAllUsersThread = new FindAllUsersThread(context);
findAllUsersThread.start();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
// Stop Thread which call UserService#findAllUsers();
findAllUsersThread.interrupt();
findAllUsersThread = null;
}
}
Relancez (via GestCV OSGi ) Equinox et la console OSGi affiche ceci toutes les 5 sec :
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Ceci montre que le service UserService est appelé toutes les 5 sec. Arrêter le bundle services via la commande stop 4. La console OSGi affiche ceci toutes les 5 sec :
Cannot get UserService=> ServiceReference for UserService is null!
Relancez le bundle services via la commande start 4, la console OSGi réaffiche a nouveau la liste de Users :
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Ici nous avons montré que l’utilisation du registre de services OSGi permet de rendre opérationnel le lancement/arrêt du bundle services. Autrement dit si ce bundle est arrêté, le service UserService n’est plus disponible et si il est à nouveau lancé, le service UserService redevient disponible.
ServiceTracker
Il est aussi possible de récupérer un service via le ServiceTracker d’OSGi. Je ne vais pas rentrer dans le détail de cette classe, mais l’idée de celle-ci est qu’elle est capable de surveiller les enregistrements/désenregistrements d’un service donné. Cette classe permet d’éviter de solliciter le registre de service OSGi pour récupérer un service comme ce que nous avons fait avant :
if (ref != null) {
UserService userService = (UserService) context.getService(ref);
...
}
Avec le ServiceTracker, il faut l’initialiser comme ceci :
ServiceTracker userServiceTracker = new ServiceTracker(context, UserService.class .getName(), null);
userServiceTracker.open();
Puis pour récupérer le service UserService (que nous effectuons dans la Thread) il faut faire :
UserService userService = (UserService) userServiceTracker.getService();
Vous pouvez télécharger org.akrogen.gestcv_step4-servicetracker.zip qui contient le code expliqué ci-dessous.
Activator – ServiceTracker
Dans le bundle org.akrogen.gestcv.simpleosgiclient, importez le package org.osgi.util.tracker. Le MANIFEST.MF doit avoir son Import-Package comme suit :
org.osgi.util.tracker;version="1.4.2"
Modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.Activator pour initialiser un ServiceTracker sur UserService et passer l’instance ServiceTracker dans la Thread FindAllUsersThread :
import org.akrogen.gestcv.services.UserService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
private ServiceTracker userServiceTracker = null;
private FindAllUsersThread findAllUsersThread = null;
public void start(BundleContext context) throws Exception {
System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
// Create and open the MovieFinder ServiceTracker
userServiceTracker = new ServiceTracker(context, UserService.class.getName(), null);
userServiceTracker.open();
// Start Thread which call UserService#findAllUsers();
findAllUsersThread = new FindAllUsersThread(userServiceTracker);
findAllUsersThread.start();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
// Stop Thread which call UserService#findAllUsers();
findAllUsersThread.interrupt();
findAllUsersThread = null;
// Close the MovieFinder ServiceTracker
userServiceTracker.close();
}
}
FindAllUsersThread – ServiceTracker
Modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread pour utiliser le ServiceTracker (au lieu du BundleContext) pour récupérer le service UserService :
import java.util.Collection;
import org.akrogen.gestcv.domain.User;
import org.akrogen.gestcv.services.UserService;
import org.osgi.util.tracker.ServiceTracker;
public class FindAllUsersThread extends Thread {
private static final long TIMER = 5000;
private ServiceTracker userServiceTracker;
public FindAllUsersThread(ServiceTracker userServiceTracker) {
this.userServiceTracker = userServiceTracker;
}
@Override
public void run() {
while (super.isAlive()) {
try {
// 1. Get UserService
UserService userService = getUserService();
if (userService != null) {
// 2. Display users by using UserServive
displayUsers(userService);
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
sleep(TIMER);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private UserService getUserService() {
System.out.println("--- Get UserService from OSGi services registry with ServiceTracker ---");
UserService userService = (UserService) userServiceTracker.getService();
if (userService != null) {
return userService;
}
System.out.println(" Cannot get UserService=> UserService is null!");
return null;
}
private void displayUsers(UserService userService) {
Collection<User> users = userService.findAllUsers();
for (User user : users) {
System.out.println("User [login=" + user.getLogin() + ", password=" + user.getPassword() + "]");
}
}
}
La Thread attend dans son constructeur l’instance ServiceTracker :
this.userServiceTracker = userServiceTracker;
}
qui est ensuite utilisé pour rechercher le service :
UserService userService = (UserService) userServiceTracker.getService();
Relancez (via GestCV OSGi ) Equinox et la console OSGi doit afficher ceci :
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
qui montre que le service est récupéré dans le registre de services OSGi via le ServiceTracker.
Conclusion
Dans ce billet nous avons montrer comment consommer/fournir le service UserService via le registre de services OSGi avec ServiceTracker. Utiliser le registre de services OSGi permet ensuite de bénéficier du lancement/arrêt des bundles à chaud (sans devoir redémarrer le conteneur OSGi). Cette fonctionnalité est très intéressante, car elle permet par exemple d’installer et lancer un bundle « patch » qui corrigerait un bundle buggé sans devoir stopper le conteneur OSGi. Je souhaitais dans un premier temps montrer en évidence ce scénario, mais l’architecture actuelle de nos bundles ne le permet pas, car le bundle org.akrogen.gestcv.services doit être scindé en 2 bundles API et Implémentation que nous expliquerons dans le prochain billet.
Articles récents
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step4]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step3]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step2]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step1]
Commentaires récents
- Conception d’un Editeur Eclipse de workflow XML [step 0] dans
- Conception d’un Editeur Eclipse de workflow XML [step 19] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans