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
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&lt;Customer&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
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 :
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;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 :
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&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