Bridge JAX-RS and Camel CDI
Camel is a wonderful toolkit to build proxies or advanced integration chains. On another side JAX-RS is a great way to express an HTTP API (or REST API with some more work). Both can be integrated in various ways like:
- using a camel HTTP component (rest, servlet, ...)
- sending JAX-RS inputs to camel
In this post we'll see how to do the last option, ie keep our rest layer standard and just wire the invocation to a camel route.
What's the goal? Benefit from the very strong power of camel without having to do a new setup for our HTTP layer. Said otherwise: keep our development classical and still inherit from all the calel component and chaining.
Define a JAX-RS enpoint
Our goal is to reuse all common JAX-RS backbone so our endpoint would look like:
@ApplicationScoped
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public static class GreeterEndpoint {
@GET
@Path("hi")
public String sayHi(@QueryParam("name") String name) {
return /*some camel magic */;
}
}
Define Camel CDI chain
What should our camel chain (our route) do? Take the input passed from the rest endpoint (the name there) and convert it to the greeting message: "hi ${name}".
Here is a potential solution:
@ApplicationScoped
public static class GreeterRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:chain")
.routeId("greeter")
.setBody().simple("Hi ${body}");
}
}
So we read a request from a direct queue (seda would have worked too) and we set the output (body) to "Hi ${body}" which will use the current body (so our rest input) as placeholder. Since we'll pass the name as body of the route it will output what we are expecting.
Side note: naming the route (routeId) is a good practise, in particular if you monitor your routes - otherwise no real way to know who is who quickly.
Bridge Camel and JAX-RS layers
To bridge both layers we use one of the most powerful camel component for external framework integration, the org.apache.camel.ProducerTemplate. Since we are using camel-cdi we can just inject it in our rest CDI bean and then it enables us to access the requestBody method which sends a payload as body and can return the output of the route as returned value:
@ApplicationScoped
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public class GreeterEndpoint {
@Inject
@EndpointInject(uri = "direct:chain")
private ProducerTemplate camel;
@GET
@Path("hi")
public String sayHi(@QueryParam("name") String name) {
return camel.requestBody((Object) name, String.class);
}
}
- then injection relies on camel @EndpointInject to reference which endpoint will be used in the underlying camel context (the cdi one there). It is not mandatory and you can actually pass the value as parameter of another requestBody method (with 3 parameters).
- we need to cast the name as an object to choose the method we want cause there is another signature conflicting using a String which expects the endpoint uri as first parameter. Don't worry, in real life you would pass an object anyway ;)
- Finally we request the body output of the route as a String (second parameter). It either matches the actual output or you can rely on camel converters to build this instance which is pretty elegant.
Test the sample
If you want to test this sample just add camel-cdi and a jaxrs runtime and copy the provided classes. If you want to use Apache Meecrowawe here what it can look like:
<!-- maven dependencies -->
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cdi</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-core</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>
package com.github.rmannibucau.camel.demo;
import org.apache.camel.EndpointInject;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.meecrowave.Meecrowave;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
public class Runner {
public static void main(final String[] args) {
new Meecrowave().bake().await();
}
@ApplicationScoped
public static class Builder extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:chain")
.routeId("greeter")
.setBody().simple("Hi ${body}");
}
}
@ApplicationScoped
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public static class Api {
@Inject
@EndpointInject(uri = "direct:chain")
private ProducerTemplate camel;
@GET
@Path("hi")
public String sayHi(@QueryParam("name") String name) {
return camel.requestBody((Object) name, String.class);
}
}
}
Then just hit: http://localhost:8080/greet/hi?name=test
Conclusion
Thanks to all the utility classes around the camel context (ProducerTemplate, java proxy etc...) it is very easy to integrate camel with any library. When it comes to HTTP layer it can be easier to integrate camel with your own endpoints if you have some HTTP work to do (around headers or validation for instance) than reusing camel rest integration and configuring the whole stack to be align on the one you already have in your container (like bean validation + json in a EE server for example).
From the same author:
In the same category: