How RESTFUL is your WebSocket App

Restful is composed with four principles as said in this post:

  • The Swamp of POX: You’re using HTTP to make RPC calls. HTTP is only really used as a tunnel. (for WebSocket, change the word ‘HTTP’ by ‘WebSocket’)
  • Resources. Rather than making every call to a service endpoint, you have multiple endpoints that are used to represent resources, and you’re talking to them. This is the very beginnings of supporting REST.
  • HTTP Verbs. This is the level that something like Rails gives you out of the box: You interact with these Resources using HTTP verbs, rather than always using POST.
  • Hypermedia Controls. HATEOAS. You’re 100% REST compliant.

So I tried to transpose these concept to my WebSocket project in order to be fully compliant with these wonder-use-ful concepts.

HATEOAS

To be HATEOAS respectful, you must have one single URI entrypoint for your app, then the server send you the next accessible URI for your current client STATE.

The way to do this with WebSocket is as easy as its HTTP counterpart.

As a programming Stack, I choose Stomp over websocket protocol for the client (StompJS), a Stomp to JMS ActiveMQ Broker ans some Apache Camel routes server endpoint.

Here’s a sample of Javascript Uri Entrypoint:

function getPreAuthUris(client) {
	var client = client || $('#mainNavbar').data("stompClient");
	var idURIsQueue = client.subscribe("/queue/gotEntryPoints", function(
			message) {
		if (!heartBeatFilter(message)) {
			client.unsubscribe(idURIsQueue);
			var uris = parseUris(JSON.parse(message.body));
			// auth entry, auth error, auth success
			createLoginMenu(uris[0], uris[1], uris[2]);
			// chat entry, chat topic
			manageChat(uris[3], uris[4]);
		}
	});
	client.send("/queue/entryPoint", {}, "");

And its Camel counterpart:

from("{{application.entryPoint}}")
.filter(header("webSocketMsgType").isNotEqualTo("heartBeat"))
.enrich("direct:getEntryPointUrisInternal", uriToXmlObjectAggregationStrategy).marshal().xmljson().log("json Uris sent: ${body}").to("{{application.gotEntryPoint}}");
				from("direct:getEntryPointUrisInternal").beanRef("urisBean","getPreAuthenticationUris");
 
public class UrisBean {
@Setter
	private String authenticationEntryPoint;
@Setter
	private String authenticationErrorEndPoint;
@Setter
	private String authenticationSuccessEndPoint;...
 
public List<String> getPreAuthenticationUris() {
		List<String> uris = new ArrayList<String>();
		log.trace("adding an uri for the new client state: " + authenticationEntryPoint);
		uris.add(camelToStompUriFormat(authenticationEntryPoint));
		log.trace("adding an uri for the new client state: " + authenticationErrorEndPoint);
		uris.add(camelToStompUriFormat(authenticationErrorEndPoint));
		log.trace("adding an uri for the new client state: " + authenticationSuccessEndPoint);
		uris.add(camelToStompUriFormat(authenticationSuccessEndPoint));
		log.trace("adding an uri for the new client state: " + chatEntryPoint);
		uris.add(camelToStompUriFormat(chatEntryPoint));
		log.trace("adding an uri for the new client state: " + chatGeneralTopicEndpoint);
		uris.add(camelToStompUriFormat(chatGeneralTopicEndpoint));
		return uris;
	}
/**
	 * Change a camel formatted uri (jms:...:...) to a stomp uri (/.../...)
	 * @param camelFormat formatted uri
	 * @return stomp formatted uri
	 */
private String camelToStompUriFormat(String camelFormat) {
		return camelFormat.replaceAll("[^:]*:([^:]+)(:([^:]*?))?","/$1/$3");
	}
}

Hopa HATEOAS style!

In the near future, if we have for example to add a new adress to the current user, we’ll use the stomp header to route to the right person:

/*Precedent messages is in the form {"uris":"{adressQueue : {"linkRel": "/queue/adress", "uri": "/user/UserId-2/adress"}}}*/
 
client.send(precedentMessage.uris.adressQueue.linkRel, {"uri": precedentMessage.uris.adressQueue.uri, "RESTProtocol": "POST"}, JSON.stringify({"street":139,...}));

And the corresponding route:

from("jms:queue:adress")
.setHeader(Exchange.HTTP_METHOD,constant(${header.RESTProtocol}))
.setHeader(CxfConstants.CAMEL_CXF_RS_USING_HTTP_API, Boolean.TRUE)
.to("cxfrs://http://localhost:9080/${header.uri}?httpClientAPI=true");

Thanks to camel CXFRS component!

Content negociation

Simple, CXFRS component comes with content negociation done by header:

setHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_CLASS, Customer.class);

Or you can always use JSON, Xstream or JaxB DataFormat to convert your response.
Conclusion

I’m aware that it’s not 100% REST compliant (linkrel is tied to the server queue), but we have also decoupled the url to the performed action (the only limitation is that we can’t change this URL whithout impacting the client).

If you’ve any suggestion on this or think that I’m totally wrong (it’s just my personal way of doing it), feel free to comment!

Tests Javascript avec JsTestDriver

Après avoir testé le métier avec Pax-Exam et les routes avec Camel-Spring, il ne reste plus qu’à tester le Javascript.

J’ai choisi jsTestDriver pour son intégration avec Sonar (ainsi, j’ai une idée de ma couverture de tests).

Voici la recette pour l’intégrer avec Maven et la CI.

Tout d’abord, voici l’architecture du module:

<jsApp>
|_src/main/webapp <!-- sources html et js -->
|_src/test/resources
    |_lib/coverage.jar <!--jar utilisé pour la couverture de code-->
    |_jsTestDriver.conf <!--configuration de bootstrap de jsTestDriver-->
    |_startJsTestDriver.bat <!--script de démarrage de l'agent sous windows-->
    |_startJsTestDriver.sh <!--script de démarrage de l'agent sous linux-->
    |_stopJsTestDriver.bat <!--script d'arrêt de l'agent sous windows-->
    |_stopJsTestDriver.sh <!--script d'arrêt de l'agent sous linux-->
|_src/test/webapp <!--mocks et tests-->

Tout d’abord, JsTestDriver a besoin d’un agent navigateur (et l’agent) démarré lors de l’éxecution des tests.
Les scripts n’étant pas les même sous windows et sous linux, nous devons en faire un de démarrage et d’arrêt pour chaque OS (bien sûr, les propriétés seront valorisées par Maven):

Démarrage sous windows:

java -jar "${settings.localRepository}\com\googlecode\js-test-driver\1.3.5\js-test-driver-1.3.5.jar" --port 9876 --browser "${browser.path}"

Arrêt sous windows:

wmic process where (commandline like "%%js-test-driver%%" and not name="wmic.exe") delete

Et Pour linux (dans deux scripts sh différents):

# démarrage
#!/bin/bash
java -jar ${settings.localRepository}/com/googlecode/js-test-driver/1.3.5/js-test-driver-1.3.5.jar --port 9876 --browser /usr/bin/firefox  --basePath . &
#arrêt
ps ax | grep -i 'js-test-driver' | grep -v grep | awk '{print $1}' | xargs kill -SIGTERM

Ensuite, nous devons créer deux profils Maven dont l’activation dépend de l’OS (afin de choisir les scripts qui conviennent lors de l’éxecution):

	<profile>
			<id>windows-properties</id>
			<activation>
				<os>
					<family>Windows</family>
				</os>
			</activation>
			<properties>
				<run.command>cmd</run.command>
				<run.command.additionnal.arg>/c</run.command.additionnal.arg>
				<jsTestDriver.start.script>start-jsTestDriver.bat</jsTestDriver.start.script>
				<jsTestDriver.stop.script>stop-jsTestDriver.bat</jsTestDriver.stop.script>
			</properties>
		</profile>
		<profile>
			<id>linux-properties</id>
			<activation>
				<os>
					<family>unix</family>
				</os>
			</activation>
			<properties>
				<run.command>sh</run.command>
				<run.command.additionnal.arg></run.command.additionnal.arg>
				<jsTestDriver.start.script>start-jsTestDriver.sh</jsTestDriver.start.script>
				<jsTestDriver.stop.script>stop-jsTestDriver.sh</jsTestDriver.stop.script>
			</properties>
		</profile>

Il faut ensuite configurer le filtrage des resources (afin que les variables des fichiers soient valorisées):

<properties>
		<sonar.language>js</sonar.language>
		<sonar.sources>${project.sourceDirectory}</sonar.sources>
		<sonar.tests>src/test/resources</sonar.tests>
	<sonar.javascript.jstestdriver.reportsfolder>target/jstestdriver</sonar.javascript.jstestdriver.reportsfolder>
	<sonar.javascript.jstestdriver.reportsPath>target/jstestdriver</sonar.javascript.jstestdriver.reportsPath>
<sonar.javascript.lcov.reportPath>target/jstestdriver/jsTestDriver.conf-coverage.dat</sonar.javascript.lcov.reportPath>
	</properties>
<build>
<sourceDirectory>src/main/webapp</sourceDirectory>
		<testResources>
			<testResource>
				<directory>src/test/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>*</include>
				</includes>
			</testResource>
		</testResources>

Finalement, il ne reste qu’à réaliser le lancement et l’arrêt du navigateur :

<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-antrun-plugin</artifactId>
				<inherited>false</inherited>
				<executions>
					<execution>
						<id>start-jsTestDriver</id>
						<phase>process-test-classes</phase>
						<configuration>
							<target name="starting jsTestDriver">
							<chmod file="${project.build.directory}/test-classes/${jsTestDriver.start.script}" perm="ugo+rx"/>
								<exec executable="${run.command}" dir="${project.build.directory}"
									spawn="true">
									<arg value="${run.command.additionnal.arg}" />
									<arg value="${project.build.directory}/test-classes/${jsTestDriver.start.script}" />
								</exec>
								<sleep seconds="3"/>
							</target>
						</configuration>
						<goals>
							<goal>run</goal>
						</goals>
					</execution>
					 <execution>
						<id>stop-jsTestDriver</id>
						<phase>prepare-package</phase>
						<configuration>
							<target name="stop jsTestDriver">
							<chmod file="${project.build.directory}/test-classes/${jsTestDriver.stop.script}" perm="ugo+rx"/>
								<exec executable="${run.command}" dir="${project.build.testOutputDirectory}"
									spawn="false">
									<arg value="${run.command.additionnal.arg}" />
									<arg value="${project.build.testOutputDirectory}/${jsTestDriver.stop.script}" />
								</exec>
							</target>
						</configuration>
						<goals>
							<goal>run</goal>
						</goals>
					</execution> 
				</executions>
			</plugin>

Les Mocks

Il nous faudra mocker les appels distants (ajax, websocket…), pour cela, il vous faut wrapper vos appels par une fonctions javascript dans un fichier à part, il suffira de ne pas le charger dans votre configuration te tests mais de référencer le mocks à la place.

Voici un exemple pour les appels stomp:

function ConnectStomp (){
	this.hitSubscribed = 0;
	var callbacks = [];
 
 
	this.send = function (queue, header, body) {
		//TODO implement mock 
	};
	this.subscribe = function (queue, callback) {
		console.info("Subscribing to queue: " + queue);
		message = { "body": 'ok!'};
		callback = function(message) {
			};		
		var tuple = { "queue" : queue,"callback" : callback}
		callbacks = callbacks.concat(tuple);
		this.hitSubscribed = callbacks.length;
		return 1;
	};	
};

Puis la configuration jsTestDriver associées:

server: http://localhost:9876
 
load:
  - http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js
  - http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.0/js/bootstrap.min.js
  - http://cdn.sockjs.org/sockjs-0.3.min.js
  - /src/test/webapp/mock/stompConnectionHelperMock.js #noter le referencement du fichier mock au lieu de l'original
  - /src/main/webapp/js/authentication/ui/loginui.js
  - /src/main/webapp/js/authentication/controllers/login.js
  - /src/main/webapp/js/authentication/controllers/authCommon.js
  - /src/main/webapp/js/index/index-start.js
 
test:
  - src/test/webapp/*.js
 
serve:
 - src/main/webapp/css/index.css
 - src/main/webapp/html/index.html
 - http://twitter.github.com/bootstrap/assets/css/bootstrap.css
 
plugin:
 - name: "coverage"
   jar: "lib/coverage-1.3.5.jar"
   module: "com.google.jstestdriver.coverage.CoverageModule"
 
timeout: 90

Tests!

Il ne vous reste plus qu’à rédiger vos tests (enfin!):

TestCase("Test Subscribe", {
    testSubscribe:function(){
    	var client = attachStompClient();
    	assertNotUndefined("hitSubscribed not defined", client.hitSubscribed);
    	createLoginMenu();
    	loginClick(client);
    	assertEquals("six subscriptions must have been set", 6, client.hitSubscribed)
 
    },
    testB:function(){
    }
  });

Conclusion

De part cette série de posts sur les tests, vous pouvez couvrir l’intégralité (sauf les cas d’exception technique, ou je vous renverrez vers des exemple avec byteman) de votre Stack OSGI/Javascript, tout en ayant des résultats de couverture dessus (sauf pour pax-exam où la solution n’existe pas encore).

J’espère vous avoir donné envie de pratiquer OSGi et le TDD, qui est à mon sens une pile de premier choix pour un projet digne de ce nom!

Camel unit-testing

Dans ce post, je vais vous expliquer comment tester vos routes Camel.

Tout d’abord, je vous déconseille fortement d’utiliser camel-blueprint pour des projets avancés, et ce même en environnement OSGI: en effet, ce module et fait pour les tests d’intégration (et il faut avoir toutes les bibliothèques dépendantes de référencées dans le classpath). Pour cette série, nous utiliserons donc camel-spring.

1. Variabiliser vos entrypoints et endpoints

La première phase consiste à variabiliser les endpoints: en effet, nous allons réaliser des tests unitaire, donc les dépendances tierces ne seront pas accessibles (par exemple, toute route jms fera échouer toute la suite de tests).

La seule chose que vous avez à faire est de changer vos déclaration ‘from’ et ‘to’ de vos routes camel par des propriétés résolues au runtime via la configuration admin OSGI:

Par exemple, pour une route comme celle-ci:

from("jms:in").beanRef("fooBean", "fooMethod").to("jms:out");

Vous devrez créer deux propriétés:

from("{{inJMS}}").beanRef("fooBean", "fooMethod").to("{{outJMS}}");

Afin de charger le fichier de propriétés requis par ce bundle, vous devez le référencer dans votre fichier blueprint.xml (ici camelOSGORoutes):

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
	http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.0.0.xsd
	http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
<cm:property-placeholder persistent-id="camelOSGORoutes" id="camelOSGORoutes"/>
	</blueprint>

Vous devrez ensuite créer la feature référençant ce fichier de propriétés.
Voici le pom permettant de créer l’artefact de propriété:

<build>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*.cfg</include>
				</includes>
			</resource>
		</resources>
...
<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>attach-artifacts</id>
						<phase>package</phase>
						<goals>
							<goal>attach-artifact</goal>
						</goals>
						<configuration>
							<artifacts>
								<artifact>
									<file>${project.build.directory}/classes/camelOSGORoutes.cfg</file>
									<type>cfg</type>
									<classifier>camel</classifier>
								</artifact>
							</artifacts>
						</configuration>
					</execution>
				</executions>
			</plugin>

Puis la feature référençant ce fichier qui sera automatiquement copié dans le répertoire etc de karaf lors de l’installation (hors tests):

<feature name="osgo-routes" version="${project.version}">
		<configfile finalname="/etc/camelOSGORoutes.cfg">mvn:${project.groupId}/${project.artifactId}/${project.version}/cfg/camel</configfile>
		<bundle>mvn:net.osgiliath.view/goserver.view.routes/${project.version}/jar/karaf
		</bundle>
</feature>

2. Mocking

Nous devons maintenant mocker les différents services, transactionManagers…

Rien de plus simple vu que nous utilisons Spring (context dans src/tests/resources/spring):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
    ">
    	<bean id="transactionManager"
		class="net.osgiliath.view.main.routes.mock.TransactionManagerMock"/>
<camelContext id="camelCtx"
		xmlns="http://camel.apache.org/schema/spring">
		 <propertyPlaceholder id="properties" location="cfg/camelOSGORouteTest.cfg"/> <!-- reference les propriétés de test situées dans src/tests/resources-->
		<contextScan />
		<jmxAgent id="agent" disabled="true" />
		<routeBuilder ref="authenticationRoute"></routeBuilder>
	</camelContext>
<bean id="authenticationRoute" class="net.osgiliath.view.main.routes.AuthenticationRoutes">
		<property name="transactionManager" ref="transactionManager"></property>
	</bean>
...

Remplacez dans votre fichier de propriétés de test les routes jms par ‘direct’ (ou ‘mock’ pour pouvoir s’assurer de l’appel des endpoints).

3. Test!
Nous pouvons finalement réaliser notre test Junit

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/testSpring.xml"})
public class AuthenticationRouteTest  {
    @Produce(uri = "direct:start")
    protected ProducerTemplate template;
    @EndpointInject(uri = "mock:authenticated")
    protected MockEndpoint resultEndpoint;
@Test
	public void testSubscription() throws InterruptedException {
 
		// set mock expectations
		resultEndpoint.expectedMessageCount(1);
		MUser correct = PlayerGenerator.getCorrectMUser();
		// send a message
		template.sendBodyAndHeader("direct:authenticate", "{\"pseudo\": \""+correct.getPseudo()+"\", \"password\": \""+correct.getPassword()+"\", \"email\": \""+correct.getEmail()+"\"}","authType", "subscription");
 
		// assert mocks
		resultEndpoint.assertIsSatisfied();
	}

That’s it, vous avez maintenant la possibilité de tester vos routes.

Mes dernier posts sur la BFI

Je m’apprête à quitter le monde de la banque-finance-assurance pour intégrer celui des systèmes complexes (ma soif d’apprentissage me pousse à changer de domaine).

Aussi, laissez-moi donner mes visions sur ce domaine que je quitte, partant de mes acquis en entreprise et autres expériences nocturnes.

En partant de ces postulats:

  • La programmation dans ces domaines et principalement faite de plomberie (JMS, SOAP, REST, Securité)
  • Les algorithmes sont pauvres, il y a très peu de code métier à proprement dit
  • L’interface utilisateur doit communiquer à chaque action avec le serveur
  • Le code est verbeux, implique nombre d’acteurs et multiplie le nombre de lignes produites pour un manque de visibilité certain
  • Les mises en production se font souvent dans la douleur, incluant un nombre de serveurs grandissant afin de supporter la charge
  • Avez vous déjà retrouvé facilement la documentation d’un projet que vous repreniez, votre urbaniste sait-il que votre module est assez générique pour authentifier n’importe quel utilisateur du SI?

Pour remédier à cela, nombre de solutions existent et risquent d’arriver dans cet environnement afin de simplifier cette complexité qui est de part sa nature automatisable.

Plusieurs questions se posent donc à tous les échelons pour répondre à ces problématiques:

Comment mettre en prod un programme sans bug?
Utiliser le test-driven développement: les specs définissent le contrat, à vous d’en implémenter le contrat.

Pour la MOA, je conseille d’utiliser la syntaxe given… when… then…
Par exemple: étant un nouvel utilisateur, lorsque je m’inscris avec un pseudo ayant une longueur inférieure à 6, alors je reçois un message me demandant de rentrer un pseudo plus long.

L’implémentation est alors facile: (ici avec la validation api)

@Size(min = 6, message = "rentrer un pseudo plus long")
private String pseudo;

Les tests sont très importants: ils assurent le respect de la spécification (Junit 4):

@Test
void testFalseLogin() {
try {
User u = new User.Builder().pseudo("toto").build();
login(usr);
fail("tho shall not be here");
}catch (ValidationException ve) {
assertEquals ("rentrer un pseudo plus long", ve.getMessage());}

Il y a deux types de tests (en BFI, quelques uns supplémentaires en système), les deux étant complémentaires:

Les tests unitaires: un test isolé de son contexte, servant à vérifier le bon fonctionnement d’une méthode d’une classe.
Pour cela, deux concepts:

  • Le test en lui même.
  • Le Mock, permettant de renvoyer un résultat crédible d’une autre méthode appelée par celle qui est testée

Un petit exemple de mock (Mockito):

SubscriptionRepository mockedRepo = mock(SubscriptionRepository.class);
when(mockedRepo.subscribe(u)).thenReturn(u);

Les tests d’intégration: permettant de tester cette fameuse méthode dans son context cette fois-ci.
Pour cela, un concept est nécessaire: celui d’application.
Pour cela Karaf et Eclipse utilisent le concept de feature: une feature est un ensemble de bundles (unité de traitement).

Voici un exemple de features (Karaf):

<feature name="osgo-game" version="${project.version}">
		<feature version='${osgiliath.framework.version}'>framework-model</feature>
		<bundle>mvn:net.osgiliath.game/game.management.database/${project.version}/jar/karaf
		</bundle>
		<bundle>mvn:net.osgiliath.game/game.management.model/${project.version}/jar/karaf
		</bundle>
		<bundle>mvn:net.osgiliath.game/game.management.business/${project.version}/jar/karaf
		</bundle>
	</feature>

Ici, nous avons crée une “feature” comprenant une autre feature et 3 bundles.
Ainsi, nous pouvons tester notre module métier dans son contexte (avec pax-exam):

@RunWith(JUnit4TestRunner.class)
@ExamReactorStrategy(AllConfinedStagedReactorFactory.class)
public class ITPlayerSubscriber {
@Inject
	private PlayerSubscriber playerSubscriber;
@Inject
	private PlayerRepository playerRepository;
@Configuration
	public Option[] config() {
 
		return new Option[] {
 
				karafDistributionConfiguration()
						.frameworkUrl(
								maven().groupId("org.apache.karaf")
										.artifactId("apache-karaf").type("zip")
										.versionAsInProject())
										.karafVersion("3.0.0.RC1")
						.name("Apache Karaf"),
				new KarafDistributionConfigurationFileReplacementOption(
						"etc/org.ops4j.pax.url.mvn.cfg", new File(
								"src/test/resources/org.ops4j.pax.url.mvn.cfg")),
								logLevel(LogLevel.INFO), keepRuntimeFolder(),
 
						scanFeatures(
								maven().groupId("net.osgiliath.view")
										.artifactId("goserver.view.main.features")
										.type("xml").classifier("features")
										.versionAsInProject(), "osgo-game", "osgo-user"),
				workingDirectory("target/paxrunner/features/") };
	}
	@Test
	public void testPlayerSubscriber() throws Exception {
assertNotNull(playerSubscriber);
		assertNotNull(playerRepository);
		txMngr.begin();
		Player p = playerSubscriber.subscribeOrLogPlayer(1);
		txMngr.commit();
		assertEquals(1, playerRepository.findAll().size());
 
	}

Vous avez alors dans les mains tout ce qui vous permet de réaliser vos tests, vous pouvez donc réaliser votre “bundle” en respect total avec les spécifications.

Attelons nous à la réutilisation de vos “bundles”.

Modélisation
Tout d’abord il doit être assez générique: opter pour authentification plutôt que authentification d’un utilisateur MGEN dans le cadre de l’utilisation du site Internet… Si le backend est spécifique, préférez les conditionnelles dans le bundle (au moins on saura où l’algorithme se trouve).

Ensuite, il doit être modélisé: les décideurs retrouvent les fonctionnalités en visualisant des boites (type diagramme de classe), pas en naviguant dans de la javadoc…

Il existe plusieurs types de diagrammes, chacun agissant à son niveau de responsabilité:

Le diagramme TOGAF (ou Archimate), le diagramme de plus haut niveau, permettant de voir les interactions des différents Acteur du SI (commercial, DG…), avec les différentes applications existantes.

Le méta-modèle sur ce site: http://www.google.fr/imgres?um=1&hl=fr&safe=off&sa=N&rlz=1C1CHFX_frFR521FR521&authuser=0&biw=1600&bih=756&tbm=isch&tbnid=wuKSpSEgDVB9NM:&imgrefurl=http://www.togaf.org/&docid=6Kw6lNZtIDQlYM&imgurl=http://pubs.opengroup.org/graphics/N114cov.png&w=666&h=470&ei=8WZYUaTOI8a5hAeHhoG4Aw&zoom=1&iact=hc&vpx=4&vpy=133&dur=9&hovh=189&hovw=267&tx=87&ty=103&page=1&tbnh=147&tbnw=201&start=0&ndsp=33&ved=1t:429,r:0,s:0,i:82

Ainsi vos applications seront référencées en tant que “LogicalApplicationComponent” (nom du bundle), et les services qu’ils exposent en tant que “InformationSystemService” (nom de la classe exposant le service).

En descendant un cran en dessous, vous disposez du diagramme SOA: celui-ci référence les différentes classes exposant des services, et ses différentes méthodes. Ce diagramme peut être sourcé à partir d’annuaires de services tels que ZooKeeper.
Les services viables aujourd’hui étant Jax-ws (SOAP 2.0), Jax-RS (implémentation REST), JMS (Java Messaging services asynchrones), STOMP (pour les protocoles langages tiers comme Javascript, Perl, C), OSGI services (Intra-JVM) ou D-OSGI (distribué).

Finalement, le diagramme d’entités références les différentes classes d’un bundle (la modélisation technique à proprement dite), permettant de réaliser un ou plusieurs services.

Comme surcouche au diagramme SOA, vous disposez du diagramme BPM (business process model), permettant de les lier, ceci afin de réaliser les EIP (enterprise integration patterns).

Imaginez donc tout ces diagrammes, associés à de la génération de code (regardez du coté d’Eclipse Sirius qui va sortir en Novembre prochain), et vous obtenez un SI visible par les différents échelons. Ces diagrammes peuvent soit générer du code, soit générer de la documentation servant à générer du code (regarder du coté de Mylyn Intent).

Plomberie
Au niveau de la plomberie, Apache Karaf permet d’installer des bundles et features à chaud…
Pour vous donner un aperçu de la puissance de Karaf/Camel, voici le code vous permettant de mettre en place un SI permettant de cherche un fichier présent sur FTP et envoyant le fichier coupé en utilisateurs dans une queue JMS

Instructions Karaf:

feature:repo-add mvn:org.apache.camel.karaf/camel/xml/features
feature:install camel-ftp
feature:install camel-jaxb
feature:install camel-jms
activems:broker "vm://localhost"

Classe Camel (voir modelisation BPM):

class toJMS extends RouteBuilder{
from("ftp://someone@someserver.com?password=secret&localWorkDirectory=/tmp").split().tokenizeXML(Player.class.getName()).to("direct:go"):
from("direct:go").unmarshall().jaxb().to("jms:post")
}

Comme expliqué au dessus, Karaf permet le chargement/déchargement à chaud.
Imaginez votre prod mettre en production avec la commande:

feature:install modulesV2
feature:uninstall modulesV1

Ou encore un service appelant avec cette définition (la syntaxe n’est pas bonne mais pas si éloignée de la réalité):

ServiceReference ref = getService (monServiceAppelé).filter("(charge<60%)");
if (ref = null) {
KarafCommand command = new KarafCommand();
Bundle b = new Bundle("serverBundleref").
command.startAndInstallBundleOnMyServer(b);

On peut aller plus loin avec des routines Chef ou Puppet (créer un nouveau serveur avec système d’exploit et un nouveau serveur Karaf, puis installation du bundle).

Convaincus? Venez participer au framework Osgiliath, il vous suffira d’un mail à cmordant1@gmail.com et d’un peu de votre temps…

Integration testing OSGI with Pax-exam and Karaf

So, let’s dive into integration testing with OSGI.

First of all we have to create a new module of your project that will contain your tests (with OSGI, we consider that I-testing is totally out of the tested module scope as it tests many different modules).

Configure this Itest module dependencies to reference our module + itest container deps (pax-exam 2.60 at the time wirting):

 
<!-- Itest container deps -->
<dependency>
        <groupId>org.apache.karaf.tooling.exam</groupId>
	<artifactId>org.apache.karaf.tooling.exam.container</artifactId>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<groupId>org.ops4j.pax.swissbox</groupId>
			<artifactId>pax-swissbox-core</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.ops4j.pax.swissbox</groupId>
			<artifactId>pax-swissbox-extender</artifactId>
		</exclusion>
		<exclusion>
		        <groupId>org.ops4j.pax.swissbox</groupId>
			<artifactId>pax-swissbox-framework</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- Pax Exam version you would like to use. At least 2.2.x is required. -->
<dependency>
	<groupId>org.ops4j.pax.exam</groupId>
	<artifactId>pax-exam-junit4</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.ops4j.pax.swissbox</groupId>
	<artifactId>pax-swissbox-core</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.ops4j.pax.swissbox</groupId>
	<artifactId>pax-swissbox-framework</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.ops4j.pax.swissbox</groupId>
	<artifactId>pax-swissbox-extender</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.ops4j.pax.exam</groupId>
	<artifactId>pax-exam</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.apache.karaf</groupId>
	<artifactId>apache-karaf</artifactId>
	<type>zip</type>
	<scope>test</scope>
</dependency>
<!-- tested module full stack -->
<dependency>
	<groupId>net.osgiliath.view</groupId>
	<version>${project.version}</version>
	<artifactId>goserver.view.main.features</artifactId>
	<scope>test</scope>
	<type>xml</type>
	<classifier>features</classifier>
</dependency>

As we’re in an integration tests context, you’ve to configure the Maven-failsafe plugin (surefire fork dedicated to itests

<plugin>
	<groupId>org.apache.servicemix.tooling</groupId>
	<artifactId>depends-maven-plugin</artifactId>
	<executions>
		<execution>
			<id>generate-depends-file</id>
			<goals>
				<goal>generate-depends-file</goal>
			</goals>
		</execution>
	</executions>
</plugin>
<plugin>
	<artifactId>maven-failsafe-plugin</artifactId>
	<version>2.6</version>
	<executions>
		<execution>
			<goals>
				<goal>integration-test</goal>
				<goal>verify</goal>
			</goals>
		</execution>
	</executions>
</plugin>

We also have configured the maven-depends plugin that will allow pax-exam to be aware of the dependencies versions.

Finally, we just have to write the test itself:

@RunWith(JUnit4TestRunner.class)
@ExamReactorStrategy(AllConfinedStagedReactorFactory.class) //Pax container OSGI RT starter configuration
public class ITRepositorySubscriptionService {
	@Inject
	private SubscriptionService subscriptionService; // the service we want to test
 
	@Configuration // pax exam configuration that will start the runtime
	public Option[] config() {
		return new Option[] {
 
				karafDistributionConfiguration() //here we're using Karaf container 
						.frameworkUrl(
								maven().groupId("org.apache.karaf")
										.artifactId("apache-karaf").type("zip")
										.versionAsInProject())
						.karafVersion("2.3.0").name("Apache Karaf"),
				new KarafDistributionConfigurationFileReplacementOption(
						"etc/org.ops4j.pax.url.mvn.cfg", new File(
								"src/test/resources/org.ops4j.pax.url.mvn.cfg")), // Karaf configuration which, for example add our nexus to its configuration
				new KarafDistributionConfigurationFilePutOption(
						"etc/jmsconfig.cfg", "jmstransportconnector.uri",
						"tcp://0.0.0.0:61616"), // Some compedium configuration to make the framework running
				new KarafDistributionConfigurationFilePutOption(
						"etc/jmsconfig.cfg", "connectionfactory.uri",
						"vm://localhost"),
						scanFeatures(
								maven().groupId("net.osgiliath.view")
										.artifactId("goserver.view.main.features")
										.type("xml").classifier("features")
										.versionAsInProject(), "osgo-user"),//the feature we want to install (all that's needed to start our service)
 
				workingDirectory("target/paxrunner/features/") };
	}
 
	@Test
	public void test() throws Exception {
		MUser u = MUser.mUser().email("tcharl@toto.fr").pseudo("toto")
				.password("aa").build();
 
		assertNotNull(subscriptionService);
		assertNotNull(subscriptionService.onSubscription(u).getId());
	}
}

Tested service!

Programming a Karaf feature

Features are the Virgo equivalent of their Plans, it allows to type a single command in your server to deploy an entire fully functionnal application.

Let’s implement one of them.

The code of the osgiliath framework fetaures is fully open sourced (ASL) and present on github.

First of all, we have to configure some plugin in our pom.xml:

Resource filtering (if you have your dependencies version managed with properties in a super pom):

<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
		</resources>

Then, the build helper that will export to the repo the xml file with the artifact:

<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>build-helper-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>attach-artifacts</id>
						<phase>package</phase>
						<goals>
							<goal>attach-artifact</goal>
						</goals>
						<configuration>
							<artifacts>
								<artifact>
									<file>${project.build.directory}/classes/${artifactId}.xml</file>
									<type>xml</type>
									<classifier>features</classifier>
								</artifact>
							</artifacts>
						</configuration>
					</execution>
				</executions>
			</plugin>

Finally, you’ll have to add your feature dependencies, otherwise it won’t work in some case, for example with pax-exam.

Let’s now write the feature:

<?xml version="1.0" encoding="UTF-8"?>
<features>
  <feature name='derby' version='${com.springsource.org.apache.derby.client.version}'>
 <bundle>mvn:org.apache.derby/com.springsource.org.apache.derby/${com.springsource.org.apache.derby.client.version}</bundle>
  </feature>
</features>

Here, we have written a feature called ‘derby’ that will download the maven artifact derby when installed.

On github, you’ll have some more advanced examples for example importing an other feature artifact to use it for programming an other feature…

Finally, you can run karaf and type:

features:addurl mvn:[groupId]/[artifactId]/[version]/xml/features
features:install derby

It will be downloaded and installed to Karaf

JQuery, Websocket & Camel: How-to

Here’s how to make Websocket working with Apache Camel.

Here’s an example of a login component.

First, let’s implement the html part:

Create index.html in your src/main/resources/webapp folder:

	<link href="js/bootstrap/css/bootstrap.css" rel="stylesheet" /><script type="text/javascript" src="js/jquery/jquery-1.8.3.js"></script><script type="text/javascript" src="js/bootstrap/js/bootstrap.js"></script><script type="text/javascript" src="js/stomp/stomp.js"></script><script type="text/javascript" src="js/index/index-start.js"></script>
<script type="text/javascript">// <![CDATA[
	$(function() {
		$("#showModalLogin").bind("click", function(event) {
			$("#loginModal").modal();
		})
});	
// ]]></script>
<div class="navbar navbar-static" id="main-navbar">
<div class="navbar-inner">
<div class="container" style="width: auto;"><a class="brand" href="#">Go Server</a>
<ul class="nav" id="accountNavigation" role="navigation">
	<li class="dropdown"><a class="dropdown-toggle" id="dropAccount" role="button" href="#" data-toggle="dropdown">Account<b class="caret"></b> </a>
<ul class="dropdown-menu" role="menu">
	<li><a id="showModalLogin" tabindex="-1"></a>Login</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="modal hide fade" id="loginModal">
<div class="modal-header"><button class="close" data-dismiss="modal">×</button>
<h3>Login</h3>
</div>
<form id="loginForm">
<div class="modal-body">
<div class="control-group"><label class="control-label" for="loginPseudo">Pseudo</label>
<div class="controls"><input id="loginPseudo" type="text" placeholder="Pseudo" /></div>
</div>
<div class="control-group"><label class="control-label" for="loginPwd">Password</label>
<div class="controls"><input id="loginPwd" type="password" placeholder="Password" /></div>
</div>
<div class="modal-footer"><button class="btn" id="loginButton"><span>Login</span> </button> <a class="btn" href="#" data-dismiss="modal">Close</a></div>
</div>
</form></div>

And the index.js file wich will make the websocket call:

$(document).ready(function() {
	var loginClient;
	var url = "ws://localhost:9292/stomp/queue/login";
	loginClient = new WebSocket(url);
	$("#loginButton").bind("click", function(event) {
		console.debug("loginButton clicked");
		login(loginClient);
	});
});
function login(client) {
	console.info("I'm in");
	client.onmessage = function(evt) {
		alert("ok");
		console.info("received message");
		console.info("value " + evt.data);
		var validOrNot = JSON.parse(evt.data);
		if (validOrNot == true) {
			alert(validOrNot);
			$('#accountNavigation').style.visibility = "hidden";
			$('#loginModal').dialog("close");
		}
		client.close();
	};
	client.onerror = function(frame) {
		console.error(frame.message);
 
	};
	var loginPwd = {
		username : $('#loginPseudo').val(),
		password : $('#loginPwd').val()
	};
	client.send(JSON.stringify(loginPwd));
};

Here we have created the client which will send the login information to the websocket address ws://localhost:9292/stomp/queue/login and which will receive login message at the same address.

At the server side (in the same Maven module), we’ll configure Websocket component with spring (via camel-spring and camel-websocket dependencies).

Easy isn’t it?
We just have now to configure the Camel route calling the security service from the websocket call:

@Component
public class IndexRoutes extends RouteBuilder{
	@Override
	public void configure() throws Exception {
		from("websocket://0.0.0.0:9292/stomp/queue/login")
		.log("message received in camel : ${body}")
		.beanRef("securityService", "authenticate")
		.to("websocket://0.0.0.0:9292/stomp/queue/login?staticResources=classpath:webapp");
	}
}

Camel starts a websocket Listener on the queue and start a sender server due to the addition of the staticResources path.

In order to configure the module as a WAB (Web application bundle), just add these lines to your MANIFEST.mf file

Web-ContextPath: goserver.view.main.js
Bundle-ClassPath: ., WEB-INF/classes
Import-Package: 
 org.apache.activemq.camel.converter,
 org.apache.activemq.pool,
 com.thoughtworks.xstream.io,
 org.apache.camel.builder,
 org.springframework.stereotype,
 org.apache.camel,
 org.apache.activemq.camel.component,
 org.apache.camel.model

And you’re done, take your chrome web browser and test it!

Expérimentations Virgo – Utilisation de Spring integration

Nous allons maintenant utiliser la configuration ActiveMQ pour créer un bundle permettant d’authentifier un utilisateur.

Voici la cinématique:

  • Un message contenant login, mot de passe, mail et session utilisateur arrive via JMS
  • On essaie de valider les différents champs selon les contraintes métier (longueur du mdp, du pseudo, respect des standards d’adresse mail)
  • En cas d’erreur, on renvoie le message de validation dans la queue d’erreur
  • En cas de succès, on enregistre l’utilisateur, puis on renvoie l’objet utilisateur dans la queue destinée aux utilisateurs

Voici tout d’abord les dépendances non-déduite à rajouter dans le fichier template.mf (permettant de générer les manifest):

Bundle-ManifestVersion: 2
Bundle-Name: ${project.artifactId}
Bundle-Vendor: Osgiliath Inc.
Bundle-SymbolicName: ${project.groupId}_${project.artifactId}
Bundle-Version: ${user.osgi.version}
Excluded-Imports: net.osgiliath.social.business.services
Import-Package: org.apache.activemq.command,
org.springframework.integration.config,
org.springframework.integration.config.xml,
org.springframework.integration.jms,
org.springframework.integration.channel,
org.springframework.integration.endpoint,
org.springframework.scheduling.concurrent,
org.springframework.integration.handler,
org.springframework.jms.listener,
org.springframework.scheduling.support,
org.springframework.integration.json,
org.springframework.integration.config.annotation,
org.springframework.integration.aop,
org.springframework.integration.transformer

Puis la configuration Spring configurant les différents canaux Spring intégration (Un message vient d’une queue JMS, est redirigé dans un canal qui est écouté par un MessageEndpoint).

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:security="http://www.springframework.org/schema/security"
	xmlns:int="http://www.springframework.org/schema/integration"
	xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jms="http://www.springframework.org/schema/integration/jms"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
	http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
	http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
	http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
	http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
">
<osgi:reference id="connectionFactory" interface="javax.jms.ConnectionFactory" />
	<int:poller default="true" max-messages-per-poll="1000"
		time-unit="MILLISECONDS" fixed-delay="100" id="defaultpoller" />
	<context:annotation-config />
	<int:annotation-config />
<!-- + service de validation, scan des packages, repository utilisateurs...-->
<int:channel id="subscribeChannel">
	</int:channel>
	<int:channel id="subscriptionErrorChannel">
		<int:queue />
	</int:channel>
<int:channel id="userInformationChannelOut">
		<int:queue />
	</int:channel>
<jms:message-driven-channel-adapter
		channel="subscribeChannel" destination="subscribeDestination"
		connection-factory="connectionFactory" />
 
	<bean id="subscribeDestination" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="local.subscribe" />
	</bean>
<jms:outbound-channel-adapter channel="userInformationChannelOut"
		destination="userInformationOutDestination" connection-factory="connectionFactory" />
 
	<bean id="userInformationOutDestination" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="local.userInformation" />
	</bean>
	<jms:outbound-channel-adapter channel="subscriptionErrorChannel"
		destination="subscribedErrorDestination" connection-factory="connectionFactory" />
 
	<bean id="subscribedErrorDestination" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="local.error" />
	</bean>
</beans>

Il ne reste plus qu’à coder la classe de souscription:

package net.osgiliath.security.manager.internal;</code>
 
import java.util.List;
import java.util.Set;
 
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
 
import lombok.extern.slf4j.Slf4j;
import net.osgiliath.security.manager.SubscriptionService;
import net.osgiliath.user.authorization.AUTHORITY;
import net.osgiliath.user.model.MUser;
import net.osgiliath.user.model.authorization.MAuthority;
import net.osgiliath.user.model.authorization.builder.MAuthorityBuilder;
import net.osgiliath.user.repository.MUserRepository;
import net.osgiliath.user.repository.authorization.MAuthorityRepository;
 
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
 
import com.google.common.collect.Lists;
 
@Slf4j
@MessageEndpoint
public class RepositorySubscriptionService implements SubscriptionService {
@Inject
private Validator validator;
@Inject
private MUserRepository mUserRepository;
@Inject
private MAuthorityRepository mAuthorityRepository;
@Inject
private PasswordEncoder passwordEncoder;
@Inject
private SaltSource saltSource;
@Inject
private MessageChannel subscriptionErrorChannel;
 
@Override
@ServiceActivator(inputChannel = "subscribeChannel", outputChannel = "userInformationChannelOut" )
public Message onSubscription(Message arg0) {
log.info("received message for Subscription");
try {
MUser user = (MUser) arg0.getPayload();
Set&lt;ConstraintViolation&gt; validationResults = validator
.validate(user);
if (!validationResults.isEmpty()) {
log.info("subscription error, validating user:" + validationResults.iterator().next().getMessage());
// création du message d'erreur, puis envoi
subscriptionErrorChannel.send(MessageBuilder.withPayload(
validationResults.iterator().next().getMessage()).build());
return null;
} else if (!mUserRepository.findByPseudo(user.getPseudo()).isEmpty()) {
log.info("subscription error: user already exists");
subscriptionErrorChannel.send(MessageBuilder.withPayload(
"Pseudo already exists").build());
return null;
} else if (!mUserRepository.findByEmail(user.getEmail()).isEmpty()) {
log.info("subscription error: email already registered");
subscriptionErrorChannel.send(MessageBuilder.withPayload(
"Email already registered").build());
return null;
} else {
String password = user.getPassword();
password = passwordEncoder.encodePassword(password,
saltSource.getSalt(user));
user.setPassword(password);
MAuthority auth;
List auths = mAuthorityRepository
.findByAuthority(AUTHORITY.SLOGGED);
if (auths.isEmpty()) {
auth = new MAuthorityBuilder().withAuthority(AUTHORITY.LOGGED)
.build();
auth = mAuthorityRepository.save(auth);
} else {
auth = auths.iterator().next();
}
 
user.setMAuthorities(Lists.newArrayList(auth));
user = mUserRepository.save(user);
log.info("user saved with pseudo:" + user.getPseudo()
+ " saved with id: " + user.getId());
// renvoi le message dans l'output channel
return MessageBuilder.withPayload(user).build();
}
}catch (Exception e) {
log.error("Error during subscription", e);
 
}
throw new RuntimeException(
"Unexpected error on the onsubscription method");
}
 
}

Expérimentations Virgo – mise en place d’ActiveMQ et Spring-integration

Voici comment par exemple nous allons intégrer un système d’authentification relié aux couches supérieures avec JMS/Spring integration.

Tout d’abord, nous devons définir un plan Virgo recensant toutes les dépendances nécessaire au fonctionnement du messaging:

<plan name="net.osgiliath.framework.middleware.jms.plan" version="1.0.0"
	scoped="false" atomic="true" xmlns="http://www.eclipse.org/virgo/schema/plan"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
					http://www.eclipse.org/virgo/schema/plan 
					http://www.eclipse.org/virgo/schema/plan/eclipse-virgo-plan.xsd">
	<artifact name="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec" type="bundle" version="[1.0.0,2)"></artifact>
	<artifact name="org.apache.geronimo.specs.geronimo-jta_1.1_spec" type="bundle" version="[1.0.0,2)"></artifact>
	<artifact name="org.apache.geronimo.specs.geronimo-jms_1.1_spec" type="bundle" version="[1.0.0,2)"></artifact>
	<artifact name="org.apache.activemq.kahadb" type="bundle" version="[5.0.0,6)"></artifact>
	<artifact name="org.apache.activemq.activemq-core" type="bundle" version="[5.0.0,6)"></artifact>
	<artifact name="org.apache.xbean.spring" type="bundle" version="[3.0.0,4)"></artifact>
	<artifact name="jackson-core-asl" type="bundle" version="[1.9.0,2)"></artifact>
	<artifact name="jackson-mapper-asl" type="bundle" version="[1.9.0,2)"></artifact>
	<artifact name="org.springframework.integration" type="bundle" version="[2,3)"></artifact>
	<artifact name="org.springframework.integration.jms" type="bundle" version="[2,3)"></artifact>
	<artifact name="jms-config" type="configuration" version="0"></artifact>
	<artifact name="net.osgiliath.framework_framework.middleware.jms.connectionfactory.exporter" type="bundle" version="[0,1)"></artifact>
</plan>

Nous allons ensuite créer un bundle de configuration de jms (net.osgiliath.framework_framework.middleware.jms.connectionfactory.exporter).

Il contient juste un fichier de contexte Spring (dans META-INF/spring) permettant l’export de la ConnectionFactory ActiveMQ:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:amq="http://activemq.apache.org/schema/core" 
	xmlns:osgi="http://www.springframework.org/schema/osgi"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
	http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
	<amq:broker brokerName="brokerB1"
		destroyApplicationContextOnStop="true">
		<amq:destinationPolicy>
			<amq:policyMap>
				<amq:policyEntries>
					<amq:policyEntry queue=">" producerFlowControl="true"
						memoryLimit="5mb">
						<amq:deadLetterStrategy>
							<amq:individualDeadLetterStrategy
								queuePrefix="DLQ." useQueueForQueueMessages="true" />
						</amq:deadLetterStrategy>
					</amq:policyEntry>
				</amq:policyEntries>
			</amq:policyMap>
		</amq:destinationPolicy>
		<amq:managementContext>
			<amq:managementContext createConnector="false" />
		</amq:managementContext>
		<amq:persistenceAdapter>
			<amq:kahaDB directory="activeMQ/kahadb"
				enableJournalDiskSyncs="false" indexWriteBatchSize="10000"
				indexCacheSize="1000" />
		</amq:persistenceAdapter>
		<amq:plugins>
			<amq:loggingBrokerPlugin />
		</amq:plugins>
		<amq:systemUsage>
			<amq:systemUsage>
				<amq:memoryUsage>
					<amq:memoryUsage limit="420 mb" />
				</amq:memoryUsage>
				<amq:storeUsage>
					<amq:storeUsage limit="1 gb" />
				</amq:storeUsage>
				<amq:tempUsage>
					<amq:tempUsage limit="250 mb" />
				</amq:tempUsage>
			</amq:systemUsage>
		</amq:systemUsage>
		<amq:transportConnectors>
			<amq:transportConnector name="tcp"
				uri="tcp://0.0.0.0:61616" />
		</amq:transportConnectors>
	</amq:broker>
	<!-- JMS ConnectionFactory to use, configuring the embedded broker using 
		XML -->
	<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost" />
	<bean id="cachedConnectionFactory"
               class="org.springframework.jms.connection.CachingConnectionFactory">
               <property name="targetConnectionFactory" ref="jmsFactory" />
               <property name="sessionCacheSize" value="3" />
        </bean>
	<osgi:service ref="cachedConnectionFactory"
		interface="javax.jms.ConnectionFactory"/>
</beans>

Ne pas oublier de rajouter les classes qui vont bien dans le fichier template.mf (servant via Bundlor à générer le MANIFEST de l’application:

Bundle-ManifestVersion: 2
Bundle-Name: ${project.artifactId}
Bundle-Vendor: Osgiliath Inc.
Bundle-SymbolicName: ${project.groupId}_${project.artifactId}
Bundle-Version: ${project.version}
Import-Package: org.apache.activemq.broker.region.policy,
 org.apache.activemq.xbean,
 org.apache.activemq.util,
 org.apache.activemq.broker.jmx,
 org.apache.activemq.store.kahadb,
 org.apache.activemq.broker.util,
 org.apache.activemq.usage,
 org.apache.activemq.broker,
 org.apache.activemq.spring

Nous avons maintenant ActiveMQ de configuré sur notre serveur!