Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development

Creating an Apache Camel JMS route in Java from the ground up

This article describes how to implement a Camel route that consumes from a JMS message broker, logs a message, and then writes the message to a directory on the filesystem. As in all the articles in this series, we'll be using only command-line tools and a text editor. To be honest, this example is about as complicated as can be implemented using this low-level approach to development, because of the large number of library dependencies involved.

I'm assuming that the reader is broadly familiar with Apache Camel and JMS messaging, is experienced with Java, and is comfortable working with command-line tools. The specific command-line examples are for Linux, but should work on Windows with the usual pathname tweaks. The download bundle (see link at the end of the article) contains Linux scripts to build and run the example.

This article builds on my earlier articles Creating an Apache Camel route in Java from the ground up, and JMS messaging from the ground up using Apache ActiveMQ. Even if you're broadly familiar with the technologies involved, I would recommend that you read those articles first, because the steps in this article make use of software and configuration described in those other articles. In particular, I assume that you have installed and configured the Apache ActiveMQ broker, and it is running and listening for connections on the default TCP port 61616. I assume also that you have obtained and installed the Apache Camel libraries.

Step 1: install and configure software

As I noted above, installing and setting up Camel and ActiveMQ are described here, and here. In this article, we'll be using the JMS message producer from the ActiveMQ article to feed the JMS queue, so I would advise testing that this works before continuing.

This example requires log4j, slf4j, and the bridging library slf4j-log4j.jar. However, these components are all bundled with ActiveMQ, so do not necessarily need to be obtained separately. Perhaps rather unexpectedly, it also requires a bunch of libraries from the Spring framework, even though it does not use Spring at all. The reason for this is that the Camel JMS client implementation uses Spring for transaction management, among other things. Happily, all the Spring libraries are also bundled with ActiveMQ. Of course, there's nothing to stop you using your own, existing installations of all these software components if you have them.

In this example, ActiveMQ is installed in /home/kevin/lib/apache-activemq-5.9-SNAPSHOT, amd Camel in /home/kevin/lib/apache-camel-2.12.1; naturally you'll need to use directories appropriate to your installation in the command-line examples shown.

Step 2: code the Camel route

The Camel route is implemented in a Java class JSMToFile. It is listed below; I hope that with the comments it is reasonably self-explanatory. The Java class does not define a specific package, and does not to be in any particular directory.
/*
Simple Camel route that consumes from JMS, logs the message content,
and writes it to a file in the directory /tmp/out. 

(c)2013 Kevin Boone
*/
import javax.jms.*;
import org.apache.activemq.*;
import org.apache.camel.*;
import org.apache.camel.impl.*;
import org.apache.camel.builder.*;
import org.apache.camel.component.jms.*;

public class JMSToFile
  {
  public static void main(String args[]) throws Exception
    {
    // As always, we must instantiate a Camel context
    CamelContext context = new DefaultCamelContext();

    // Instantiate a connection factory that is appropriate to the 
    //  JMS broker in use. In this case, we're using ActiveMQ.
    // The connection URI will vary between brokers; in this case
    //  we're using TCP protocol with the default ActiveMQ port
    ConnectionFactory connectionFactory =
      new ActiveMQConnectionFactory("tcp://localhost:61616");

    // Add the JMS connection factory as a Camel component. The name
    //  'myjms' is abitrary, but will be used in specifying the route
    //  later
    context.addComponent("myjms",
      JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));

    context.addRoutes(new RouteBuilder()
      {
      public void configure()
        {
        // Set up the route — from the input JMS broker, to
        //  the output directory, via a log operation 
        // Note that we're specifying the log class as JMSToFile, and
        //  the log level as DEBUG. We will use these parameters in
        //  the log configuration file log4j.properties to control
        //  logging from the program.
        // Note also that the specific destination we consume from is
        //  test_queue. To exercise this program, we must place messages
        //  in that destination using a JMS client
        from ("myjms:test_queue")
          .to("log:JMSToFile?level=DEBUG")
            .to("file:/tmp/out");
        }
      });

    // Start the Camel route
    context.start();

    // Wait five minutes, then stop
    Thread.sleep (60*5*1000);
    context.stop ();
    }

Step 3: compile the Camel route

To compile the Camel program, you'll need to provide on the class search path the ActiveMQ client library, the JMS API library, and the Camel core and JMS libraries.
$ CAMEL_HOME=/home/kevin/lib/apache-camel-2.12.1
$ ACTIVEMQ_HOME=/home/kevin/lib/apache-activemq-5.9-SNAPSHOT/

javac -classpath ${ACTIVEMQ_HOME}/lib/activemq-client-5.9-SNAPSHOT.jar:\
${ACTIVEMQ_HOME}/lib/geronimo-jms_1.1_spec-1.1.1.jar:\
${CAMEL_HOME}/lib/camel-core-2.12.1.jar:\
${CAMEL_HOME}/lib/camel-jms-2.12.1.jar\ 
 JMSToFile.java
As always, the backslash character indicates that this is intended to be one long command.

Because the JMSToFile class does not define a specific packge, the compiled class file will be written to the current directory.

Step 4: set up logging

The logging in this example is just slightly more sophisticated than in in earlier ones. Here, I use the log4j configuration to output DEBUG-level messages from the Camel program itself, while suppressing all but significant warnings and errors from the Camel framework.

In the Camel route, the log class was defined as JMSToFile, and the log level as DEBUG. So in log4j.properties we need something like this:

log4j.rootLogger=INFO, out
log4j.logger.org.apache.camel=WARN
log4j.logger.JMSToFile=DEBUG
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%-30.30c{1} %-5p %m%n
The important lines, that set the relevant log levels, are the ones shown in bold type.

Step 5: test the route

Assuming that the Camel program built successfully, we can run it as follows. Note that this program has an awful lot of dependencies, and I wouldn't recommend trying to run it at the command line — better to insert the command into a shell script or batch file. I'm assuming that the compiled JMSToFile.class is in the current directory, as is log4j.properties.
$ CAMEL_HOME=/home/kevin/lib/apache-camel-2.12.1
$ ACTIVEMQ_HOME=/home/kevin/lib/apache-activemq-5.9-SNAPSHOT/

$java -classpath ${ACTIVEMQ_HOME}/lib/optional/log4j-1.2.17.jar:\
${ACTIVEMQ_HOME}/lib/optional/slf4j-log4j12-1.7.5.jar:\
${CAMEL_HOME}/lib/camel-core-2.12.1.jar:\
${CAMEL_HOME}/lib/slf4j-api-1.6.6.jar:\
${CAMEL_HOME}/lib/camel-core-2.12.1.jar:\
${CAMEL_HOME}/lib/camel-jms-2.12.1.jar:\
${ACTIVEMQ_HOME}/lib/geronimo-jms_1.1_spec-1.1.1.jar:\
${ACTIVEMQ_HOME}/lib/activemq-client-5.9-SNAPSHOT.jar:\
${ACTIVEMQ_HOME}/lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-context-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-beans-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-tx-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-jms-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-jms-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/lib/optional/spring-core-3.2.4.RELEASE.jar:\
${ACTIVEMQ_HOME}/webapps/hawtio/WEB-INF/lib/commons-logging-1.0.4.jar:.\ 
 JMSToFile
Some of these libraries are components of the Spring framework, which our simple application does not use. However, it is used internally by the Camel JMS client, so the dependencies have to be satisfied.

The Camel program should produce no output if it is running properly, because the log level was set to WARN in log4j.properties.

To test the Camel route, we need to put some messages onto the JMS queue test_queue. To do this, use the message producer described in the ActiveMQ article.

$ cd /path/to/amqtest/example

$ ACTIVEMQ_HOME=/home/kevin/lib/apache-activemq-5.9-SNAPSHOT/

$ java -classpath .:target/classes:\
${ACTIVEMQ_HOME}/lib/optional/log4j-1.2.17.jar:\
${ACTIVEMQ_HOME}/lib/optional/slf4j-log4j12-1.7.5.jar:\
${ACTIVEMQ_HOME}/lib/geronimo-jms_1.1_spec-1.1.1.jar:\
${ACTIVEMQ_HOME}/lib/activemq-client-5.9-SNAPSHOT.jar:\
${ACTIVEMQ_HOME}/lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar:\
${ACTIVEMQ_HOME}/lib/slf4j-api-1.7.5.jar\
 net.kevinboone.amqtest.Producer
Running this program will append to test_queue a single message containing the text "Hello, World!". The Camel program should produce the following log output:
JMSToFile DEBUG Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello, world!]
Note that the log message describes the type of the data (String) and the message body.

If you look in the directory /tmp/out (or whatever you changed it to), you should see a new file created; there will be one file for each message, with a filename of the form

ID-hostname-XXXXX-XXXXXXXXXXXXXXXX-1-1-1-1-1
where the XXX parts provide a unique number for each message.

Summary

I hope that this article has been helpful in demonstrating how ActiveMQ, Camel, and JMS fit together at a relatively low level. Comments and suggestions are welcome, as always.

Downloads

Source code bundle

Copyright © 1994-2013 Kevin Boone. Updated Jan 07 2014