Red Hat Training

A Red Hat training course is available for Red Hat Fuse

1.3. Getting Started with Transactions

1.3.1. Prerequisites

Overview

The following are required to complete this example:

Java Runtime

Apache Camel requires a Java 7 development kit (JDK 1.7.0). After installing the JDK, set your JAVA_HOME environment variable to point to the root directory of your JDK, and set your PATH environment variable to include the Java bin directory.

Apache Maven 3

The Apache Camel Maven tooling requires Apache Maven version 3. To download Apache Maven, go to http://maven.apache.org/download.cgi.
After installing Apache Maven do the following:
  1. Set your M2_HOME environment variable to point to the Maven root directory.
  2. Set your MAVEN_OPTS environment variable to -Xmx512M to increase the memory available for Maven builds.
  3. Set your PATH environment variable to include the Maven bin directory:
    PlatformPath
    Windows%M2_HOME%\bin
    UNIX$M2_HOME/bin

1.3.2. Generate a New Project

Overview

Use the Maven archetype, karaf-camel-cbr-archetype, to generate a sample Java application which you can then use as a starting point for your application.

Steps

To generate the new project, perform the following steps:
  1. Open a new command window and change to the directory where you want to store the new Maven project.
  2. Enter the following command to generate the new Maven project:
    mvn archetype:generate
     -DarchetypeGroupId=io.fabric8.archetypes
     -DarchetypeArtifactId=karaf-camel-cbr-archetype
     -DarchetypeVersion=1.2.0.redhat-630xxx
     -DgroupId=tutorial
     -DartifactId=tx-jms-router
     -Dversion=1.0-SNAPSHOT
     -Dfabric8-profile=tx-jms-router-profile
    Each time you are prompted for input, press Enter to accept the default.
    This command generates a basic router application under the tx-jms-router directory. You will customize this basic application to demonstrate transactions in Apache Camel.
    Note
    Maven accesses the Internet to download JARs and stores them in its local repository.
  3. Add dependencies on the artifacts that implement Spring transactions. Look for the dependencies element in the POM file and add the following dependency elements:
    <project ...>
        ...
        <dependencies>
          ...
          <!-- Spring transaction dependencies -->
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
          </dependency>
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
          </dependency>
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
          </dependency>
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
          </dependency>
    
      </dependencies>
      ...
    </project>
    Note
    It is not necessary to specify the versions of these artifacts, because this POM is configured to use the Fabric8 BOM, which configures default artifact versions through Maven's dependency management mechanism.
  4. Add the JMS and ActiveMQ dependencies. Look for the dependencies element in the POM file and add the following dependency elements:
    <project ...>
        ...
        <dependencies>
          ...
          <!-- Persistence artifacts -->
          <dependency> 
            <groupId>org.apache.camel</groupId> 
            <artifactId>camel-jms</artifactId> 
          </dependency> 
          <dependency> 
            <groupId>org.apache.activemq</groupId> 
            <artifactId>activemq-client</artifactId> 
          </dependency>
    
      </dependencies>
      ...
    </project>

1.3.3. Configure a Transaction Manager and a Camel Route

Overview

The basic requirements for writing a transactional application in Spring are a transaction manager bean and a resource bean (or, in some cases, multiple resource beans). You can then use the transaction manager bean either to create a transactional Apache Camel component (see Section 5.2, “Demarcation by Transactional Endpoints”) or to mark a route as transactional, using the transacted() Java DSL command (see Section 5.1, “Demarcation by Marking the Route”).

Steps

To configure a JMS transaction manager and a Camel route in Blueprint XML, perform the following steps:
  1. Customize the Blueprint XML configuration. Using your favourite text editor, open the tx-jms-router/src/main/resources/OSGI-INF/blueprint/cbr.xml file and replace the contents of the file with the following XML code:
    <?xml version="1.0"?>
    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
                 http://www.osgi.org/xmlns/blueprint/v1.0.0 https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
                 http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
    
        <camelContext xmlns="http://camel.apache.org/schema/blueprint" xmlns:order="http://fabric8.com/examples/order/v7"
            id="tx-jms-router-context">
          <route>
            <from uri="file:work/data?noop=true"/>
            <convertBodyTo type="java.lang.String"/>
            <to uri="jmstx:queue:giro"/>
          </route>
          <route>
            <from uri="jmstx:queue:giro"/>
            <to uri="jmstx:queue:credits"/>
            <to uri="jmstx:queue:debits"/>
            <bean ref="myTransform" method="transform"/>
          </route>
        </camelContext>
    
      <bean id="myTransform" class="tutorial.MyTransform"/>
    
      <bean id="jmstx" class="org.apache.camel.component.jms.JmsComponent"> 
        <property name="configuration" ref="jmsConfig" /> 
      </bean> 
    
      <bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration"> 
          <property name="connectionFactory" ref="jmsConnectionFactory"/> 
          <property name="transactionManager" ref="jmsTransactionManager"/> 
          <property name="transacted" value="true"/> 
      </bean> 
    
      <bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
        <property name="connectionFactory" ref="jmsConnectionFactory" />
      </bean>
      
      <bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616"/>
        <property name="userName" value="Username"/>
        <property name="password" value="Password"/>
      </bean>
    
    </blueprint>
  2. In the jmsConnectionFactory bean from the preceding Spring XML code, customize the values of the userName and password property settings with one of the user credentials from the JBoss Fuse container. By default, the container's user credentials are normally defined in the etc/users.properties file.

1.3.4. Create the MyTransform Bean

Overview

The purpose of the MyTransform bean class is to force a rollback of the current transaction, by throwing an exception. The bean gets called at the end of the second transactional route. This enables you to verify the behaviour of a rolled back transaction.

Steps

Create the MyTransform bean class. Using your favourite text editor, create the tx-jms-router/src/main/java/tutorial/MyTransform.java file and add the following Java code to the file:
package tutorial;

import java.util.Date;
import java.util.logging.Logger;

public class MyTransform {
    private static final transient Logger LOGGER = Logger.getLogger(MyTransform.class.getName());

    public String transform(String body)
    throws java.lang.Exception
    {
        // should be printed n times due to redeliveries 
        LOGGER.info("message body = " + body); 
        // force rollback 
        throw new java.lang.Exception("test");
    }

}

1.3.5. Build and Run the Example

Overview

After building and running the example using Maven, you can use the Fuse Management Console to examine what has happened to the JMS queues involved in the application.

Steps

To build and run the transactional JMS example, perform the following steps:
  1. To build the example, open a command prompt, change directory to tx-jms-router, and enter the following Maven command:
    mvn install
    If the build is successful, you should see the file, tx-jms-router.jar, appear under the tx-jms-router/target directory.
  2. Create a sample message for the routes to consume when they are running in the container. Create the following directory path in the container's installation directory (where you installed JBoss Fuse):
    InstallDir/work/data
    In the data directory create the file, message.txt, with the following contents:
    Test message.
  3. Start up the JBoss Fuse container. Open a new command prompt and enter the following commands:
    cd InstallDir/bin
    ./fuse
  4. To install and start the example in the container, enter the following console command:
    JBossFuse:karaf@root> install -s mvn:tutorial/tx-jms-router/1.0-SNAPSHOT
  5. To see the result of running the routes, open the container log using the log:display command, as follows:
    JBossFuse:karaf@root> log:display
    If all goes well, you should see about a dozen occurrences of java.lang.Exception: test in the log. This is the expected behaviour.
  6. What happened? The series of runtime exceptions thrown by the application is exactly what we expect to happen, because the route is programmed to throw an exception every time an exchange is processed by the route. The purpose of throwing the exception is to trigger a transaction rollback, causing the current exchange to be un-enqueued from the queue:credit and queue:debit queues.
  7. To gain a better insight into what occurred, user your browser to connect to the Fuse Management Console. Navigate to the following URL in your browser:
    http://localhost:8181/hawtio
    You will be prompted to log in. Use one of the credentials configured for your container (usually defined in the InstallDir/etc/users.properties file).
  8. Click on the ActiveMQ tab to explore the JMS queues that are accessed by the example routes.
  9. Drill down to the giro queue. Notice that the EnqueueCount and DequeueCount for giro are all equal to 1, which indicates that one message entered the queue and one message was pulled off the queue.
  10. Click on the debits queue. Notice that the EnqueueCount, DispatchCount, and DequeueCount for debits are all equal to 0. This is because the test exception caused the enqueued message to be rolled back each time an exchange passed through the route. The same thing happened to the credits queue.
  11. Click on the ActiveMQ.DLQ queue. The DLQ part of this name stands for Dead Letter Queue and it is an integral part of the way ActiveMQ deals with failed message dispatches. In summary, the default behavior of ActiveMQ when it fails to dispatch a message (that is, when an exception reaches the JMS consumer endpoint, jmstx:queue:giro), is as follows:
    1. The consumer endpoint attempts to redeliver the message. Redelivery attempts can be repeated up to a configurable maximum number of times.
    2. If the redeliveries limit is exceeded, the consumer endpoint gives up trying to deliver the message and enqueues it on the dead letter queue instead (by default, ActiveMQ.DLQ).
    You can see from the status of the ActiveMQ.DLQ queue that the number of enqueued messages, EnqueueCount, is equal to 1. This is where the failed message has ended up.