Je vais présenter ici quelques moyens pour faire de la sérialisation/désérialisation en XML avec Camel, selon différents besoins :
- mapping "simple" de et/ou vers des pojos java existants ;
- mapping à partir d'un fichier XSD fourni, ou mapping à partir d'un modèle Java bindé.
Les sources (mavenisées) des exemples sont fournies à la fin de ce billet.
Tout d'abord, partons d'une configuration de base :
Nos pojos existants :
EssaiClinique.java :
/** Description d'un essai. * @author NETAPSYS * @date 15 mars 2011 * @version $Revision$ $Date$ */ public class EssaiClinique implements Serializable { /** Serial ID. */ private static final long serialVersionUID = -3188027620920487407L; /** Nom de l'essai. */ private String nom; /** Nombre de patients pour l'essai. */ private Integer nbPatient; /** Date de début de l'essai. */ private Date dateDebut; /** Liste des médicaments nécessaires à l'essai. */ private List<Medicament> medicaments; /** {@inheritDoc} */ @Override public String toString() { final StringBuffer sb = new StringBuffer(); sb.append(" EssaiClinique " + this.nom + " du " + this.dateDebut); for (final Medicament medicament : this.medicaments) { sb.append(medicament); } return sb.toString(); } // Getters & Setters
Medicament.java :
/** Description d'un médicament. * @author NETAPSYS * @date 15 mars 2011 * @version $Revision$ $Date$ */ public class Medicament implements Serializable { /** Serial ID. */ private static final long serialVersionUID = 3111655483593665106L; /** Nom du médicament. */ private String nom; /** Concentration du médicament. */ private BigDecimal concentration; /** {@inheritDoc} */ @Override public String toString() { return " Medicament " + this.nom + " à concentration de " + this.concentration; } // Getters & Setters
Contexte camel :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <camelContext trace="false" xmlns="http://camel.apache.org/schema/spring"> <packageScan> <package>com.netapsys.routes</package> </packageScan> </camelContext> </beans>
Classe de test permettant d'appeller les routes :
public class RoutesTest extends CamelSpringTestSupport { /** {@inheritDoc} */ @Override public void setUp() throws Exception { super.setUp(); } /** {@inheritDoc} */ @Override protected AbstractXmlApplicationContext createApplicationContext() { return new ClassPathXmlApplicationContext("/config/camel-context.xml"); } }
Mapping Pojo/XML
Mise en place
Librairie
Si le besoin est uniquement de faire de l'export/import xml de pojos Java existant (pour faire de la sauvegarde, ou partager des données pour une application répartie par exemple), le plus simple est d'utiliser le serialiseur Xstream.
Pour cela il faut commencer par inclure camel-xstream au classpath. Avec Maven il faut rajouter la dépendance :
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-xstream</artifactId> </dependency>
Camel
Dans la définition de notre contexte camel, il faut rajouter le format qui sera utilisé dans la route :
<dataFormats> <xstream id="xstream-utf8" encoding="UTF-8"/> </dataFormats>
La route permettant la sérialisation/désérialisation :
/** Routes utilisant le marshaller xstream. * @author NETAPSYS * @date 16 mars 2011 * @version $Revision$ $Date$ */ public class XStreamRoutes extends RouteBuilder { /** @see org.apache.camel.builder.RouteBuilder#configure() */ @Override public void configure() throws Exception { this.from("direct:xstream-marshal") .marshal("xstream-utf8") .to("file:/testCamel?fileName=essais-xstream.xml"); this.from("direct:xstream-unmarshal") .unmarshal("xstream-utf8") .to("log:com.netapsys.routes?level=DEBUG"); } }
Test
Et enfin le test de nos routes :
/** Appelle les routes pour la sérialisation/désérialisation. * @throws Exception Exception lors de l'exécution des routes. */ @Test public void test() throws Exception { final EssaiClinique essai = this.getEssaiForTest(); this.template.sendBody("direct:xstream-marshal", essai); Thread.sleep(2000); this.template.sendBody("direct:xstream-unmarshal", new File("/testCamel/essais-xstream.xml")); Thread.sleep(2000); } /** Créé un Essai pour le test. * @return Essai */ private EssaiClinique getEssaiForTest() { // Créé un essai de test avec 2 médicaments. // Pour bien gérer l'export des concentrations, ne pas oublier de paramétrer le MathContext : final Medicament m1 = new Medicament(); m1.setConcentration(new BigDecimal(1.2345, MathContext.DECIMAL32)); }
Résultats
Sérialisation
En éditant le fichier XML créé (/testCamel/essais-xstream.xml), on peut voir qu'il correspond bien à nos attentes :
<?xml version='1.0' encoding='UTF-8'?> <com.netapsys.model.EssaiClinique> <nom>Essai n°1</nom> <nbPatient>3</nbPatient> <dateDebut>2011-03-16 18:10:08.270 CET</dateDebut> <medicaments> <com.netapsys.model.Medicament> <nom>Médicament 1</nom> <concentration>1.234500</concentration> </com.netapsys.model.Medicament> <com.netapsys.model.Medicament> <nom>Médicament 2</nom> <concentration>2.000100</concentration> </com.netapsys.model.Medicament> </medicaments> </com.netapsys.model.EssaiClinique>
On peut voir que par défaut XStream nomme les noeuds avec le nom complet des classes, ce qui lui permet de s'y retrouver lors de la désérialisation.
Désérialisation
En regardant les logs, on peut voir que les informations on bien été récupérées :
2011/03/16 18:29:19 DEBUG service.process(74) : Exchange[ExchangePattern:InOnly, BodyType:com.netapsys.model.EssaiClinique, Body: EssaiClinique Essai n°1 du Wed Mar 16 18:29:16 CET 2011 Medicament Médicament 1 à concentration de 1.234500 Medicament Médicament 2 à concentration de 2.000100]
Mapping à partir d'un fichier XSD / Modèle Java bindé
Il peut être nécessaire de faire évoluer une application pour exporter des données en XML, tout en respectant un fichier XSD fourni.
Dans ce cas le sérialiseur JAXB va être utilisé, et il faudra préalablement passer par une conversion en java entre nos pojos de notre modèle métier et le modèle qui sera généré. Tout d'abord, il va falloir commencer par générer le modèle Java correspondant au XSD.
Ce type de sérialisation/désérialisation peut aussi être utilisé si nos pojos sont déjà bindés.
1-Génération du modèle
XJC
XJC est un outil inclus dans le JDK qui va nous permettre de générer le modèle Java à partir d'un fichier XSD.
Le fichier XSD de référence, essais.xsd, est dans le projet camel-jaxb, lié à ce billet.
XJC a besoin d'un fichier de binding, qui contient principalement le chemin vers le fichier XSD, et le package où seront stockées les classes créées.
binding.xjb :
<?xml version="1.0" encoding="UTF-8"?> <bindings schemaLocation="../essais.xsd" xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1"> <schemaBindings> <package name="com.netapsys.model.xml" /> </schemaBindings> <globalBindings> <serializable uid="100" /> </globalBindings> </bindings>
Commande à exécuter :
"C:\Program Files\Java\jdk1.6.0_20\bin\xjc.exe" -d D:\Projet\camel-jaxb\src\main\java\ -extension D:\Projet\camel-jaxb\src\main esources\essais.xsd -b D:\Projet\camel-xsd\src\main esources\schema\binding.xjb
Explication succincte des paramètres :
- -d : répertoire où se situe le package spécifié dans le fichier xjb ;
- -extension : chemin vers le fichier xsd (attention, le fait de mettre un chemin relatif peut être source de bug lors de l'exécution de la commande) ;
- -b : emplacement du fichier xjb (même remarque que ci-dessus).
Modèle
Les traces dans les logs montrent les classes créées, qui sont visibles, dans le package spécifié dans le fichier de binding. Pour plus d'informations sur les annotations utilisées, je vous invite à consulter ce post.
Pour notre exemple, ces classes générées correspondront à un modèle métier déjà bindé.
2-Mise en place
Librairie
Pour effectuer la sérialisation/désérialisation, il faut inclure la librairie camel-jaxb au classpath. Avec maven, il faut rajouter la dépendance :
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jaxb</artifactId> </dependency>
Camel
Il n'y a pas de modification à apporter au contexte Camel.
Écrivons la route que nous appellerons pour effectuer la sérialisation/désérialisation :
/** @see org.apache.camel.builder.RouteBuilder#configure() */ @Override public void configure() throws Exception { final DataFormat dataFormat = new JaxbDataFormat("com.netapsys.model.xml"); this.from("direct:jaxb-marshal") .marshal(dataFormat) .to("file:/testCamel?fileName=essais-jaxb.xml"); this.from("direct:jaxb-unmarshal").unmarshal(dataFormat).to("log:logger.service?level=DEBUG"); }
Cette route ressemble beaucoup à la première, à la différence de l'utilisation d'une implémentation de DataFormat spécifique à JAXB, qui connaît l'emplacement du modèle à utiliser.
Test
Là aussi le test ressemble beaucoup au précédent :
/** Test de sérialisation/désérialisation. * @throws Exception Exception lors de l'exécution des routes. */ @Test public void test() throws Exception { final XMLEssaiClinique essai1 = this.getEssaiForTest(); this.template.sendBody("direct:jaxb-marshal", essai1); Thread.sleep(2000); this.template.sendBody("direct:jaxb-unmarshal", new File("/testCamel/essais-jaxb.xml")); Thread.sleep(2000); } /** Créé un Essai pour le test. * @return Bean du modèle XML pour le test. * @throws DatatypeConfigurationException Erreur lors de la conversion des données. */ private XMLEssaiClinique getEssaiForTest() throws DatatypeConfigurationException { // Attention à la gestion de la date : final XMLEssaiClinique essai1 = new XMLEssaiClinique(); essai1.setDateDebut(DatatypeFactory.newInstance() .newXMLGregorianCalendar(new GregorianCalendar())); // Valorisation de l'essai avec deux médicaments. }
La principale différence ici est le type des objets passés à la route. Il faut en effet passer des objets que JAXB connaît. Dans une application existante, cela implique la mise en place d'un mapping java entre les pojos existants (EssaiClinique.java et Medicament.java dans notre cas), vers les pojos du modèle XML générés par XJC.
3-Résultats
Sérialisation
Ici encore, le fichier XML correspond à nos attentes :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <XMLEssaiClinique> <nom>Essai n°1</nom> <nbPatient>3</nbPatient> <dateDebut>2011-03-17+01:00</dateDebut> <medicaments> <XMLMedicament> <nom>Médicament 1</nom> <concentration>1.234500</concentration> </XMLMedicament> <XMLMedicament> <nom>Médicament 2</nom> <concentration>2.000100</concentration> </XMLMedicament> </medicaments> </XMLEssaiClinique>
Désérialisation
Toujours en étudiant les logs, on peut voir que les objets ont bien été créés. Pour les besoins du test, j'ai surchargé la méthode toString() dans les classes du modèle XML générées par xjc :
XMLEssaiClinique Essai n°1 du 2011-03-17+01:00 XMLMedicament Médicament 1 à concentration de 1.234500 XMLMedicament Médicament 2 à concentration de 2.000100
Conclusion
Nous avons présenté deux méthodes pour faire de la sérialisation/désérialisation d'objets en XML, chacune des méthodes répondant à un besoin différent. Les deux premiers avantages que je vois à l'utilisation de Camel sont la simplicité de la mise en place du système, tout en gardant de nombreuses possibilités de personnalisation grâce aux différentes annotations disponibles.
Liens :
- XJC : http://download.oracle.com/javase/6/docs/technotes/tools/share/xjc.html
- Camel - XStream : http://camel.apache.org/xstream.html
- Camel - JAXB : http://camel.apache.org/jaxb.html