I was tasked with creating a skeleton Java application that ran Camel for a project at work. As documentation was a bit sparse on how to achieve what I wanted, I’ve written this post on how to bootstrap a very simple Camel application with Guice using the HTTP ping endpoint as an example route, providing an example of dependency injection and Camel component configuration. The application builds to a runnable jar.

The full source code is available at https://github.com/andystanton/camel-guice-example

Build configuration

My build tool is maven so the first step is to create the pom.xml. In the dependencies section we need to include camel-core as well as camel-jetty for the embedded Jetty server and camel-guice to handle the dependency injection and Camel configuration.

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>${camel.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jetty9</artifactId>
    <version>${camel.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-guice</artifactId>
    <version>${camel.version}</version>
</dependency>

We tell maven to build a standalone jar by adding the maven Shade plugin to the plugins section:.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>${maven-shade-plugin.version}</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>${maven-shade-plugin.mainClass}</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

We should also filter out files of type *.SF, *.DSA and *.RSA in the META-INF path because the Shade plugin bundles all the content from jars in this jar, including the signatures from signed jars. These signatures are validated when we run the jar but are now invalid because our jar is not the one for which they were generated causing an error. The filters section is added to the plugin’s configuration section:

<filters>
    <filter>
        <artifact>*:*</artifact>
        <excludes>
            <exclude>META-INF/*.SF</exclude>
            <exclude>META-INF/*.DSA</exclude>
            <exclude>META-INF/*.RSA</exclude>
        </excludes>
    </filter>
</filters>

The next step is to create the class referred to by the variable ‘maven-shade-plugin.mainClass’. I’ve called this class EntryPoint.java. This class will contain the main method executed when we run the jar, which can be blank for now.

This allows us to build the standalone jar with the command:

mvn clean package

Application code

In order to be able to configure Camel components using Guice, the application’s initial context must be created using a Camel-provided GuiceInitialContextFactory. To indicate this, we create a jndi.properties file in src/main/resources and set the value of ‘java.naming.factory.initial’ to the full name of this class:

java.naming.factory.initial=org.apache.camel.guice.jndi.GuiceInitialContextFactory

Now we can update the main method to create the InitialContext which will bootstrap Guice. The important things happen in the side effects of these methods so it is unimportant to assign them to variables at this stage.

new InitialContext();

If we were to run the application at this point it wouldn’t do anything because we haven’t told Guice to do anything. We need to tell Guice about our Camel routes so let’s start by creating one. We’ll add a class called HttpRoutes which will contain the Camel route for a simple ‘ping’ HTTP endpoint. This endpoint will be served by an embedded Jetty server and respond to HTTP GET requests to the path /ping on port 8080.

The class must extend Camel’s RouteBuilder class and therefore implement the configure() method of type void to void. The route configuration string for an http Camel route using the Jetty component looks like this:

jetty:http://$host:$port/$endpoint?$options

We create a Camel route by supplying this as the argument to the ‘from’ method. We want the response to include the text “ping” so let’s route the exchange to a processor that we’ll creatively call PingProcessor. Setting chunked to false causes the Content-Length header to be set on the response which is appropriate for this endpoint.

public void configure() throws Exception {
    from("jetty:http://0.0.0.0:8080/ping?chunked=false").process(pingProcessor);
}

We’ll worry about how the instance of pingProcessor is available to the ‘configure’ method in a minute, but first let’s create the class. The class, which we’ll call PingProcessor, must implement Camel’s Processor interface which demands a method called ‘process’ of type Exchange to void. The process method sets the response body and HTTP Content-Type header. The HttpHeaders and MediaType static constants come from a Guava net package.

public void process(final Exchange exchange) throws Exception {
    exchange.getOut().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8);
    exchange.getOut().setBody("pong");
}

Let’s add pingProcessor in HttpRoutes as a private member, and annotate it with @Inject to indicate to Guice that it needs to inject a PingProcessor object here:

@Inject
private PingProcessor pingProcessor;

Wiring it together

We’ve now defined everything we need to know about the route, but we still need to tell Guice about it. To do this, we create a new class called ApplicationModule that extends the provided CamelModuleWithMatchingRoutes class. We need to override the ‘configure’ method and use it to tell Guice about PingProcessor and HttpRoutes:

protected void configure() {
    super.configure();
    bind(PingProcessor.class);
    bind(HttpRoutes.class);
}

If we had more a more complex domain we might have wanted to bind an interface to a concrete implementation, but for this example we can just bind to the implementations. The last thing we need to do is to tell Guice about ApplicationModule, so we add the following line to jndi.properties:

org.guiceyfruit.modules=is.stanton.andy.modules.ApplicationModule

Now when the application starts, it will use Guice to instantiate the PingProcessor and HttpRoutes classes, as well as injecting the HttpRoutes class with the PingProcessor instance. Camel keeps the route running until we kill the application with ctrl-c.

Configuring Camel components

So far we’ve seen how to bootstrap a Camel application using Guice, and we’ve been able to create an configure a route, but what about configuring Camel components? Camel components are bound to JNDI names, and it happens that by using Guice to create the JNDI context, we can also use Guice to bind JNDI names to objects it manages.

As an example, let’s set the minimum and maximum number of threads available to the Jetty Camel component. Back in ApplicationModule.java we create a method that returns a JettyHttpComponent. We annotate it with @Provides to let Guice know it can be used to instantiate the JettyHttpComponent and also with @JndiBind(“jetty”) to state that we want the returned instance to be bound to the JNDI name “jetty”, which will override the default Camel JettyHttpComponent. Inside the method, we create a new JettyHttpComponent9 and set its min and max threads:

@Provides
@JndiBind("jetty")
private JettyHttpComponent jettyComponent() {
    JettyHttpComponent component = new JettyHttpComponent9();
    component.setMinThreads(10);
    component.setMaxThreads(20);
    return component;
}

We can now repackage the the application and run it:

$ mvn clean package
$ java -jar target/camel-guice-example-1.0.0.jar
Running Camel + Guice example on http://127.0.0.1:8080/ping
Press Ctrl-C to quit

Testing

Here is a example of how to test that the route works using JUnit. We create a mock PingProcessor that gets injected instead of a Guice-instantiated one in the @Before method. The test then creates an Apache HTTP client to execute a request to the created endpoint and we verify that the mock’s process method has been called.

public class HttpRoutesTest {
    private static Logger log = LoggerFactory.getLogger(HttpRoutesTest.class);

    @Mock
    private PingProcessor pingProcessor;

    @Test
    public void testPingRoute() throws Exception {
        HttpHost target = new HttpHost("127.0.0.1", 8080);
        HttpRequest request = new HttpGet("/ping");

        try (CloseableHttpClient client = HttpClients.createDefault();
             CloseableHttpResponse response = client.execute(target, request)) {
            log.info(Integer.toString(response.getStatusLine().getStatusCode()));
        }
        Mockito.verify(pingProcessor).process(Mockito.any(Exchange.class));
    }

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        Guice.createInjector(new CamelModuleWithMatchingRoutes() {
            @Override
            protected void configure() {
                super.configure();
                bind(PingProcessor.class).toInstance(pingProcessor);
                bind(HttpRoutes.class);
            }
        });
    }
}

We need to add a jndi.properties in the test resources directory in which we blank out the value of org.guiceyfruit.modules. If we don’t do this, the JNDI context will inherit the value for this key from the jndi.properties in the java resources directory.

java.naming.factory.initial=org.apache.camel.guice.jndi.GuiceInitialContextFactory
org.guiceyfruit.modules=