You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 143 Next »

Bindy

Available as of Camel 2.0

The idea that the developers have followed to design this component was to allow the binding of non structured data (or to be more precise non-XML data)
to Java Bean using annotations. Using Bindy, you can bind data like :

  • CSV record,
  • Fixedlength record,
  • FIX messages,
  • or any other non-structured data

to one or many POJOS and to convert the data according to the type of the java property. POJOS can be linked together. Moreover, for data type like Date, Double, Float, Integer, Short, Long and BigDecimal, you can provide the pattern to apply during the formatting of the property.

For the BigDecimal number, you can also define the precision and the decimal or grouping separators

Type

Format Type

Pattern example

Link

Date

DateFormat

"dd-MM-yyyy"

http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html

Decimal*

Decimalformat

"##.###.###"

http://java.sun.com/j2se/1.5.0/docs/api/java/text/DecimalFormat.html

Decimal* = Double, Integer, Float, Short, Long

Be careful

This first release only support CSV record.

To work with camel-bindy, you must first define your model in a package (e.g. com.acme.model) and for each model class (e.g. Order, Client, Instrument, ...) associate the required annotations (described hereafter) with Class or property name.

FIX messages

To work with FIX messages, the model must contain : Header and Trailer classes linked to the root class which could be a Order class. This is not mandatory but will be helful when you will use camel-bindy in combination with camel-fix which is a Fix gateway based on quickFix project [http://www.quickfixj.org.

Annotations

The annotations created allow to map different concept of your model to the POJOs like :

  • Type of record/message (csv, fixed length, key value pair (e.g. FIX message) ...),
  • Link,
  • Data field and their properties (int, type, ...)
  • Key value Pair field

This section will describe them :

CsvRecord

The CsvRecord annotation is used to identified the root class of the model. It represents a record = a line of a CSV file and can be linked to several children model classes.

Annotation name

Record type

Level

CsvRecord

csv

Class

Parameter name

type

Info

separator

string

mandatory - can be ',' or ';' or 'anything'

skipFirstLine

boolean

optional - default value = false - allow to skip the first line of the CSV file

 

 

This annotation is associated to the root class of the model and must be declared one time.

case 1 : separator = ','

The separator used to segregate the fields in the CSV record is ',' :

10, J, Pauline, M, XD12345678, Fortis Dynamic 15/15, 2500, USD,08-01-2009

Separator ,
@CsvRecord( separator = "," )
public Class Order {
...
}

case 2 : separator = ';'

Compare to the previous cae, the separator here is ';' instead of ',' :

10; J; Pauline; M; XD12345678; Fortis Dynamic 15/15; 2500; USD; 08-01-2009

Separator ;
@CsvRecord( separator = ";" )
public Class Order {
...
}

case 3 : separator & skipfirstline

The feature is interesting when the client wants to have in the first line of the file, the name of the data fields :

order id, client id, first name, last name, isin code, instrument name, quantity, currency, date

Separator & skipFirstLine
@CsvRecord(separator = ",", skipFirstLine = true)
public Class Order {
...
}

The link annotation will allow to link objects together.

Annotation name

Record type

Level

Link

all

Class & Property

Parameter name

type

Info

linkType

LinkType

optional - by default the value is LinkType.oneToOne - so you are not obliged to mention it

 

 

Only one-to-one relation is allowed.

e.g : If the model Class Client is linked to the Order class, then use annotation Link in the Order class like this :

Property Link
@CsvRecord(separator = ",")
public class Order {

    @DataField(pos = 0)
    private int orderNr;

    @Link
    private Client client;
...

AND for the class Client :

Class Link
@Link
public class Client {
...
}

DataField

The DataField annotation defines the property of the field. Each datafield is identified by its position in the record, a type (string, int, date, ...) and optionaly of a pattern

Annotation name

Record type

Level

DataField

all

Property

Parameter name

type

Info

int

pos

mandatory - digit number

pattern

string

optional - default value = "" - will be used to format Decimal, Date, ...

length

int

optional - digit number - represents the length of the field for fixed length format

precision

int

optional - digit number - represents the precision to be used when the Decimal number will be formatted/parsed

case 1 : position

This parameter represents the position of the field in the csv record

Position
@CsvRecord(separator = ",")
public class Order {

    @DataField(pos = 0)
    private int orderNr;

    @Link 
    private Client client; -- class to link

    @DataField(pos = 4)
    private String isinCode;

...
}

As you can see in this example the position starts at '0' but continues at '4'. The number '1' to '3' are defined in the class linked to Order.

Position continues in another model class
public class Client {

    @DataField(pos = 1)
    private String clientNr;

    @DataField(pos = 2)
    private String firstName;

    @DataField(pos = 3)
    private String lastName;
...
}

case 2 : pattern

The pattern allows to enrich the format of your data

Pattern
@CsvRecord(separator = ",")
public class Order {

    @DataField(pos = 0)
    private int orderNr;

    @Link
    private Client client;

    @DataField(pos = 4)
    private String isinCode;

    @DataField(name = "Name", pos = 5)
    private String instrumentName;

    @DataField(pos = 6, precision = 2)
    private BigDecimal amount;

    @DataField(pos = 7)
    private String currency;

    @DataField(pos = 8, pattern = "dd-MM-yyyy") -- pattern
    private Date orderDate;
...
}

case 3 : precision

The precision is helpful when you want to define the decimal part of your number

Precision
@CsvRecord(separator = ",")
public class Order {

    @DataField(pos = 0)
    private int orderNr;

    @Link
    private Client client;

    @DataField(pos = 4)
    private String isinCode;

    @DataField(name = "Name", pos = 5)
    private String instrumentName;

    @DataField(pos = 6, precision = 2) -- precision
    private BigDecimal amount;

    @DataField(pos = 7)
    private String currency;

    @DataField(pos = 8, pattern = "dd-MM-yyyy")
    private Date orderDate;
...
}

Message

The Message annotation is used to identified the class of your model who will contain key value pairs fields. This kind of format is used mainly in Financial Exchange Protocol Messages (FIX). Nevertheless, this annotation can be used for any other format where data are identified by keys. The key pair values are separated each other by a separator which can be a special character like a tab delimitor (unicode representation : \u0009) or a start of heading (unicode representation : \u0001)

"FIX information"

More information about FIX can be found on this web site : http://www.fixprotocol.org/

Annotation name

Record type

Level

Message

key value pair

Class

Parameter name

type

Info

pair separator

string

mandatory - can be '=' or ';' or 'anything'

key value pair separair

string

mandatory - can be '
u0001', '
u0009', '#' or 'anything'

type

string

optional - define the type of message (e.g. FIX, EMX, ...)

version

string

optional - version of the message (e.g. 4.1)

 

 

This annotation is associated to the message class of the model and must be declared one time.

case 1 : separator = ','

The separator used to segregate the fields in the CSV record is ',' :

10, J, Pauline, M, XD12345678, Fortis Dynamic 15/15, 2500, USD,08-01-2009

Separator ,
@CsvRecord( separator = "," )
public Class Order {
...
}

Using the Java DSL

The next step consists in instantiaing the DataFormat bindy class associated with this record type and providing Java package name(s) as parameter.

For example the following uses the class CsvBindyFormat (who correspond to the class associated with the CSV record type) which is configured with "com.acme.model"
package name to initialize the model objects configured in this package.

DataFormat bindy = new CsvBindyDataFormat("com.acme.model");

from("file://inbox").
  unmarshal(bindy).
  to("bean:handleOrder");

The Camel route will pick-up files in the inbox directory, unmarshall CSV records in a collection of model objects and send the collection
to the bean referenced by 'handleOrder'.

The collection is a list of Map. Each Map of the list contains the objects of the model. Each object can be retrieve using its class name.

int count = 0;

    List<Map<String, Object>> models = new ArrayList<Map<String, Object>>();
    Map<String, Object> model = new HashMap<String, Object>();

    models = (List<Map<String, Object>>) exchange.getIn().getBody();

    Iterator<Map<String, Object>> it = models.iterator();

    while(it.hasNext()){

          model = it.next();

	  for(String key : model.keySet()) {
	     Object obj = model.get(key);
	     LOG.info("Count : " + count + ", " + obj.toString());
	  }

	 count++;
    }

    LOG.info("Nber of CSV records received by the csv bean : " + count);

To generate CSV records from a collection of model objects, you create the following route :

from("bean:handleOrder")
   marshal(bindy)
   to("file://outbox")

You can if you prefer use a named reference to a data format which can then be defined in your Registry such as via your Spring XML file. e.g.

from("file://inbox").
  unmarshal("myBindyDataFormat").
  to("bean:handleOrder");

Unit test

Here is two examples showing how to marshall or unmarshall a CSV file with Camel

Marshall
package org.apache.camel.dataformat.bindy.csv;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.dataformat.bindy.model.complex.twoclassesandonelink.Client;
import org.apache.camel.dataformat.bindy.model.complex.twoclassesandonelink.Order;
import org.apache.camel.spring.javaconfig.SingleRouteCamelConfiguration;
import org.junit.Test;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.test.JavaConfigContextLoader;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = "org.apache.camel.dataformat.bindy.csv.BindyComplexCsvMarshallTest$ContextConfig", loader = JavaConfigContextLoader.class)
public class BindyComplexCsvMarshallTest extends AbstractJUnit4SpringContextTests {

    private List<Map<String, Object>> models = new ArrayList<Map<String, Object>>();
    private String result = "10,A1,Julia,Roberts,BE123456789,Belgium Ventage 10/12,150,USD,14-01-2009";

    @Produce(uri = "direct:start")
    private ProducerTemplate template;

    @EndpointInject(uri = "mock:result")
    private MockEndpoint resultEndpoint;

    @Test
    public void testMarshallMessage() throws Exception {
        resultEndpoint.expectedBodiesReceived(result);

        template.sendBody(generateModel());

        resultEndpoint.assertIsSatisfied();
    }

    private List<Map<String, Object>> generateModel() {
        Map<String, Object> model = new HashMap<String, Object>();

        Order order = new Order();
        order.setOrderNr(10);
        order.setAmount(new BigDecimal("150"));
        order.setIsinCode("BE123456789");
        order.setInstrumentName("Belgium Ventage 10/12");
        order.setCurrency("USD");

        Calendar calendar = new GregorianCalendar();
        calendar.set(2009, 0, 14);
        order.setOrderDate(calendar.getTime());

        Client client = new Client();
        client.setClientNr("A1");
        client.setFirstName("Julia");
        client.setLastName("Roberts");

        order.setClient(client);

        model.put(order.getClass().getName(), order);
        model.put(client.getClass().getName(), client);

        models.add(0, model);

        return models;
    }

    @Configuration
    public static class ContextConfig extends SingleRouteCamelConfiguration {
        BindyCsvDataFormat camelDataFormat = new BindyCsvDataFormat("org.apache.camel.dataformat.bindy.model.complex.twoclassesandonelink");

        @Override
        @Bean
        public RouteBuilder route() {
            return new RouteBuilder() {
                @Override
                public void configure() {
                    from("direct:start").marshal(camelDataFormat).to("mock:result");
                }
            };
        }
    }

}
Unmarshall
package org.apache.camel.dataformat.bindy.csv;

import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.spring.javaconfig.SingleRouteCamelConfiguration;
import org.junit.Test;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.test.JavaConfigContextLoader;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = "org.apache.camel.dataformat.bindy.csv.BindyComplexCsvUnmarshallTest$ContextConfig", loader = JavaConfigContextLoader.class)
public class BindyComplexCsvUnmarshallTest extends AbstractJUnit4SpringContextTests {

    @EndpointInject(uri = "mock:result")
    private MockEndpoint resultEndpoint;

    @Test
    public void testUnMarshallMessage() throws Exception {
        resultEndpoint.expectedMessageCount(1);
        resultEndpoint.assertIsSatisfied();
    }

    @Configuration
    public static class ContextConfig extends SingleRouteCamelConfiguration {
        BindyCsvDataFormat camelDataFormat = new BindyCsvDataFormat("org.apache.camel.dataformat.bindy.model.complex.twoclassesandonelink");

        @Override
        @Bean
        public RouteBuilder route() {
            return new RouteBuilder() {
                @Override
                public void configure() {
                    from("file://src/test/data?noop=true").unmarshal(camelDataFormat).to("mock:result");
                }
            };
        }
    }

}

Using Spring XML

TODO:

Dependencies

To use Bindy in your camel routes you need to add the a dependency on camel-bindy which implements this data format.

If you use maven you could just add the following to your pom.xml, substituting the version number for the latest & greatest release (see the download page for the latest versions).

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-bindy</artifactId>
  <version>2.0.0</version>
</dependency>
  • No labels