In this tutorial, we will explore how to use Apache Camel in combination with Bindy to produce and consume CSV files. Camel is an open-source integration framework that provides a powerful routing and mediation engine. Bindy is a Camel component that allows for the transformation of Java objects to/from various data formats, including CSV.
Prerequisites
To follow along with this tutorial, you will need:
- Java Development Kit (JDK) installed on your machine
- Apache Maven installed on your machine
- Basic knowledge of Java and Apache Camel
Step 1: Set Up your project dependencies
Include in your pom.xml the following dependencies:
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-bindy</artifactId> </dependency>
Step 2: Create CSV Data Model
- Create a new package
com.sample.csv
in thesrc/main/java
directory. - Inside the package, create a new Java class
User
. - Define properties and corresponding getter and setter methods for the
class. For example:User
package com.sample.csv; import java.io.Serializable; import org.apache.camel.dataformat.bindy.annotation.CsvRecord; import org.apache.camel.dataformat.bindy.annotation.DataField; @CsvRecord(separator = ",", generateHeaderColumns = true) public class User implements Serializable { @Override public String toString() { return "User [name=" + name + ", surname=" + surname + ", id=" + id + "]"; } @DataField(pos = 1) private String name; @DataField(pos = 2) private String surname; private String id; // Getter/Setters omitted for brevity }
Here are some details about the CSV annotations:
@CsvRecord
: This annotation marks the class as a CSV record and allows customization of CSV-specific properties. In this case, separator = ","
specifies that the CSV fields are separated by commas, and generateHeaderColumns = true
indicates that the header line should be generated in the CSV file.
@DataField
: This annotation marks the fields that need to be mapped to CSV columns. The pos
attribute specifies the position of the field in the CSV record. In this case, name
corresponds to the first field (position 1), and surname
corresponds to the second field (position 2).
In the next section we will learn how to convert a CSV file to Java and viceversa using the above model.
From CSV to Java with Camel
In the first example, we will consume a CSV file and unmarshal it using BindyCsvDataFormat. To do that, we need to create an instance of it specifying the Model to use:
final DataFormat bindy = new BindyCsvDataFormat(User.class);
Next, let’s code a simple Route:
from("file:src/main/resources/data?noop=true&include=.*\\.csv") .split(body().tokenize("\n")) .log("Body: ${body}") .unmarshal(bindy).process(new SimpleProcessor()).log("Body: ${body}");
from("file:src/main/resources/data?noop=true&include=.*\\.csv")
: This line sets up the route’s starting point. It consumes files from the directory src/main/resources/data
with the file extension .csv
. The noop=true
option prevents the files from being deleted after processing.
.split(body().tokenize("\n"))
: This line splits the content of the file into individual lines. It uses the split
EIP (Enterprise Integration Pattern) to iterate over each line.
.log("Body: ${body}")
: This line logs the content of each line to the console. The ${body}
placeholder represents the current message body.
.unmarshal(bindy)
: This line performs unmarshalling, converting the CSV data into Java objects using the Bindy library. The bindy
component is responsible for data format conversion.
.process(new SimpleProcessor())
: This line invokes a custom processor, SimpleProcessor
, to perform additional processing on the unmarshalled data. The processor can implement custom logic for manipulating or transforming the data.
The Processor performs a simple Task: it adds a random UID to the User Class:
public class SimpleProcessor implements Processor { public void process(Exchange exchange) throws Exception { User user = (User) exchange.getIn().getBody(); user.setId(UUID.randomUUID().toString()); exchange.getIn().setBody(user); } }
To learn more about Processors, check this article: Using a Camel Processor to modify the message
Finally, add a sample CSV file in the resources/data folder of your application:
John,Doe Jane,Smith Michael,Johnson Emily,Williams
Here is the project view:
Running the example
By running our route, you will see that the CSV entries are unmarshalled and the Processor does its job to add an userid:
[file://src/main/resources/data] route1 INFO Body: User [name=surname, surname=name, id=4950fcde-1635-40b4-bbd6-c0797ff44c20] [file://src/main/resources/data] route1 INFO Body: John,Doe [file://src/main/resources/data] route1 INFO Body: User [name=Doe, surname=John, id=c7e3985a-a71f-4524-be5a-d6b12be10f63] [file://src/main/resources/data] route1 INFO Body: Jane,Smith [file://src/main/resources/data] route1 INFO Body: User [name=Smith, surname=Jane, id=51c4cea1-99ff-4a43-86e5-5f3932cbb48d] [file://src/main/resources/data] route1 INFO Body: Michael,Johnson [file://src/main/resources/data] route1 INFO Body: User [name=Johnson, surname=Michael, id=6cc698a9-1dfd-469e-a63f-3933e6850fe3] [file://src/main/resources/data] route1 INFO Body: Emily,Williams [file://src/main/resources/data] route1 INFO Body: User [name=Williams, surname=Emily, id=05069b1b-91c5-453d-a0cf-8b61568b1a16]
From Java to CSV with Camel
In the send part of this article, we will do the reverse step: from a Java User class, we will produce an entry in a CSV file. Since we will not use a Processor in this step, the Camel Route is pretty lean:
from("direct:start") .marshal(bindy).log("Body: ${body}");
To trigger the direct:start component, we will use a ProducerTemplate and add an User object in it:
ProducerTemplate template = context.createProducerTemplate(); User user = new User(); user.setName("John"); user.setSurname("Smith"); template.sendBody("direct:start", user);
Since our CSVRecord contains generateHeaderColumns = true
, the CSV file that we will marshall will contain the Headers for the User Class:
INFO Body: surname,name Smith,John
Advanced Transformation using @BindyConverter
Sometimes you need to perform transformation of data when transforming from Java to CSV and viceversa. You can use the @BindyConverter for this purpose and specify in it a Class that will act as a Converter. The provided class must implement the Format interface.
For example:
@DataField(pos = 1) @BindyConverter(CustomConverter.class) public String name;
Here is a sample Converter class that simply reverses the field value:
public static class CustomConverter implements Format<String> { @Override public String format(String object) throws Exception { return (new StringBuilder(object)).reverse().toString(); } @Override public String parse(String string) throws Exception { return (new StringBuilder(string)).reverse().toString(); } }
Conclusion
In this tutorial, we explored how to leverage the power of Apache Camel and the Bindy component to handle CSV files effectively. Apache Camel provides a flexible integration framework, while Bindy simplifies the transformation of Java objects to and from CSV format. By combining these technologies, you can streamline the process of producing and consuming CSV files in your applications.
Source code for this article: https://github.com/fmarchioni/masterspringboot/tree/master/camel/csv
Found the article helpful? if so please follow us on Socials