Ayez le réflexe Camel !

EIP, ESB, SOA … A travers ces acronymes barbares, l’utilisation d’Apache Camel (http://camel.apache.org/index.html) peut paraître effrayant.

Il ne s’agit pas ici de vous faire un article “Camel pour les nuls” mais de vous montrer que Camel met à notre disposition offre une palette d’outils facilement intégrable à nos webapps et qui peuvent nous rendre bien des services.

Création de notre plateforme d’exécution Camel

Dans un premier temps nous allons utiliser la servlet de camel pour faciliter nos tests.

Le pom.xml

 <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>fr.ippon.sample</groupId> <artifactId>customer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Camel Router Application</name> <description>Camel project that deploys the Camel routes as a WAR</description> <url>http://www.ippon.fr</url> <packaging>war</packaging> <dependencies> <!-- Camel Dependencies --> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring</artifactId> <version>2.8.1</version> </dependency> <!-- Spring Web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>3.0.5.RELEASE</version> </dependency> <!-- logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> </dependencies> <build> <defaultGoal>install</defaultGoal> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <!-- plugin so you can run mvn jetty:run --> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>7.4.5.v20110725</version> <configuration> <webAppConfig> <contextPath>/</contextPath> </webAppConfig> <scanIntervalSeconds>10</scanIntervalSeconds> </configuration> </plugin> </plugins> </build> </project>

Le web.xml

Ensuite il est nécessaire de déclarer la servlet camel dans le web.xml :

 <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Camel server</display-name> <!-- Spring configuration --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/context/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Camel servlet --> <servlet> <servlet-name>CamelServlet</servlet-name> <servlet-class>org.apache.camel.component.servlet.CamelHttpTransportServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CamelServlet</servlet-name> <url-pattern>/camel/*</url-pattern> </servlet-mapping> </web-app>

Le fichier de configuration Spring

 <?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:context="http://www.springframework.org/schema/context" 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://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <camelContext xmlns="http://camel.apache.org/schema/spring"> <package>fr.ippon.sample.customer</package> </camelContext> </beans>

La balise permet de définir le context qui sera utilisé par Camel (http://camel.apache.org/advanced-configuration-of-camelcontext-using-spring.html).

La balise fonctionne de manière similaire à spring et se charge d’identifier les classes qui déclarent une route.

Le RouteBuilder

Nous allons maintenant créer une classe permettant de déclarer des routes (http://camel.apache.org/routes.html).

package fr.ippon.sample.customer.server; import org.apache.camel.builder.Builder; import org.apache.camel.builder.RouteBuilder; public class CustomerServiceRouteBuilder extends RouteBuilder { /** * @see org.apache.camel.builder.RouteBuilder#configure() */ @Override public void configure() throws Exception { from("servlet:///hello").transform(Builder.constant("Hello world")); } }

Quelques explications :

  • from("servlet:///hello") permet de “capturer” les requêtes provenant de la servlet camel dont le path est hello.
  • transform(...) permet de transformer le message.
  • Builder.constant("Hello world") ajoute la chaîne de caractères “Hello world” dans la réponse.

Le test

Pour tester notre projet, il suffit de lancer jetty via la commande mvn jetty:run et d’ouvrir un navigateur à l’URL http://localhost:8080/camel/hello

Hello word

La boîte à outils

Maintenant que nous allons nous servir de notre plateforme d’exécution Camel pour tester les nombreux fonctionnalités de Camel.

Appeler un bean spring

Au travers du composant bean (http://camel.apache.org/bean.html), Camel offre la possibilité de router un message vers un bean spring.

Pour se faire, nous allons créer un service de liste de clients.

Le POJO

package fr.ippon.sample.customer.model; public class Customer { private String name; public Customer() { super(); } public Customer(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

L’interface du service

package fr.ippon.sample.customer.service; import java.util.List; import fr.ippon.sample.customer.model.Customer; public interface CustomerService { List findAllCustomers(); List findFrenchCustomers(); List findEnglishCustomers(); }

Et enfin son implémentation

 package fr.ippon.sample.customer.service.impl; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; import fr.ippon.sample.customer.model.Customer; import fr.ippon.sample.customer.service.CustomerService; @Component("defaultCustomerService") public class DefaultCustomerService implements CustomerService { @Override public List&amp;lt;Customer&amp;gt; findAllCustomers() { Lis<Customer> customers = new ArrayList<Customer>(); customers.addAll(findFrenchCustomers()); customers.addAll(findEnglishCustomers()); return customers; } @Override public List<Customer> findFrenchCustomers() { List<Customer> customers = new ArrayList<Customer>(); customers.add(new Customer("Jean Dupont")); customers.add(new Customer("Paul Durand")); return customers; } @Override public List<Customer> findEnglishCustomers() { List<Customer> customers = new ArrayList<Customer>(); customers.add(new Customer("John Doe")); customers.add(new Customer("Adam Smith")); return customers; } }

Ensuite il nous faut déclarer ce service dans le context spring en modifiant le fichier applicationContext.xml :

 <context:component-scan base-package="fr.ippon.sample.customer" />

Maintenant nous pouvons créer une route vers ce service avec Camel (fr.ippon.sample.customer.route.CustomerServiceRouteBuilder.configure())

from("servlet:///customer/list") .to("bean:defaultCustomerService?method=findAllCustomers");

En relançant de serveur jetty on obtient le résultat suivant à l’URL http://localhost:8080/camel/customer/list :

fr.ippon.sample.customer.model.Customer@2b650cea, fr.ippon.sample.customer.model.Customer@7732ece7, fr.ippon.sample.customer.model.Customer@305f6ed4, fr.ippon.sample.customer.model.Customer@4965391b]

Transformation du résultat

Nous allons maintenant transformer le résultat de manière à ce que la liste puisse être affichée en XML.

Camel fournit un ensemble de “data format” (http://camel.apache.org/data-format.html) permettant de transformer le rendu des messages.

Pour afficher la liste résultante en XML à l’aide de xstream (< a href=”http://camel.apache.org/xstream.html”>http://camel.apache.org/xstream.html), il suffit d’ajouter la dépendance suivante dans le pom.xml

org.apache.camel camel-xstream 2.8.1

puis de créer une nouvelle route :

 from("servlet:///customer/list/xml").to("bean:defaultCustomerService?method=findAllCustomers") .marshal().xstream();

En relançant de serveur jetty nous obtenons le résultat suivant à l’URL http://localhost:8080/camel/customer/list/xml :

 <list> <fr.ippon.sample.customer.model.Customer> <name>Jean Dupont</name> </fr.ippon.sample.customer.model.Customer> <fr.ippon.sample.customer.model.Customer> <name>Paul Durand</name> </fr.ippon.sample.customer.model.Customer> <fr.ippon.sample.customer.model.Customer> <name>John Doe</name> </fr.ippon.sample.customer.model.Customer> <fr.ippon.sample.customer.model.Customer> <name>Adam Smith</name> </fr.ippon.sample.customer.model.Customer> </list>

Si maintenant nous souhaitons afficher la liste sous un format JSON, il suffit d’utiliser le data format JSON (http://camel.apache.org/json.html) et de paramétrer la route suivante :

from("servlet:///customer/list/json") .to("bean:defaultCustomerService?method=findAllCustomers") .marshal().json();

Nous obtenons ainsi le résultat suivant :

 {"list":{"fr.ippon.sample.customer.model.Customer":[{"name":"Jean Dupont"},{"name":"Paul Durand"},{"name":"John Doe"},{"name":"Adam Smith"}]}}

Ecriture dans un fichier

Camel fournit un composant permettant de gérer les fichiers (http://camel.apache.org/file2.html).

Pour enregistrer la liste des clients sous la forme XML dans un fichier, il suffit de déclarer la route suivante :

 from("servlet:///customer/list/export") .to("bean:defaultCustomerService?method=findAllCustomers") .marshal().xstream() .to("file:/home/data/out/?fileName=customers.xml");

Ce composant permet également de copier automatiquement des fichiers d’un répertoire à un autre, nous évitant au passage de créer un job Quartz et un service de copie

 from("file:/home/camel/data/in").to("file:/home/camel/data/out");

Envoi dans un mail

Camel fournit également un composant permettant d’envoyer des mails (http://camel.apache.org/mail.html).

Pour cela, il est nécessaire de modifier le pom :

org.apache.camel camel-mail 2.8.1

et de déclarer la route suivante :

 from("servlet:///customer/list/mail").to("bean:defaultCustomerService?method=findAllCustomers") .marshal().xstream() .process(new Processor() { @Override public void process(Exchange exchange) throws Exception { exchange.getIn().setHeader("to", "waaza@yopmail.com"); exchange.getIn().setHeader("subject", "send by camel"); } }) .to("smtp://host?username=sender&amp;amp;password=password");

Envoi sur un serveur FTP

Camel fournit également un composant permettant la communication avec un serveur FTP (http://camel.apache.org/ftp2.html).

Pour cela, il est nécessaire de modifier le pom :

org.apache.camel camel-ftp 2.8.1

et de déclarer la route suivante :

 from("servlet:///customer/list/ftp").to("bean:defaultCustomerService?method=findAllCustomers") .marshal().xstream() .to("ftp://user@host:port/directory?username=user&amp;password=password");

Agrégation de services

Camel peut être utilisé pour réaliser de l’agrégation de service. Dans notre exemple de liste de clients, nous avons créé deux méthodes findEnglishCustomers et findFrenchCustomers.

L’idée est de proposer un seul et même service réalisant l’appel à ces deux méthodes.

Pour cela, Camel implémente le pattern multicast (http://camel.apache.org/multicast.html). Il suffit alors de créer les routes suivantes :

 from("servlet:///customer/agregate/xml").multicast( new AggregationStrategy() { @Override public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { Message newIn = newExchange.getIn(); if (oldExchange == null) { return newExchange; } else { List oldBody = oldExchange.getIn().getBody(List.class); newIn.getBody(List.class).addAll(oldBody); return newExchange; } } }).parallelProcessing() .timeout(500).to("direct:en", "direct:fr").end().marshal() .xstream(); from("direct:en").to("bean:defaultCustomerService?method=findEnglishCustomers"); from("direct:fr").to("bean:defaultCustomerService?method=findFrenchCustomers");

Quelques explications :

Le principe est de dispatcher le message vers les deux routes direct:en et direct:fr qui seront en charge de lister respectivement les clients anglais et les clients français.

Le multicast permet d’agréger ensuite les résultats en suivant la stratégie décrite par la classe interne AggregationStrategy.

Conclusion

J’espère vous avoir convaincu que dans certains cas Camel peut s’avérer très utile dans nos développements.

Il est possible d’intégrer Camel simplement dans nos Web App. Je vous invite à regarder de plus près l’ensemble des composants Camel (http://camel.apache.org/components.html) ainsi que ses patterns (http://camel.apache.org/enterprise-integration-patterns.html).

Les sources sont disponibles ici