Build Restful Web Services using Apache Camel

bank-of-america
Reading Time: 4 minutes

Introduction of Restful webservices using Apache Camel

In this blog, we will expose Restful Webservices using Apache Camel. As you know, the Camel framework is based on Spring. If you’re utilizing Spring data to consume data from a Postgres database, the only business logic your team is responsible for writing a test harness for is in your implementation of the Spring framework. Your development team isn’t writing unit tests to validate that Spring Data returns rows and columns as it should from the JDBC datasource, or any of the underlying technology that that framework is built. JNDI, the JVM, the Linux Kernel.

Camel is a framework for merging between different systems. It usually doesn’t do something by itself, but instead creates and configures something (e.g. a component) that will actually do the work.

One of the biggest Camel benefits is that the testing of the Camel framework is outsourced to the community.

Apache Camel also follows the same concept for creating REST services. You only have to write some instructions for Camel, and it will use a bunch of other components to expose your REST service for you. You only have to pick exactly how you want the service to be implemented, and Camel will take care of all the configuration.

Let’s Begin with Project Structure of Restful webservices using Apache Camel

The pom.xml would be as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.7</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.jss</groupId>
	<artifactId>camel</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>camel</name>
	<description>Apache Camel with rest services</description>

	<properties>
		<java.version>1.8</java.version>
		<junit.platform.version>1.6.0</junit.platform.version>
		<camel.version>3.7.3</camel.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.apache.camel.springboot</groupId>
			<artifactId>camel-spring-boot-starter</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-test</artifactId>
			<scope>test</scope>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-rabbitmq</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-jackson</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-jaxb</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
			<version>1.18.12</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-servlet</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel.springboot</groupId>
			<artifactId>camel-servlet-starter</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.camel.springboot</groupId>
			<artifactId>camel-rest-starter</artifactId>
			<version>${camel.version}</version>
		</dependency>

		<dependency>
			<groupId>org.junit.platform</groupId>
			<artifactId>junit-platform-runner</artifactId>
			<version>${junit.platform.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
			<version>5.7.0</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-test-junit5</artifactId>
			<version>${camel.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-test-spring-junit5</artifactId>
			<version>${camel.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Create RestJavaDsl.java, this class will expose GET service to fetch pin Code details.

package com.spring.components.rest;

import com.spring.components.dto.PinCodeDto;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.apache.camel.support.DefaultMessage;
import org.springframework.stereotype.Component;

import java.util.Objects;

import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.springframework.http.HttpStatus.NOT_FOUND;

@Component
public class RestJavaDsl extends RouteBuilder {
    private final PinDataProvider pinDataProvider;

    public RestJavaDsl(PinDataProvider pinDataProvider) {
        this.pinDataProvider = pinDataProvider;
    }

    @Override
    public void configure() throws Exception {
        restConfiguration().component("servlet").bindingMode(RestBindingMode.auto);


        from("rest:get:javadsl/pin/{pinCode}?produces=application/json").outputType(PinCodeDto.class).process(this::getPinCodeData);
    }

    private void getPinCodeData(Exchange exchange) {
        String city = exchange.getMessage().getHeader("pinCode", String.class);
        PinCodeDto currentPinCode = pinDataProvider.getUserPin(city);

        if (Objects.nonNull(currentPinCode)) {
            Message message = new DefaultMessage(exchange.getContext());
            message.setBody(currentPinCode);
            exchange.setMessage(message);
        } else {
            exchange.getMessage().setHeader(HTTP_RESPONSE_CODE, NOT_FOUND.value());
        }
    }
}

Implement PinDataProvider.java, this class will help you to set and get pin Code details.

package com.spring.components.rest;

import com.spring.components.dto.PinCodeDto;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class PinDataProvider {
    private static Map<String, PinCodeDto> pinCodeData = new HashMap<>();

    public PinDataProvider() {
        PinCodeDto dto = PinCodeDto.builder().pinCode("244717").state("Uttarakhand").city("Ramnagar").receivedTime(new Date().toString()).id(1).build();
        pinCodeData.put("244717", dto);
    }

    public PinCodeDto getUserPin(String city) {
        return pinCodeData.get(city);
    }

    public void setUserPin(PinCodeDto dto) {
        dto.setReceivedTime(new Date().toString());
        pinCodeData.put(dto.getCity(), dto);
    }
}

Create PinCodeDto.java, this class consists of all the parameters that will be displayed as JSON in response.

package com.spring.components.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PinCodeDto implements Serializable {
    static int counter = 1;
    private int id =counter++;
    private String city;
    private String state;
    private String pinCode;
    private String receivedTime;
}

Now, you can create RestDsl.java in order to POST the data to the server.

package com.spring.components.rest;

import com.spring.components.dto.PinCodeDto;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.apache.camel.support.DefaultMessage;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.util.Objects;

import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.springframework.http.HttpStatus.NOT_FOUND;

@Component
public class RestDsl extends RouteBuilder {
    private final PinDataProvider pinDataProvider;

    public RestDsl(PinDataProvider pinDataProvider) {
        this.pinDataProvider = pinDataProvider;
    }

    @Override
    public void configure() throws Exception {
        restConfiguration().component("servlet").bindingMode(RestBindingMode.auto);
        rest().consumes(MediaType.APPLICATION_JSON_VALUE).produces(MediaType.APPLICATION_JSON_VALUE).get("pin/{pinCode}").outType(PinCodeDto.class).to("direct:get-pin-data").post("/pin").type(PinCodeDto.class).to("direct:save-pin-data");

        from("direct:get-pin-data").process(this::getPinCodeData);
        from("direct:save-pin-data").process(this::savePinDataAndSetToExchange);

    }

    private void savePinDataAndSetToExchange(Exchange exchange) {
        PinCodeDto pinCodeDto = exchange.getMessage().getBody(PinCodeDto.class);
        pinDataProvider.setUserPin(pinCodeDto);

    }

    private void getPinCodeData(Exchange exchange) {
        String userPin = exchange.getMessage().getHeader("pinCode", String.class);
        PinCodeDto pinCode = pinDataProvider.getUserPin(userPin);

        if (Objects.nonNull(pinCode)) {
            Message message = new DefaultMessage(exchange.getContext());
            message.setBody(pinCode);
            exchange.setMessage(message);
        } else {
            exchange.getMessage().setHeader(HTTP_RESPONSE_CODE, NOT_FOUND.value());
        }
    }
}

Configure in application.yml.

camel:
     component:
              servlet:
                      mapping:
                              context-path: /services/*

Testing the Rest Services using Postman

You can test you rest services with url given below:

For GET rest service: http://localhost:8080/services/pin/244717

For POST rest service: http://localhost:8080/services/pin

Conclusion

In this blog, we have learned how to write REST services(GET and POST) using Apache Camel. The Camel REST allows the creation of REST services using Restlet, Servlet, and many such HTTP-aware components for implementation. You can utilize the Camel REST component to expose REST API and to consume/produce messages using known Camel DSL, which helps you to standardize the technical stake. Continue learning about Apache Camel by clicking here.