Développement OSGi pour serveur Karaf – Part 2

image_pdfimage_print

Dans la première partie Développement OSGi pour serveur Karaf – Part 1, nous avons vu comment créer un bundle de base et comment le déployer facilement dans Karaf en permettant un redéploiement à la volée en cas de repackaging de notre bundle.

Dans cette partie nous allons mettre en place une logique d’application afin de mettre en place quelques concepts de base.

karaf-logo

Le cas pratique

Afin de pouvoir mettre en place une logique d’application, nous allons travailler en utilisant comme base un cas pratique que j’appellerai “SPOUT”. Il s’agira de mettre en place une plateforme de vente/échange de spiritueux et de vins.

La première partie de cette plateforme se composera d’une interface minimale d’insertion unitaire, d’une interface minimale de recherche (listing par critère “type”) et de la mise en place des bundles de bases de notre application.

  • model : Contenant l’interface du service de persistance et l’objet représentant notre produit (spiritueux ou vin)
  • persistence : Contenant l’implémentation du service mentionné précédemment (logique de sauvegarde et de recherche)
  •  ui : Contenant les classes permettant de construire l’interface.

NB. : L’ensemble de ces composants sera modifié au fur et à mesure des parties afin d’utiliser différents composants susceptibles de s’approcher du cahier d’acceptance d’une telle application en milieu professionnel. Notre projet contiendra donc 3 bundles donc au moins 3 projets eclipse/maven. Je décomposerai tout ceci en 5 parties :

  1. Un fichier pom.xml servant de parent aux différents projets
  2. Un projet “model” contenant mes sources et le pom.xml
  3. Un projet “persistence” contenant mes sources et le pom.xml
  4. Un projet “ui” contenant mes sources et le pom.xml
  5. Un projet “features” contenant un fichier ressource “features.xml” et le pom.xml.
Soit l’arborescence suivante :
  • spout
    • pom.xml
    • features
      • pom.xml
      • src
    • model
      • pom.xml
      • src
    • persistence
      • pom.xml
      • src
    • web
      • pom.xml
      • src

Nous reviendrons un peu plus tard sur ce dernier projet afin d’expliquer le pourquoi de ce fichier “features.xml” et son utilité dans le context Karaf.

Le pom parent

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>fr.conceptit.tuto</groupId>
	<artifactId>spout</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>pom</packaging>

	<name>Spout :: Parent</name>
	<description>Spout Parent</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	</properties>

	<modules>
		<module>model</module>
		<module>persistence</module>
		<module>ui</module>
		<module>features</module>
  </modules>

	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.felix</groupId>
					<artifactId>maven-bundle-plugin</artifactId>
					<version>2.3.7</version>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

Le model

Le model ressemble beaucoup à l’exemple utilisé dans la partie précédente (Part 1). Nous allons cependant revenir sur quelques points :

  1. L’Activator n’est pas un élément indispensable d’une application pour Karaf … donc nous allons nous en passer.
  2. Cela va permettre de simplifier au passage le pom.xml qui ne contiendra plus que les lignes suivantes :
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<parent>
    		<groupId>fr.conceptit.tuto</groupId>
    		<artifactId>spout</artifactId>
    		<version>1.0-SNAPSHOT</version>
    	</parent>
    
    	<groupId>fr.conceptit.tuto.spout</groupId>
    	<artifactId>spout-model</artifactId>
    	<packaging>bundle</packaging>
    
    	<name>Spout :: Model :: Bundle</name>
    	<description>spout-model OSGi blueprint bundle project.</description>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.apache.felix</groupId>
    				<artifactId>maven-bundle-plugin</artifactId>
    				<extensions>true</extensions>
    				<configuration>
    					<instructions>
    						<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
    						<Bundle-Version>${project.version}</Bundle-Version>
    					</instructions>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    
  3. Nous définissons l’objet Spirit de la façon suivante : Spirit
  4. Et l’interface de service : SpiritServiceImpl

La persistance

La persistance sera faite dans un premier temps dans une liste du type <ArrayList>. Nous verrons dans une prochaine étape comment apporter une persistance plus intéressante  et surtout plus … persistante. Pour le moment, nous allons implémenter notre interface de la façon suivante :

SpiritServiceImpl

package fr.conceptit.tuto.spout.service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import fr.conceptit.tuto.spout.model.Spirit;
import fr.conceptit.tuto.spout.model.SpiritService;

public class SpiritServiceImpl implements SpiritService {
	private List cellar = new ArrayList();

	public SpiritServiceImpl() {
		cellar.add(createSpirit("Kirsch", "simple", 12.20f));
		cellar.add(createSpirit("Cognac", "simple", 12.20f));
		cellar.add(createSpirit("Armagnac", "simple", 12.20f));
		cellar.add(createSpirit("Vodka", "simple", 12.20f));
		cellar.add(createSpirit("Whisky", "simple", 12.20f));

		cellar.add(createSpirit("Pastis", "compose", 12.20f));
		cellar.add(createSpirit("Pontarlier-Anis", "compose", 12.20f));
		cellar.add(createSpirit("Anisette", "compose", 12.20f));
		cellar.add(createSpirit("Ouzo", "simple", 12.20f));
		cellar.add(createSpirit("Gin", "simple", 12.20f));
	}

	public boolean insert(Spirit spirit) {
		cellar.add(spirit);
		return true;
	}

	public List findByType(String type) {
		List result = new ArrayList();

		if (type != null) {
			Iterator iter = cellar.iterator();
			while (iter.hasNext()) {
				Spirit spirit = (Spirit) iter.next();
				if (spirit.getType().contains(type) || "*".equals(type)) {
					result.add(spirit);
				}
			}
		}

		return result;
	}

	private Spirit createSpirit(final String name, final String type, final float price){
		Spirit spirit = new Spirit();
		spirit.setName(name);
		spirit.setType(type);
		spirit.setPrice(price);

		return spirit;
	}
}

De cette façon nous avons une liste d’entités sur laquelle faire une insertion basique et une recherche par type de spiritueux. Il nous reste à remplir le pom.xml avec les infos suivantes :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>fr.conceptit.tuto</groupId>
		<artifactId>spout</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<groupId>fr.conceptit.tuto.spout</groupId>
	<artifactId>spout-persistence</artifactId>
	<packaging>bundle</packaging>

	<name>Spout :: Persistence :: Bundle</name>
	<description>spout-persistence OSGi blueprint bundle project.</description>

	<dependencies>
		<dependency>
			<groupId>fr.conceptit.tuto.spout</groupId>
			<artifactId>spout-model</artifactId>
			<version> ${project.version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<extensions>true</extensions>
				<configuration>
					<instructions>
						<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
						<Bundle-Version>${project.version}</Bundle-Version>
					</instructions>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Maintenant il nous faut exposer notre service aux autres bundles. Pour le faire nous avons plusieurs choix :

  • Bundle Activator
  • Service déclaratif
    • Par annotation
    • Par fichier de déclaration
  •  Service Blueprint

Ma préférence ira pour cette fois vers le Blueprint. Pour utiliser cette exposition de service, il suffit de définir dans le dossier /src/main/ressources un dossier OSGI-INF/blueprint/ et de mettre à l’intérieur un fichier XML (peut importe son nom). Et de définir le service de la façon suivante :

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

	<bean id="spiritService" class="fr.conceptit.tuto.spout.service.SpiritServiceImpl" />

	<service ref="spiritService" interface="fr.conceptit.tuto.spout.model.SpiritService"/>
</blueprint>

Grâce à ces quelques lignes, nous définissons un service répondant à l’interface fr.conceptit.tuto.spout.model.SpiritService.

L’interface graphique “minimaliste”

Lors de cette phase nous mettrons en place une interface minimaliste à base de servlet. Mais dans une prochaine étape nous pourrons voir qu’il y a beaucoup plus intéressant et ergonomique à faire. Nous utiliserons dans un premier temps le “Pax Web whiteboard extende“. Cet outil va permettre d’enregistrer les servlets et de faire le lien avec les alias définis. Pour l’utiliser, il faudra installer la feature dans karaf :

features:install http-whiteboard

Une fois cette feature installée, il va falloir indiquer sur quel port nous voulons faire travailler notre “serveur web”. Pour cela nous créons un fichier “org.ops4j.pax.web.cfg” dans le dossier %KARAF-HOME%/etc et dans ce fichier nous allons écrire “org.osgi.service.http.port=8080″. Personnellement j’opte pour le 8080.

Une fois cela fait, nous allons créer notre première Servlet qui nous permettra de lister les spiritueux en cherchant par leur type. Cette Servlet va surtout nous servir à comprendre comment se connecter service appartenant au bundle persistence.

Le code de la servlet est un code “classique”, il va juste permettre de récupérer un URL du type : http://localhost:8080/spout?type=simple afin de récupérer une page de listing des spiritueux.

package fr.conceptit.tuto.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import fr.conceptit.tuto.spout.model.Spirit;
import fr.conceptit.tuto.spout.model.SpiritService;

public class SpiritServlet extends HttpServlet {
	private SpiritService spiritService;
	private DecimalFormat priceFormater = new DecimalFormat("########.00");

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		String spiritType = req.getParameter("type");

		PrintWriter writer = resp.getWriter();

		startPage(writer, "Spirit List");

		if (spiritType != null && spiritType.length() > 0) {
			showTask(writer, spiritType);
		}else{
			writer.println("http://localhost:8080/spirit?type=simple");
		}

		endPage(writer);
	}

	private void startPage(PrintWriter writer, String title){
		writer.println("");		writer.println("");
		writer.println(""+title+"");
		writer.println("");
		writer.println("");

	}
	private void endPage(PrintWriter writer){
		writer.println("");
		writer.println("");
	}

	private void showTask(PrintWriter writer, String spiritType) {
		List spirits = spiritService.findByType(spiritType);
		if (spirits != null && !spirits.isEmpty()) {
			showTitle(writer, spiritType);

			for (Spirit spirit : spirits) {
				writer.println("</pre>
<h1>> " + spirit.getName() + "</h1>
<pre>
");
				writer.println("Price : " + priceFormater.format(spirit.getPrice()) + "
");
				writer.println("</pre>

<hr />

<pre>");
			}
		} else {
			writer.println("</pre>
<span>Task with id \"<b>" + spiritType + "</b>\" not found</span>
<pre>");
		}

	}

	private void showTitle(PrintWriter writer, String spiritType) {
		writer.println("</pre>
<h1 align="\&quot;center\&quot;">Spirit \""+ spiritType +"\" List</h1>
<pre>");
	}

	public void setSpiritService(SpiritService spiritService) {
		this.spiritService = spiritService;
	}
}

Nous remplissons notre pom.xml de la même façon qu’habituellement en utilisant le même plugin que dans les autres bundles.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>fr.conceptit.tuto</groupId>
		<artifactId>spout</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<groupId>fr.conceptit.tuto.spout</groupId>
	<artifactId>spout-ui</artifactId>
	<packaging>bundle</packaging>

	<name>Spout :: UI :: Bundle</name>
	<description>spout-ui OSGi blueprint bundle project.</description>

	<dependencies>
		<dependency>
			<groupId>fr.conceptit.tuto.spout</groupId>
			<artifactId>spout-model</artifactId>
			<version> ${project.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<extensions>true</extensions>
				<configuration>
					<instructions>
						<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
						<Bundle-Version>${project.version}</Bundle-Version>
					</instructions>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Il nous faut maintenant faire deux choses :

  • Informer le bundle qu’il possède un service un peu spécial de type servlet. Cela se fait assez facilement! En effet, grâce à blueprint nous allons définir un service répondant à l’interface javax.servlet.http.HttpServlet.
  • Et au passage nous allons récupérer l’instance du service que nous avons défini précédemment et le lier à la servlet.

Tout ceci va se faire avec un fichier xml que nous placerons comme précédemment dans /src/main/ressources/OSGI-INF/blueprint/ et que l’on nommera comme bon vous semble et qui contiendra les lignes suivantes :

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
	<reference id="spiritService" availability="mandatory" interface="fr.conceptit.tuto.spout.model.SpiritService" />

	<bean id="spiritServlet" class="fr.conceptit.tuto.web.SpiritServlet">
		<property name="spiritService" ref="spiritService"/>
	</bean>
	<service ref="spiritServlet" interface="javax.servlet.http.HttpServlet">
		<service-properties>
			<entry key="alias" value="/spout" />
		</service-properties>
	</service>
</blueprint>

Le tag <reference> permet de rechercher le service par l’intermédiaire de son interface.

Le tag <service> est utilisé dans ce cas de façon un peu spécifique pour présenter au système notre servlet qui sera automatiquement récupérée par le http-whiteboard pour l’exposer.

Voila notre dernier bundle terminé.

Et maintenant !

Et bien maintenant rendez vous dans karaf.
Nous allons installer 1 feature et 3 bundles dans l’ordre suivant :

  • http-whiteboard : features:install http-whiteboar
  • spout-model : osgi:install mvn:fr.conceptit.tuto.spout/spout-model/1.0-SNAPSHOT
  • spout-persistence : osgi:install -s mvn:fr.conceptit.tuto.spout/spout-persistence/1.0-SNAPSHOT
  • spout-ui : osgi:install -s mvn:fr.conceptit.tuto.spout/spout-ui/1.0-SNAPSHOT

Le résultat devrait être celui la :

install-bunles-spout

Une fois les bundles installés et démarrés, il ne reste plus qu’à interroger notre service via l’url :  http://localhost:8080/spout?type=simple.

ui-servlet

Le projet features

Nous avons un projet fonctionnel mais nous pouvons améliorer le système d’installation. Il est toujours fastidieux d’installer plusieurs bundles l’un à la suite des autres et qui plus est, avec les features et bundles qu’ils ont en dépendance. Sur une application complète, on peut facilement avoir 50 dépendances voire plus…

Afin de faciliter l’installation des bundles que composent une application nous allons utiliser les  features.

Pour ce faire, il faut créer un nouveau projet que l’on nommera par exemple “spout-features”. Nous allons tout d’abord créer notre fameux fichier d’installation dans le dossier “spout-features/src/main/ressources/” et nous le nommerons features.xml :

<?xml version="1.0" encoding="UTF-8"?>
<features name="spout- ${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
	<feature name='spout' version='${project.version}' description='Global spout install'>
		<feature>http-whiteboard</feature>

		<bundle>mvn:fr.conceptit.tuto.spout/spout-model/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-persistence/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-ui/${project.version}</bundle>
	</feature>
</features>

Grâce à ce fichier nous allons donner les informations suivantes :

  • Ligne 2 : Définition du nom du groupe de features.
  • Ligne 3 : Définition d’une feature d’installation avec nom, version et description brève.
  • Ligne 4 : Demande d’installation d’une dépendance sous forme de feature, ici http-whiteboard qui permet de gérer les servlets.
  • Ligne 6 à 8 : Demande d’installation de nos bundles qui vont permettre de faire fonctionner l’application.

Comme nous pouvons le constater facilement la définition  des bundles se fait de la même façon que lorsqu’on le fait à la main dans karaf, avec le préfixe “mvn:” suivi du groupId puis de l’artifactId et pour finir la version, le tout mis entre balises <bundle/> . Dans le cas d’une dépendance sous forme de feature c’est encore plus simple il suffit de mettre le nom de celle-ci entre les balises <feature/>.

Ce fichier doit être envoyé dans votre repo maven et pour se faire nous allons définir le pom suivant qui permet de transformer les ${…} en valeur et d’envoyer le résultat au bon endroit :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>fr.conceptit.tuto</groupId>
		<artifactId>spout</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<groupId>fr.conceptit.tuto.spout</groupId>
	<artifactId>spout-features</artifactId>
	<packaging>pom</packaging>

	<name>Spout :: Features</name>
	<description>spout features project.</description>

	<build>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*</include>
				</includes>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<useDefaultDelimiters>false</useDefaultDelimiters>
					<delimiters>
						<delimiter>${*}</delimiter>
					</delimiters>
				</configuration>
				<executions>
					<execution>
						<id>filter</id>
						<phase>generate-resources</phase>
						<goals>
							<goal>resources</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<version>1.7</version>
				<executions>
					<execution>
						<id>attach-artifact</id>
						<phase>package</phase>
						<goals>
							<goal>attach-artifact</goal>
						</goals>
						<configuration>
							<artifacts>
								<artifact>
									<file>target/classes/features.xml</file>
									<type>xml</type>
									<classifier>features</classifier>
								</artifact>
							</artifacts>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Ce fichier features.xml va nous permettre d’installer notre application de la façon suivante :

install-features

La première commande :

features:addurl mvn:fr.conceptit.tuto.spout/spout-features/1.0-SNAPSHOT/xml/features

Elle permet d’informer karaf de l’endroit où il va pouvoir trouver le fichier contenant les features. Grâce à la précédente ligne nous allons profiter d’une autocomplétion afin de savoir quelle feature nous pouvons installer facilement.

La deuxième commande :

features:install spout

Cette ligne va installer à proprement parlé notre feature comportant l’ensemble des composants indispensables à notre application.

Conclusion de cette partie

Dans cette partie nous avons commencé à mettre en place une application un peu plus réelle que dans la partie 1. Dorénavant, les 4 modules (model, persistence, ui et features) vont servir de base pour les prochaines parties.

Nous avons également aperçu comment nous pouvions mettre en place une interface à base de Servlet, mais nous verrons que nous pourrons utiliser des moyens beaucoup plus efficients et puissants pour créer des interfaces de type CRUD.

Le dernier élément intéressant est la création du fichier de features permettant de gérer les dépendances et les bundles composants notre application. Il permet également de faciliter l’installation de cette dernière.

2 comments

  1. Pierre-Yves says:

    La partie 3 de cet article est disponible… http://www.conceptit.fr/?p=677

  2. […] Développement OSGi pour serveur Karaf – Part 2 […]

Leave a Reply

Your email address will not be published. Required fields are marked *