Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Note
titleMultiple models

If you use multiple models, each model has to be placed in it's own package to prevent unpredictable results.

From Camel 2.16 onwards this is no longer the case, as you can safely have multiple models in the same package, as you configure bindy using class names instead of package names now.

Annotations

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

...

Parameter name

type

Info

pos

int

mandatory - The input position of the field. digit number starting from 1 to ... - See the position parameter.

pattern

string

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

length

int

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

precision

int

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

pattern

string

optional - default value = "" - is used by the Java formatter (SimpleDateFormat by example) to format/validate data. If using pattern, then setting locale on bindy data format is recommended. Either set to a known locale such as "us" or use "default" to use platform default locale. Notice that "default" requires Camel 2.14/2.13.3/2.12.5.

position

int

optional - must be used when the position of the field in the CSV generated (output message) must be different compare to input position (pos). See the pos parameter.

required

boolean

optional - default value = "false"

trim

boolean

optional - default value = "false"

defaultValue

string

Camel 2.10: optional - default value = "" - defines the field's default value when the respective CSV field is empty/not available

impliedDecimalSeparator

boolean

Camel 2.11: optional - default value = "false" - Indicates if there is a decimal point implied at a specified location

lengthPos

int

Camel 2.11: optional - can be used to identify a data field in a fixed-length record that defines the fixed length for this field

delimiter

string

Camel 2.11: optional - can be used to demarcate the end of a variable-length field within a fixed-length record

...

For example the following uses the class BindyCsvDataFormat (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.

Code Block
DataFormat bindy = new BindyCsvDataFormat("com.acme.model");

Setting locale

Bindy supports configuring the locale on the dataformat, such as 

Code Block
BindyCsvDataFormat// Camel 2.15 or older (configure by package name)
DataFormat bindy = new BindyCsvDataFormat("com.acme.model");

bindy.setLocale("us");

Or to use the platform default locale then use "default" as the locale name. Notice this requires Camel 2.14/2.13.3/2.12.5.

 
// Camel 2.16 onwards (configure by class name)
DataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);

Setting locale

Bindy supports configuring the locale on the dataformat, such as 

Code Block
// Camel 2.15 or older (configure by package name)
Code Block
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");

bindy.setLocale("default");

for older releases you can set it using Java code as shown

Code Block
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model".MyModel.class);

bindy.setLocale(Locale.getDefault().getISO3Country());

Unmarshaling

Code Block
from("file://inbox")
  .unmarshal(bindy)
  .to("direct:handleOrders");

Alternatively, you can use a named reference to a data format which can then be defined in your Registry e.g. your Spring XML file:

Code Block
from("file://inbox")
  .unmarshal("myBindyDataFormat")
  .to("direct:handleOrders");

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

The collection returned is a List of Map objects. Each Map within the list contains the model objects that were marshalled out of each line of the CSV. The reason behind this is that each line can correspond to more than one object. This can be confusing when you simply expect one object to be returned per line.

Each object can be retrieve using its class name.

Code Block
    List<Map<String, Object>> unmarshaledModels = (List<Map<String, Object>>) exchange.getIn().getBody();

    int modelCount = 0;
    for (Map<String, Object> model : unmarshaledModels) {
	  for (String className : model.keySet()) {
	     Object obj = model.get(className);
	     LOG.info("Count : " + modelCount + ", " + obj.toString());
	  }
	 modelCount++;
    }

    LOG.info("Total CSV records received by the csv bean : " + modelCount);

Assuming that you want to extract a single Order object from this map for processing in a route, you could use a combination of a Splitter and a Processor as per the following:

Code Block
from("file://inbox")
    .unmarshal(bindy)
    .split(body())
        .process(new Processor() {
            public void process(Exchange exchange) throws Exception {
                Message in = exchange.getIn();
                Map<String, Object> modelMap = (Map<String, Object>) in.getBody();
                in.setBody(modelMap.get(Order.class.getCanonicalName()));
            }
        })
        .to("direct:handleSingleOrder")
    .end();

Marshaling

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

Code Block
from("direct:handleOrders")
   .marshal(bindy)
   .to("file://outbox")

Unit test

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

Code Block
titleMarshall
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");
                }
            };
        }
    }

}
Code Block
titleUnmarshall
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 csvBindyDataFormat = 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(csvBindyDataFormat).to("mock:result");
                }
            };
        }
    }

}

In this example, BindyCsvDataFormat class has been instantiated in a traditional way but it is also possible to provide information directly to the function (un)marshal like this where BindyType corresponds to the Bindy DataFormat class to instantiate and the parameter contains the list of package names.

"us");

Or to use the platform default locale then use "default" as the locale name. Notice this requires Camel 2.14/2.13.3/2.12.5.

Code Block
// Camel 2.15 or older (configure by package name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);

bindy.setLocale("default");

for older releases you can set it using Java code as shown

Code Block
// Camel 2.15 or older (configure by package name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);


bindy.setLocale(Locale.getDefault().getISO3Country());

Unmarshaling

Code Block
from("file://inbox")
  .unmarshal(bindy)
  .to("direct:handleOrders");

Alternatively, you can use a named reference to a data format which can then be defined in your Registry e.g. your Spring XML file:

Code Block
from("file://inbox")
  .unmarshal("myBindyDataFormat")
  .to("direct:handleOrders");

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

The collection returned is a List of Map objects. Each Map within the list contains the model objects that were marshalled out of each line of the CSV. The reason behind this is that each line can correspond to more than one object. This can be confusing when you simply expect one object to be returned per line.

Each object can be retrieve using its class name.

Code Block
    List<Map<String, Object>> unmarshaledModels = (List<Map<String, Object>>) exchange.getIn().getBody();

    int modelCount = 0;
    for (Map<String, Object> model : unmarshaledModels) {
	  for (String className : model.keySet()) {
	     Object obj = model.get(className);
	     LOG.info("Count : " + modelCount + ", " + obj.toString());
	  }
	 modelCount++;
    }

    LOG.info("Total CSV records received by the csv bean : " + modelCount);

Assuming that you want to extract a single Order object from this map for processing in a route, you could use a combination of a Splitter and a Processor as per the following:

Code Block
from("file://inbox")
    .unmarshal(bindy)
    .split(body())
        .process(new Processor() {
            public void process(Exchange exchange) throws Exception {
                Message in = exchange.getIn();
                Map<String, Object> modelMap = (Map<String, Object>) in.getBody();
                in.setBody(modelMap.get(Order.class.getCanonicalName()));
            }
        })
        .to("direct:handleSingleOrder")
    .end();

Take care of the fact that Bindy uses CHARSET_NAME property or the CHARSET_NAME header as define in the Exchange interface to do a characterset conversion of the inputstream received for unmarshalling. In some producers (e.g. file-endpoint) you can define a characterset. The characterset conversion can already been done by this producer. Sometimes you need to remove this property or header from the exchange before sending it to the unmarshal. If you don't remove it the conversion might be done twice which might lead to unwanted results.

Code Block
from("file://inbox?charset=Cp922")
  .removeProperty(Exchange.CHARSET_NAME)
  .unmarshal("myBindyDataFormat")
  .to("direct:handleOrders");

Marshaling

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

Code Block
from("direct:handleOrders")
   .marshal(bindy)
   .to("file://outbox")
Code Block
    public static class ContextConfig extends SingleRouteCamelConfiguration {
        @Override
        @Bean
        public RouteBuilder route() {
            return new RouteBuilder() {
                @Override
                public void configure() {
                    from("direct:start")
                    .marshal().bindy(BindyType.Csv, "org.apache.camel.dataformat.bindy.model.simple.oneclass")
                    .to("mock:result");
                }
            };
        }
    }

Using Spring XML

This is really easy to use Spring as your favorite DSL language to declare the routes to be used for camel-bindy. The following example shows two routes where the first will pick-up records from files, unmarshal the content and bind it to their model. The result is then send to a pojo (doing nothing special) and place them into a queue.

The second route will extract the pojos from the queue and marshal the content to generate a file containing the csv record. The example above is for using Camel 2.16 onwards.

Code Block
titlespring dsl
<?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">

	<bean id="bindyDataformat" class="org.apache.camel.dataformat.bindy.csv.BindyCsvDataFormat">
		<constructor-arg value="org.apache.camel.bindy.model" />
	</bean>

	<bean id="csv" class="org.apache.camel.bindy.csv.HandleOrderBean" />
http://camel.apache.org/schema/spring
       http://camel.apache.org/schema/spring/camel-spring.xsd">

        <!-- Queuing engine - ActiveMq - work locally in mode virtual memory -->
	<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
		<property name="brokerURL" value="vm://localhost:61616"/>
	</bean>


	<camelContext xmlns="http://camel.apache.org/schema/spring">
		<jmxAgent

 
        <dataFormats>
          <bindy id="agentbindyDataformat" disabledtype="falseCsv" classType="org.apache.camel.bindy.model.Order"/>
        </dataFormats>

		<route>
			<from uri="file://src/data/csv/?noop=true" />
			<unmarshal ref="bindyDataformat" />
			<to uri="bean:csv" />
			<to uri="activemq:queue:in" />
		</route>

		<route>
			<from uri="activemq:queue:in" />
			<marshal ref="bindyDataformat" />
			<to uri="file://src/data/csv/out/" />
		</route>
	</camelContext>
</beans>

...