1 pièce(s) jointe(s)
[camel] Convertir CSV->XML avec bindy jaxb
Bonjour.
Une route camel pour convertir du CSV en XML avec Camel bindy et jaxb.
Créez un projet camel (j'utilise maven pour cela)
Ajoutez camel-bindy et camel-jaxb dans les dépendances (2.8.0 pour moi)
Le but est de transformer des données CSV comme
Code:
1 2
| 4;12235;CHIR;5802;10000446;30;14078;SPE;1
4;19999;CHIR;5888;10000446;30;14078;SPE;1 |
En XML
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<reservation xmlns="urn:org.sekaijin.csv.to.xml.metier">
<poste>
<ph1>4</ph1>
<ph2>12235</ph2>
<ph3>CHIR</ph3>
<ph4>5802</ph4>
<ph5>10000446</ph5>
<ph6>30</ph6>
<ph7>14078</ph7>
<ph8>SPE</ph8>
<ph9>1</ph9>
</poste>
<poste>
<ph1>4</ph1>
<ph2>19999</ph2>
<ph3>CHIR</ph3>
<ph4>5888</ph4>
<ph5>10000446</ph5>
<ph6>30</ph6>
<ph7>14078</ph7>
<ph8>SPE</ph8>
<ph9>1</ph9>
</poste>
</reservation> |
Vous pouvez partir d'un Schéma XSD pour générer les classes avec Jaxb mais il faut les modifier.
Je préfère donc partir directement du code Java
package-info.java: ce fichier sert à définir le namespace du schéma. sans changement s'il a été généré
Code:
1 2 3 4 5 6 7
| @XmlSchema(
namespace = "urn:org.sekaijin.csv.to.xml.metier",
elementFormDefault = XmlNsForm.QUALIFIED
)
package org.sekaijin.cvs.to.xml;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema; |
Il en va de même pour ObjectFactory.java
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.sekaijin.cvs.to.xml;
import javax.xml.bind.annotation.XmlRegistry;
@XmlRegistry
public class ObjectFactory {
public ObjectFactory() {
}
public Reservation createReservation() {
return new Reservation();
}
public Poste createPoste() {
return new Poste();
}
} |
Reservation.java contient la définition d'une réservation. Par facilité, j'ai ajouté une méthode setPoste qui ajoute un poste. La classe générée par Jaxb nécessite de setter tous les postes d'un coup.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package org.sekaijin.cvs.to.xml;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"postes"})
@XmlRootElement(name = "reservation")
public class Reservation {
@XmlElement(required = false, name = "poste")
protected List<Poste> postes;
public List<Poste> getPostes() {
if (postes == null) {
postes = new ArrayList<Poste>();
}
return this.postes;
}
public Reservation(){
}
public void setPoste(Poste poste){
this.getPostes().add(poste);
}
} |
Poste.js c’est ici que se passe l'opération la plus intéressante par rapport au code généré par Jaxb j'ai ajouté toutes les annotations nécessaires à Bindy pour lire le CSV
ainsi chaque champ est annoté pour jaxb et pour Bindy
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package org.sekaijin.cvs.to.xml;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
import org.apache.camel.dataformat.bindy.annotation.DataField;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Poste", propOrder = {
"ph1",
"ph2",
"ph3",
"ph4",
"ph5",
"ph6",
"ph7",
"ph8",
"ph9"
})
@CsvRecord(separator = ";", crlf = "UNIX", skipFirstLine = false)
public class Poste {
@DataField(pos = 1)
@XmlElement(required = true)
protected String ph1;
@DataField(pos = 2)
@XmlElement(required = true)
protected String ph2;
@DataField(pos = 3)
@XmlElement(required = true)
protected String ph3;
@DataField(pos = 4)
@XmlElement(required = true)
protected String ph4;
@XmlElement(required = true)
@DataField(pos = 5)
protected String ph5;
@DataField(pos = 6)
@XmlElement(required = true)
protected String ph6;
@DataField(pos = 7)
@XmlElement(required = true)
protected String ph7;
@DataField(pos = 8)
@XmlElement(required = true)
protected String ph8;
@DataField(pos = 9)
@XmlElement(required = true)
protected String ph9;
public String getPh1() {
return ph1;
}
public void setPh1(String value) {
this.ph1 = value;
}
// je vous laisse ajouter tous les autres getters et setters
} |
Transform.java cette classe sert à partir de la map de Poste à construire un objet Réservation
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package org.sekaijin.cvs.to.xml;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
public class Transform {
public void process(Exchange exchange) throws Exception {
Message msg = exchange.getIn();
@SuppressWarnings("unchecked")
List<Map<String, Poste>> orders = (List<Map<String, Poste>>) msg.getBody();
ObjectFactory objectFactory = new ObjectFactory();
Reservation resa = objectFactory.createReservation();
Iterator<Map<String, Poste>> orderIterator = orders.iterator();
while (orderIterator.hasNext()) {
Map<String, Poste> n = orderIterator.next();
resa.setPoste(n.get(Poste.class.getName()));
}
exchange.getIn().setBody(resa);
}
} |
Il reste à définir la route
from ce que vous voulez -> bindy qui donne une Map de "Poste" -> Transform qui créé un Réservation et y place la liste des Poste -> jaxb qui en fait un XML -> to ce que vous voulez
J'ai ajouté des log pour y voir clair
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.sekaijin.cvs.to.xml;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.converter.jaxb.JaxbDataFormat;
import org.apache.camel.model.dataformat.BindyType;
public class MyRouteBuilder extends RouteBuilder {
public void configure() {
JaxbDataFormat jaxbDataFormat = new JaxbDataFormat(Reservation.class.getPackage().getName());
jaxbDataFormat.setPrettyPrint(true);
from("direct:start")
.to("log: IN ==>")
.unmarshal().bindy(BindyType.Csv, Poste.class.getPackage().getName())
.bean(Transform.class)
.marshal(jaxbDataFormat)
.to("log: OUT ==>")
.to("mock:result")
;
}
} |
et c'est tout j'ai utilisé direct et mock pour faire des tests, mais file http ou tout autre protocole fonctionne de la même façon.
Une petite classe de test pour vérifier le tout: RouteTest (ajouter camel-test au dépendances)
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package org.sekaijin.cvs.to.xml;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
public class RouteTest extends CamelTestSupport {
@Override
public String isMockEndpoints() {
// override this method and return the pattern for which endpoints to
// mock.
// use * to indicate all
return "*";
}
@Test
public void testRoute() throws Exception {
String CamelFileName = "sample.csv";
String inMsg = "4;12235;CHIR;5802;10000446;30;14078;SPE;1" + "\n"
+ "4;19999;CHIR;5888;10000446;30;14078;SPE;1";
String ouMsg = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
"<reservation xmlns=\"urn:org.sekaijin.csv.to.xml.metier\">\n" +
" <poste>\n" +
" <ph1>4</ph1>\n" +
" <ph2>12235</ph2>\n" +
" <ph3>CHIR</ph3>\n" +
" <ph4>5802</ph4>\n" +
" <ph5>10000446</ph5>\n" +
" <ph6>30</ph6>\n" +
" <ph7>14078</ph7>\n" +
" <ph8>SPE</ph8>\n" +
" <ph9>1</ph9>\n" +
" </poste>\n" +
" <poste>\n" +
" <ph1>4</ph1>\n" +
" <ph2>19999</ph2>\n" +
" <ph3>CHIR</ph3>\n" +
" <ph4>5888</ph4>\n" +
" <ph5>10000446</ph5>\n" +
" <ph6>30</ph6>\n" +
" <ph7>14078</ph7>\n" +
" <ph8>SPE</ph8>\n" +
" <ph9>1</ph9>\n" +
" </poste>\n" +
"</reservation>\n";
getMockEndpoint("mock:direct:start").expectedBodiesReceived(inMsg);
getMockEndpoint("mock:result").expectedBodiesReceived(ouMsg);
template.sendBodyAndHeader("direct:start", inMsg, "CamelFileName", CamelFileName);
assertMockEndpointsSatisfied();
}
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new MyRouteBuilder();
}
} |
Et voilà l'affaire et dans le sac.
En partant de Jaxb et en ajoutant les annotations, le travail est plutôt facile. Mais il ne faut par régénérer les classes.
La "difficulté" vient du fait que Bindy fournit une liste d'objet alors que Jaxb attend un objet unique. ça se règle avec la classe Transform
"l'astuce" consiste à utiliser la même classe (Poste) comme modèle du CSV et XML(Poste)
A+JYT