Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a Camel route which takes XML and bind some data to JPA annotated POJO?

I am new to Apache Camel and mock testing so here it goes...

I have an XML with no XSD schema which I have no influence on. Child elements of this XML hold data which I want to bind to my business pojo. This POJO (WeatherCurrent) is already JPA annotated and I was thinking of adding JAXB annotation so the splitted XML could be mapped to my POJO.

As this XML has a root element and I only want its childs (metData) I have a problem how to annotate my POJO as I can not use @XmlRootElement.

This is partialy described here: http://camel.apache.org/splitter.html at Streaming big XML payloads using Tokenizer language chapter. My POJO is like order xml element in that example. I need just a few elements out of metData xml element to map to my POJO fields.

There is also a chapter Partial marshalling/unmarshalling at http://camel.apache.org/jaxb.html but there is no JAVA DSL example (a must), nor how to annotate the pojo to work with XML fragments.

So far I have this test code:

import java.io.File;

import org.apache.camel.EndpointInject;
import org.apache.camel.Exchange;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.converter.jaxb.JaxbDataFormat;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class WeatherCurrentTest extends CamelTestSupport {

    @EndpointInject(uri = "file:src/test/resources")
    private ProducerTemplate inbox;

    @Override
    protected RouteBuilder createRouteBuilder() throws Exception {
        return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                DataFormat jaxbDataFormat = new JaxbDataFormat("com.mycompany.model.entities.weather");// WARNING two packages for JaxbDataFormat

                from("file:src/test/resources/?fileName=observation_si_latest.xml&noop=true&idempotent=false")
                .split()
                .tokenizeXML("metData")
                .unmarshal(jaxbDataFormat)
                .to("mock:meteo");          
            }
        };
    }

    @Test
    public void testMetData() throws Exception {
        MockEndpoint mock = getMockEndpoint("mock:meteo");
        mock.expectedMessageCount(9);

        File meteo = new File("src/test/resources/observation_si_latest.xml");
        String content = context.getTypeConverter().convertTo(String.class, meteo);
        inbox.sendBodyAndHeader(content, Exchange.FILE_NAME, "src/test/resources/observation_si_latest.xml");

        mock.assertIsSatisfied();
    }

}

The XML (observation_si_latest.xml) comes in this form:

<?xml version="1.0" encoding="UTF-8"?>
<data id="MeteoSI_WebMet_observation_xml">
    <language>sl</language>
    <metData>
        <domain_altitude>55</domain_altitude>
        <domain_title>NOVA GORICA</domain_title>
        <domain_shortTitle>BILJE</domain_shortTitle>
        <tsValid_issued>09.03.2012 15:00 CET</tsValid_issued>
        <t_degreesC>15</t_degreesC>
    </metData>
    <metData>
        <domain_meteosiId>KREDA-ICA_</domain_meteosiId>

I left out lots of elements of the metData elements for brevity. I want to map (amongs others) domain_title to my JPA annotated POJO's station field and later save it database, hopefully all in one smart and short Camel route.

The POJO (no JAXB annotations yet):

@Entity
@Table(name="weather_current")
public class WeatherCurrent implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    private String station;

    @Temporal( TemporalType.TIMESTAMP)
    @Column(name="successfully_updated")
    private Date successfullyUpdated;

    private short temperature;

    @Column(name="wind_direction")
    private String windDirection;

}

I also left out lots of fields and methods.

So the idea is to map the value of the *domain_title* to WeatherCurrent POJO's station field and to do so for each metData element and save a list of WeatherCurrent objects to database.

Any advice on the best way on how to implement this is welcome.

like image 213
bbcooper Avatar asked Mar 14 '12 12:03

bbcooper


1 Answers

It turns out I had one wrong assumption of not being able to use the @XmlRootElement. The route and the test passes with success after I annotated the POJO and added jaxb.index file beside it. Will post the solution later or tomorrow as I am on a train now.

Hours later...

The JAXB annotations on the POJO (on top of JPA ones):

@Entity
@Table(name="weather_current")
@XmlRootElement(name = "metData")
@XmlAccessorType(XmlAccessType.FIELD)
public class WeatherCurrent implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    @XmlElement(name = "nn_shortText")
    private String conditions;

    @XmlElement(name = "rh")
    private short humidity;

    @XmlElement(name = "msl")
    private short pressure;

    @Column(name="pressure_tendency")
    @XmlElement(name = "pa_shortText")
    private String pressureTendency;

    @Temporal( TemporalType.TIMESTAMP)
    @XmlElement(name = "tsValid_issued")
    private Date published;

    @XmlElement(name = "domain_longTitle")
    private String station;

enabled me to get a list of WeatherCurrent Exchage objects. Just for test I routed each one to my EchoBean to print out one property:

.unmarshal(jaxbDataFormat).bean(EchoBean.class, "printWeatherStation")

and EchoBean:

public class EchoBean {

    public String printWeatherStation(WeatherCurrent weatherCurrent) {
        return weatherCurrent.getStation();
    }
}

nicely prints out the names of the weather stations with the log Camel component.

The one undocumented thing that bothered me was that I had to put this jaxb.index file next WeatherCurrent java source although at http://camel.apache.org/jaxb.html it clearly says that jaxb context is initialized with

DataFormat jaxb = new JaxbDataFormat("com.acme.model");
like image 132
bbcooper Avatar answered Sep 28 '22 09:09

bbcooper