Camel, Spring et tests JUnit pour les nuls (partie 1/3)

L'objet de ce billet est d'expliquer l'essentiel de Apache Camel avec Spring et accompagné de tests JUnit.

camel-logo.png Apache Camel est un framework puissant implémentant les patterns EIP (Enterprise Integration Patterns).

Apache Camel permet l'échange, le routage et la transformations des données avec une variété de protocoles et connecteurs.

L'architecture de Camel basée sur des composants pluggables. Il offre une panoplie de composants intégrables avec facilité à nos projets web ou non.

Apache Camel n'est pas réservée à des applications Client/Serveur, des Webservices ou des échanges JMS (Java Messaging Service).

En pratiquant Camel, on découvre que c'est un couteau suisse fort utile.

Les démos ci-après, projets java standards, aident à la prise en main rapide de Camel.

La dernière démo introduit néanmoins des notions avancées de Camel.

Chacune des démos de ce billet est organisé en trois actes :

- Acte 1: Dessiner une route Camel,

- Acte 2: Configurer Camel dans Spring,

- Acte3: Tester avec Junit

Camel v2.10, Spring v3.x et Java 7 sont utilisés bien que java 5 devrait suffire.

Les mots clés de ce billet sont : Camel, EIP, route, processor, exchange, body, header, spring, encodage, test JUnit.

Passons à la mise en pratique.

Contrairement à mes habitudes, je vous propose de démarrer par télécharger le zip du projet démo.

Ce projet nous servira de base et il sera complété à partir de la seconde partie.

Dézipper le projet dans le workspace de votre eclipse.

Puis lancer la commande:

mvn test

En principe les tests passent (au vert).

Sinon créer le répertoire /temp à la racine de votre disque (sous windows c:\temp) et relancer les tests.

Lancer la commande

mvn eclipse:eclipse

Puis importer le projet dans eclipse.

Vous pouvez par la suite reproduire, étape par étape, les démos ci-après ou exécuter directement le projet dans eclipse.

Seul le fichier pom.xml ne sera détaillé dans ce billet.

Compléter les dépendances nécessaires en s'inspirant du pom.xml fourni dans le zip.

NOTIONS ET TERMINOLOGIE

On va expliquer rapidement les notions importantes de Camel ainsi que les termes provenant de l'EIP.

Certains termes anglais ne seront pas traduits.

1- Camel contexte & Composant

Camel, au plus haut niveau, définit un contexte qui est une collection d'instances de composants.

Chaque composant est principalement une fabrique (factory) d'instances de endpoint. Voir après.

2- Endpoint

Ce terme est utilisé dans la communication inter-processus ou inter-composants.

Par exemple, en communication Client/Serveur, le client est un endpoint et le serveur en est un autre.

Selon l'utilisation, un endpoint peut référer à une adresse URL www.host:port,

Et derrière cette URL il se peut qu'il y a un logiciel ou (web)service interrogeable.

Donc, un endpoint agit comme une URI (voir ci-après) dans une application ou comme une destination JMS.

On communique avec un endpoint de deux façons:

- soit par l'envoi de message en créant un Producer attaché à cet endpoint,

- soit par consommer le message en créant un Consumer sur cet endpoint.

A noter que certains endpoints (ex. DirectEndpoint ) n'acceptent pas de consommateurs et d'autres (ex. JdbcEndpoint) n'acceptent pas de producteurs.

3- Route & RouteBuilder

Une route est l'enchainement des étapes qu'un message doit parcourir à partir de sa réception par le canal (endpoint) d'entrée.

Camel fournit deux manières d'écrire une route:

- soit par configuration XML,

- soit avec Camel java DSL (domain specific language).

Note. Dans la suite on utilise plutôt le java DSL

4- URI

Camel utilise beaucoup cette notion pour construire une route.

Quels sont les significations des URI, URN, URL?

Un URI permet d'identifier une ressource de manière permanente.

Un URI peut-être de type URL (locator) ou URN (name).

Voir le wiki pour plus de détails.

5- Message

Le Message est une interface abstraite (public abstract interface org.apache.camel.Message).
Exemples:
-la requête en entrée, la réponse ou un message d' exception.

Dans le jargon de Camel, la requête est nommée In, le retour Out et le message d'exception 'Fault message'.

Des classes Camel concrètes implémentent cette interface (ex.GenericFileMessage, MailMessage).

6- Exchange

Exchange est une interface abstraite pour l'échange de messages.

C'est un conteneur de messages qui tient à jour les informations durant l'acheminement du message reçu.

DefaultExchange est une classe concrète dans Camel implémentant l'interface Exchange.

7- CamelTemplate

La classe Camel nommée auparavant CamelClient a été renommée en CamelTemplate
afin de se conformer au nommage et convention rendus célèbres par Spring.

La classe CamelTemplate est un wrapper qui permet l'envoi de message (exchange).

Avec ces notions, je crois que nous sommes bien armés pour débuter une première démo.

PREMIERE DEMO: Loguer avec Camel

Nous commençons par une démo rapide et facile pour illustrer la puissance de Camel.

Dans cette démo, on souhaite tracer des logs.

Et il ne faudrait qu'une seule ligne de code.

Mais on va détailler un peu et écrire les briques séparément en suivant les trois actes précitées.

Acte1: Dessiner une route

A l'aide du java DSL (Domain Specific Language), écrivons une première classe MyFirstRoute qui déclare une route Camel:

package fr.netapsys.camel.routes;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;

public class MyFirstRoute extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    from("direct://aa").to("log:fr.netapsys.camel?level=DEBUG").end();
 }
}

Noter que MyFirstRoute hérite de RouteBuilder d'où la méthode configure() surchargée.

Le "from" précise le endpoint d'entrée qui est un composant Camel DirectComponent.

Le "direct://aa" veut dire simplement que la route démarre avec un DirectEndpoint nommé "aa".

DirectEndpoint assure une communication synchronisée.

Pour envoyer un message à "direct://aa", il faudrait instancier un ProducerTemplate puis appeler l'une des méthodes sendXXXX.

Le "to" indique que c'est vers le composant log que le message sera transmis.

L'URI du composant log d'Apache Camel a le format suivant:

log:loggingCategory[?options] 

L'option, level=DEBUG, de notre cas indique le niveau DEBUG associé au logger (catégorie) "fr.netapsys.camel". Voir la documentation de log4j.

Nous venons de dessiner une route Camel qui a deux endpoints.

L'une d'entrée "direct://aa" et l'autre est le composant log.

Il ne reste qu'à envoyer un message à "direct:/:aa" et il sera loggué. C'est ce que nous ferons dans le test JUnit (acte3).

Il est temps de configurer le contexte Camel dans Spring.

Acte2: Configurer Spring

Le fichier spring doit par convention être localisé dans META-INF/spring et nommé camel-context.xml.

En respectant cette convention, Spring charge automatiquement le contexte Camel et le démarre.

Voici les quelques lignes xml valables pour toutes les démos:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:camel="http://camel.apache.org/schema/spring"
  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">	
  
<context:component-scan base-package="fr.netapsys.camel"/>
 <camel:camelContext
	xmlns="http://camel.apache.org/schema/spring">
     <camel:package>fr.netapsys.camel.routes</camel:package>	
</camel:camelContext>

Explications:

Nous avons déclaré dans camelContext le package où sont déclarées nos routes camel.

Et context:componet-scan permet à spring d'auto-injecter les beans (ex. les processors).

C'est tout!

Acte3: Écrire le test Junit

Le code du test JUnit est :

package fr.netapsys.camel.tests;
import org.apache.camel.ProducerTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:META-INF/spring/camel-context.xml"})
public class TestMyFirstRoute {
 @Autowired CamelContext camelCtx;
 @Test
 public void testCamel() throws Exception {		
  ProducerTemplate template=camelCtx.createProducerTemplate();		
     //send msg to log
     template.sendBody("direct://aa", "\nloggue moi  \n");
  }
}

Explications:

Camel étant configuré dans Spring, le contexte Camel est démarré automatiquement, il suffit d'injecter (via @Autowired) une instance de CamelContext.

Avec l'instance camelContext, on peut créer un ProducerTemplate: une template à la sauce de spring facilitant l'envoi de message.

La méthode sendBody utilisée ici attend deux arguments: Un endpoint et un body de l'exchange (message).

Ici, on lui a passé les deux arguments "direct://aa" et le contenu du message qui est un String.

Un point de vigilance est de reprendre le endpoint direct://aa tel qu'il a été déclaré dans la route Camel.

Enfin, la commande mvn test affiche la sortie sur la console dos:

Running fr.netapsys.camel.tests.TestMyFirstRoute
xxxxxx camel [DEBUG] Exchange[ExchangePattern:InOnly, 
   BodyType:String, Body: loggue moi]

REMARQUE IMPORTANTE

Le même résultat aurait pu être obtenu avec la configuration suivante de notre route:

 from("direct://log")
  .log(LoggingLevel.DEBUG,"fr.netapsys.camel","${body}")
  .end();

Et le test JUnit devient:

template.sendBody("direct://log","loggue moi");

DEUXIEME DEMO: Transformer les données vers un fichier

Dans la classe MySecondRoute, la route trace l'enchainement suivant:

- Le direct endpoint "direct://a" est celui qui réceptionne le message,

- Une transformation simple est opérée sur le message,

- Une conversion du message selon un encodage prédéfini,

- Sauvegarde du message dans un fichier.

En résumé on a donc cet enchainement:

uriDirect -> Processor (transformation) -> Encodage ->uriFile.

Acte1: Dessiner une route

Voici la classe MySecondRoute qui hérite de RouteBuilder et définit la méthode configure():

package fr.netapsys.camel.routes;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;

public class MySecondRoute extends RouteBuilder {
  @Override
  public void configure() throws Exception {
    from("direct://starting")
     .transform().simple("Bonjour,\n ${in.body} \nSincères salutations")
     .convertBodyTo(String.class,"iso-8859-1")
     .setHeader(Exchange.FILE_NAME, constant("camelroute2.txt"))     
     .to("file:/temp/?fileExist=Override")
     .end();
  }
}

Les déclarations importantes ici sont:

- Un DirectEndpoint nommé "//starting",

- Une transformation simple du message est effectué à l'aide de la méthode transform,

- Une conversion du code en String avec l'encodage iso-8859-1,

- Un FileEndpoint avec son header qui fixe le nom du fichier où stocker le message. L'option fileExist permet d'écraser le fichier s'il existe.

Acte2: Configurer Spring

Rien à faire si la route est déclarée dans le package fr.netapsys.camel.routes.

Acte3: Tester avec JUnit

package fr.netapsys.camel.tests;
import org.apache.camel.ProducerTemplate;
import org.junit.Test;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:META-INF/spring/camel-context.xml"})
public class TestMySecondeRoute{
@Autowired CamelContext camelCtx;
@Test
public void testCamel() throws Exception {		
 ProducerTemplate template=camelCtx.createProducerTemplate();
 template.sendBody("direct://starting",
  "\nMonsieur Néçàn\nNous avons bien reçu votre demande...\nblabla\n");
 }
}

La commande mvn test produira un fichier nommé camelroute2.txt dans le répertoire /temp.

TROISIEME DEMO: Exécuter script shell: Niveau avancé

L'objet de cette démo avancée est de pouvoir envoyer des commandes batch windows.

Le canal d'entrée attend un message (body) ayant le format suivant:

RepertoireExec;commande;optionCmd

Noter que le séparateur est un point virgule.

En résumé la route est composée de cet enchainement:

uriDirect -> Processor1 ->uriExecShell ->Processor2 -> uriFile.

La suite dans la prochaine partie.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.