Apache Camel with Spring Boot

Apache Camel with Spring Boot | Apache Camel is an open-source integration framework that transfers data between systems.

  • It supports multiple protocols like HTTP, FTP, FILE, JMS, etc.
  • It supports different languages Hadoop, PHP, Python, etc.
  • We can transfer data in different formats XML, JSON, Text, etc.
Apache Camel

Apache Camel supports 3 types of operations:-

  • Routing: Transfer data from source to destination.
  • Filtering: Check condition before transfer.
  • Processing: Modify/convert (like XML to JSON), calculations, etc.

Apache Camel coding is done using EIP (Enterprise Integration Pattern). Apache Camel can be used as replacement of the Spring Batch or it can be used as integration to the Spring Batch. For example, if we have log files in the production server and we want to copy them to the dev server then we can take the help of Apache Camel, and there is no Spring Batch processing required. ActiveMQ and Kafka support message queues only, but Apache Camel also supports cross-connectivity.

Apache Camel with Spring Boot Example

Create a Spring starter project and add the following dependencies:- Apache Camel.

<dependency>
    <groupId>org.apache.camel.springboot</groupId>
    <artifactId>camel-spring-boot-starter</artifactId>
    <version>4.6.0</version>
</dependency>

Spring Boot supports auto-configuration with Apache camel using camel-spring-boot-starter starter.

org.apache.camel.builder.RouteBuilder is an abstract class and contains an abstract method configure() method. We have to create a class extending org.apache.camel.builder.RouteBuilder and override configure() method.

Requirement:- Assume we have a data.txt file in a source folder (V:\source), and we want to transfer it to another destination folder (V:\destination). To do this we have to use File protocol and EPI (Enterprise Integration Pattern) pattern.

In data.txt:-

Know Program

Router class:-

package com.knowprogram.demo.router;

import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class MyDataRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("file:V:\\source").to("file:V:\\destination");
    }
}

In application.properties file:-
camel.springboot.main-run-controller=true

The camel.springboot.main-run-controller=true property specifies when the main method started then Apache Camel should also start. When the main method is stopped then Apache Camel should also stop.

On execution following things happen:-

  • Create a folder “source” in V drive.
  • Create a folder “destination” in F drive.
  • Copy files from the source folder to the destination folder.
  • Place the original data.txt file inside a hidden folder “.camel” in the source folder as a backup.

The application won’t stop itself. While running the application, if we place any file in the source folder then immediately it will be copied to the destination folder and the file inside the source folder will be placed inside the “.camel” folder as a backup. The process will continue till we stop the application.

Generally, it is used to transfer log files from production to dev server. If there is a change in the source folder log file then Apache camel will copy it to the destination folder by overriding the existing log file. It won’t create another file, rather it will override.

  • In the configure() method:- from("file:V:\\source").to("file:V:\\destination") indicates sending files from V:/source to V:/destination folder.
  • All files are taken as backups in the source as the .camel folder.
  • We can send the same file again with new data, that overrides at the destination.
from("file:V:\\source?noop=true").to("file:V:\\destination")
  • Here NOOP represents No Operation to Override.
  • No backup into “.camel” in the source folder.
  • It avoids sending duplicates.

If we want to change source/destination or NOOP dynamically then instead of hardcoding the values in the class we can read them from the application.properties file as follows:-

camel.springboot.main-run-controller=true
my.app.source=file:V:/source?noop=true
my.app.destination=file:V:/destination

We can either use @Value to read the data from the properties file or we can use them directly through {{}}.

Router class:-

package com.knowprogram.demo.router;

import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class MyDataRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("{{my.app.source}}").to("{{my.app.destination}}");    
    }
}

Here {{my.app.source}} indicates the dynamic location is taken from the properties file.

Processing Data Using Apache Camel

If we just want to transfer the files then the router is sufficient but if want any data conversion or calculations then processing is also required along with the router.

Processor provides:-

  • Data Conversions (like Text to JSON, XML to JSON, LOG to HTML, etc.)
  • Data Calculations

The org.apache.camel.Processor interface provides an abstract method:- void process(Exchange exchange) throws Exception. This interface indicates data modification/operations are going to be applied to source data.

Code style

// Anonymous inner class
new InterfaceName() {
   // override abstract method
}
// lambda expression
Interface ob = (methodParam) -> { methodBody };

The org.apache.camel.Exchange is also an interface having methods:- getIn(), getOut(), getMessage() and e.t.c. It supports reading source Messages using the getIn() method and supports writing destination messages using the method getOut()/getMessage().

The org.apache.camel.Message is an interface having methods:- getHeader(), getBody(), setBody() and e.t.c. It indicates the data source/destination (file). The message contains mainly two parts:-

  • Header: File information (filename, file Extension, etc.)
  • Body: Actual data inside the file.
Processing Data Using Apache Camel

Requirement

In source.txt:-
10,A,3.3

We want the destination to have, emp.json:-
{eid:10, ename:A, esal:3.3}

In application.properties file:-

camel.springboot.main-run-controller=true
my.app.source=file:V:/source?noop=true
my.app.destination=file:V:/destination?fileName=emp.json

Router:-

package com.knowprogram.demo.router;

import java.util.StringTokenizer;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class MyDataRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("{{my.app.source}}").process(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                // read input message given by source
                Message input = exchange.getIn();

                // read body as String from input message
                String data = input.getBody(String.class);

                // operation
                StringTokenizer str = new StringTokenizer(data, ",");
                String eid = str.nextToken();
                String ename = str.nextToken();
                String esal = str.nextToken();

                // output data
                String modifiedData = "{eid: " + eid + ", ename: " 
                            + ename + ", esal: " + esal + "}";

                // read output message Reference
                // Message output = exchange.getOut(); // deprecated
                Message output = exchange.getMessage();

                // set data to Message
                output.setBody(modifiedData);
            }
        }).to("{{my.app.destination}}");
    }
}

Instead of using an anonymous inner class, we can use lambda expression as follows:-

package com.knowprogram.demo.router;

import java.util.StringTokenizer;

import org.apache.camel.Message;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class MyDataRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("{{my.app.source}}").process((exchange) -> {
            // read input message given by source
            Message input = exchange.getIn();

            // read body as String from input message
            String data = input.getBody(String.class);

            // operation
            StringTokenizer str = new StringTokenizer(data, ",");
            String eid = str.nextToken();
            String ename = str.nextToken();
            String esal = str.nextToken();

            // output data
            String modifiedData = "{eid: " + eid + ", ename: " + 
                            ename + ", esal: " + esal + "}";

            // read output message Reference
            // Message output = exchange.getOut(); // deprecated
            Message output = exchange.getMessage();

            // set data to Message
            output.setBody(modifiedData);
        }).to("{{my.app.destination}}");
    }
}

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *