Camel CDI in a TomEE Fat Jar
Camel in an insanely powerful integration library and camel-cdi module brings CDI integration to this world making it easy to write any integration pipeline with all the tooling you already use for your other applications.
Packaging it as a war can be problematic for this layer cause you can desire to give control as an application configuration over the connector(s) (http pool/connection) cause it is part of the integration layer if camel is used to build a HTTP proxy for instance. In such a case providing yourself a main() can make sense to expose a consistent and unique configuration (and not split the configuration between your application and the container).
To make it possible to use camel+cdi+http a nice way is to rely on TomEE. Let see how!
Build a HTTP route with camel
Camel can be used as a HTTP proxy. There are several implementation of the HTTP transport but let's use the Servlet one which is the most portable and common one. For that you just define a rest() endpoint in a RouteBuilder:
import org.apache.camel.builder.RouteBuilder;
public class DemoRouteBuilder extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().component("servlet");
rest("/demo")
.get().route()
// .... a normal route
.endRest();
}
}
Important points are:
- you require camel to use servlet transport using restConfiguration() DSL
- you can bind an endpoint (/demo for us) to a route which would do anything based on the incoming request (manipulating payload, headers etc...)
If you run this simple route builder it will miss the servlet component by default, to activate it you need a web.xml and since I'm not that a fan of web.xml let's write a ServletContainerInitializer which will allow to get rid of the XML and even support some configuration since that's pure java:
import org.apache.camel.component.servlet.CamelHttpTransportServlet;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import java.util.Set;
public class ServletRegistrar implements ServletContainerInitializer {
@Override
public void onStartup(final Set<Class<?>> c, final ServletContext ctx) throws ServletException {
final ServletRegistration.Dynamic camelServlet = ctx.addServlet("CamelServlet", CamelHttpTransportServlet.class);
camelServlet.setLoadOnStartup(1);
camelServlet.setAsyncSupported(true);
camelServlet.setInitParameter("async", "true");
camelServlet.addMapping("/*");
}
}
To let this initializer work you need to add the qualified class name of this ServletRegistrar in META-INF/services/javax.servlet.ServletContainerInitializer
Last note: to have it working you need the module:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-servlet</artifactId>
<version>${camel.version}</version>
</dependency>
Activate the route automatically with CDI
Now we have almost everything and to activate our route by simple declaration (scanning) we'll add the camel-cdi module:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cdi</artifactId>
<version>${camel.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
Then we just need to decorate our route with @ContextName:
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.cdi.ContextName;
@ApplicationScoped
@ContextName("demo")
public class DemoRouteBuilder extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().component("servlet");
rest("/demo")
.get().route()
// ...
.endRest();
}
}
But we can go further and reuse CDI components (@Inject!) directly in the route:
import com.github.rmannibucau.camel.cdi.processor.ReverseProcessor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.cdi.ContextName;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@ApplicationScoped
@ContextName("demo")
public class DemoRouteBuilder extends RouteBuilder {
@Inject
private ReverseProcessor reverseProcessor;
@Override
public void configure() throws Exception {
restConfiguration().component("servlet");
rest("/demo")
.get().route()
.transform().constant("em desrever lemac")
.process(reverseProcessor)
.endRest();
}
}
Here you can see that we rely on a processor which is a CDI bean! This opens a lot of door and integration with your business logic and auditing capabilities.
Package it as a Fat Jar
Then to package it as a fat jar we just need to use maven-shade-plugin (or shadowjar for gradle) and define the right transformers. Most of them will be related to TomEE (you can see it in previous posts) but camel will also need to merge TypeConverter resources:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<dependencyReducedPomLocation>${project.build.directory}/reduced-pom.xml</dependencyReducedPomLocation>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.apache.tomee.embedded.FatApp</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/cxf/bus-extensions.txt</resource>
</transformer>
<transformer implementation="org.apache.openwebbeans.maven.shade.OpenWebBeansPropertiesTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/services/org/apache/camel/TypeConverter</resource>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/faces-config.xml</exclude>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-maven</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
</plugin>
With this main (FatApp is coming with TomEE 7.0.2) you can run your application with:
java -jar target/myapp.jar
Then to test your application just go on
http://localhost:8080/demo
And you should see the output payload you coded in the rest route.
Reusing FatApp main you can customize TomEE with all TomEE properties but the nice thing with a main like that is you can wrap it (or tomee embedded Main directly if you prefer) to use your own configuration format which will enable you for instance to run your application with:
java -jar myapp.jar --configuration=my-camel-proxy.yml
and this my-camel-proxy.yml would get all the configuration of camel, the business logic, the integration logic and the container configuration as well which would make it easier to configure and tune for your deployers since you can center it on the route needs instead of letting it be "up to" the operation team.
TIP: of course it also means using tomee-embedded-maven-plugin or tomee-embedded gradle plugin can enable you to test easily in development your camel-cdi routes ;).
From the same author:
In the same category: