Dans le contexte de Santéclair, nous possédons déjà un environnement Java JEE sous Tomcat 7. Ce dernier est utilisé comme un serveur d'applications léger et fait très bien ce qu'on lui demande. La structure de Santéclair, ces besoins et ces effectifs ne nous permettent pas d'envisager le déploiement d'un autre serveur : nous maîtrisons bien Tomcat, nous l'utilisons depuis longtemps. Cela exclu donc de faire le choix d'OSGi comme socle du serveur, donc pas de Virgo (excellente solution si toutefois vous souhaitiez faire de l'OSGi et que vous n'avez pas de contraintes de déploiement)
Il nous reste donc la seconde solution : embarquer un environnement OSGi dans un Tomcat. Cette solution présente l'avantage de s'appuyer sur un socle robuste et largement éprouvé : Tomcat. De plus, nous verrons par la suite qu'il est possible de profiter des librairies déjà en place dans votre Tomcat (shared / WEB-INF/lib) en les exposant dans votre environnement OSGi.
Pour l'implémentation d'OSGi, notre choix s'est naturellement tourné vers felix. Ce dernier est édité par apache, il devrait donc bien s'entendre avec Tomcat. De plus, il est assez réputé dans la communauté OSGi et est intégré dans d'autres solutions embarquant de l'OSGi. Une autre possibilité aurait été Equinox (moteur OSGi d'Eclipse, si, si, Eclipse s'appuie sur OSGi) mais ce dernier semble plus lourd. Toutefois, si vous préférez cette implémentation, il existe ici un article qui explique comment embarquer un Equinox dans un Tomcat.
Le vif du sujet
Embarquer un environnement OSGi dans un Tomcat consiste à déployer un WAR qui sera charger de démarrer felix. Donc il vous faut d'abord un projet web. Les captures d'écrans et développement pour cet article seront réalisés sous Eclipse Kepler.
Tout d'abord les dépendances maven. Pour démarrer un felix, il faut felix et une dépendance OSGi compendium :
Tout d'abord les dépendances maven. Pour démarrer un felix, il faut felix et une dépendance OSGi compendium :
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
Enfin, comme nous sommes entre gens de bonne société, on ajoute les dépendances spring qui vont bien. Attention, ici pas de spring 4, uniquement du 3 pour des raisons de compatibilité avec les librairies gemini qui seront utilisées dans un prochain tuto :
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
Ok, on est prêt pour le coeur du probème. Ci dessous, vous trouverez l'organisation du projet :
On a donc le package "santeclair.portal" qui contient deux classes :
- L'interface FelixLauncher qui nous permet de décrire les méthodes qui seront appelées pour lancer et stopper felix
- La classe abstraite AbstractFelixLauncher qui implémente les comportements par défaut des signatures de l'interface (pas de JAVA 8 ici...)
Ensuite le package "santeclair.portal.config" qui contient :
- L'interface InitConfig qui décrit la maps que doit retourner les config possibles pour démarrer felix
- Plusieurs config type :
- FileInstallConfig : permet de démarrer ou de stopper automatiquement des bundles simplement avec le système de fichier
- FrameworkConfig : config initial du framework
- WebEmbeddedConfig : config permettant d'embarquer l'environnement OSGi dans un war
On aurait évidemment put faire un seul fichier de config qui aurait contenu tous les fichiers de config en un seul mais OSGi est un framework qui est à la fois le contenu et le contenant. Notre façon d'implémenter OSGi garde à l'esprit cette philosophie et la méthode d'implémentation présentée ici pourra être facilement adaptée à d'autres situations que de l'embarqué dans un tomcat.
Enfin, nous avons le moteur a proprement parlé dans le package "santeclair.portal.webapp". On y trouve :
- FelixLauncherWebappImpl : l'implémentation de notre classe abstraite FelixLauncher
- HostActivator : l'activateur par défaut qui permettra de créer le pont entre l'environnement OSGi et le classloader du war
- HostedFelix : un composant spring qui sera en charge de démarrer felix et de l'arrêter simplement grâce à des postConstruct et preDestroy.
La vérité est dans le code
Pour voir le code en question, rien de tel que de le visualiser sur github :
https://github.com/telligcirdec/osgiTomcatEmbedded/tree/master/osgi-embeddedLe point de départ de l'implémentation est la classe FelixLauncherWebappImpl :
Dans ce code, on définit trois variables d'instance importantes :
- props : cette variable contient les propriétés de démarrage que l'on fourni à felix. Dans notre cas ces propriétés sont mis à disposition par spring depuis un fichier de config nommé felix.properties
- rootDir : qui détermine le dossier racine de vos bundles systèmes, bundles applicatifs et fichiers de config.
- hostActivator : un activateur OSGi mère en quelque sorte qui permet de faire le pont entre votre classloader tomcat et ceux des bundles. C'est grace à ce bundle que vous allez pouvoir donner de la visibilité aux librairies qui seront embarquées dans votre WAR ou dans votre tomcat vers l'environnement felix.
L'objectif de cette classe est donc de récupérer les singletons qui l'intéressent et de fournir une liste de classe de configuration en leur fournissant les instances dont elles ont besoin.
La classe abstraite qui l'accompagne est quant à elle chargée de mettre en oeuvre le démarrage de felix :
Là, rien de mystérieux, on démarre felix en lui fournissant les éléments de configuration qui lui seront nécessaire. On peut cependant remarquer que toutes les propriétés fournies sont un ensemble de clés-valeur à l'exception du bundle système (HostActivator) dont on fournit directement l'instance.
Le reste n'est que de l’enchevêtrement de bean spring afin de fournir les instances à chacun des protagonistes, felix étant démarrer du fait de l'instanciation par spring de la classe HostedFelix qui contient des annotations @PostConstruct et @PreDestroy sur des méthodes respectivement en charge de démarrer et d'arrêter felix.
Il ne vous reste plus qu'à régler la propriété rootDir afin de définir l'endroit on vous souhaitez déployer vos bundles.
Voilà, si tout va bien vous avez un felix embarqué dans tomcat. Au démarrage, ce dernier devrait vous donner une liste de bundle installé. Ces bundles sont attendus dans le dossier {rootDir}/bundle mais encore une fois, ceci peut être modifié en jouant avec les fichires de config.
Le prochain article décrira comment mettre en place un système d'autodeploy de bundle avec le bundle felix.fileinstall (déjà en partie configuré dans ce tutoriel mais non déployé)
Là, rien de mystérieux, on démarre felix en lui fournissant les éléments de configuration qui lui seront nécessaire. On peut cependant remarquer que toutes les propriétés fournies sont un ensemble de clés-valeur à l'exception du bundle système (HostActivator) dont on fournit directement l'instance.
Le reste n'est que de l’enchevêtrement de bean spring afin de fournir les instances à chacun des protagonistes, felix étant démarrer du fait de l'instanciation par spring de la classe HostedFelix qui contient des annotations @PostConstruct et @PreDestroy sur des méthodes respectivement en charge de démarrer et d'arrêter felix.
Il ne vous reste plus qu'à régler la propriété rootDir afin de définir l'endroit on vous souhaitez déployer vos bundles.
Voilà, si tout va bien vous avez un felix embarqué dans tomcat. Au démarrage, ce dernier devrait vous donner une liste de bundle installé. Ces bundles sont attendus dans le dossier {rootDir}/bundle mais encore une fois, ceci peut être modifié en jouant avec les fichires de config.
Le prochain article décrira comment mettre en place un système d'autodeploy de bundle avec le bundle felix.fileinstall (déjà en partie configuré dans ce tutoriel mais non déployé)
