Chapter 2. Getting started with transactions on Karaf (OSGi)

This section describes a Camel application that uses transactions to access an Artemis JMS broker. The information is organized as follows:

2.1. Prerequisites

Implementation of this Camel application has the following prerequisites:

  • An external AMQ 7 JMS message broker must be running.

    The following sample code runs a standalone (non-Docker) version of amq-broker-7.1.0-bin.zip. Execution creates and runs an amq7 instance:

    $ pwd
    /data/servers/amq-broker-7.1.0
    
    $ bin/artemis create --user admin --password admin --require-login amq7
    Creating ActiveMQ Artemis instance at: /data/servers/amq-broker-7.1.0/amq7
    
    Auto tuning journal ...
    done! Your system can make 27.78 writes per millisecond, your journal-buffer-timeout will be 36000
    
    You can now start the broker by executing:
    
       "/data/servers/amq-broker-7.1.0/amq7/bin/artemis" run
    
    Or you can run the broker in the background using:
    
       "/data/servers/amq-broker-7.1.0/amq7/bin/artemis-service" start
    
    $ amq7/bin/artemis run
               __  __  ____    ____            _
         /\   |  \/  |/ __ \  |  _ \          | |
        /  \  | \  / | |  | | | |_) |_ __ ___ | | _____ _ __
       / /\ \ | |\/| | |  | | |  _ <| '__/ _ \| |/ / _ \ '__|
      / ____ \| |  | | |__| | | |_) | | | (_) |   <  __/ |
     /_/    \_\_|  |_|\___\_\ |____/|_|  \___/|_|\_\___|_|
    
     Red Hat JBoss AMQ 7.1.0.GA
    
    
    018-05-02 16:37:19,294 INFO  [org.apache.activemq.artemis.integration.bootstrap] AMQ101000: Starting ActiveMQ Artemis Server
    ...
  • Client libraries are required. Artemis libraries are available in Maven Central or a Red Hat repository. For example, you can use:

    • mvn:org.apache.activemq/artemis-core-client/2.4.0.amq-710008-redhat-1
    • mvn:org.apache.activemq/artemis-jms-client/2.4.0.amq-710008-redhat-1

    Alternatively, Artemis/AMQ 7 client libraries can be installed as Karaf features, for example:

    • karaf@root()> feature:install artemis-jms-client artemis-core-client
  • Some supporting features that provide Karaf shell commands or dedicated Artemis support are required:

    karaf@root()> feature:install jms pax-jms-artemis pax-jms-config
  • Required Camel features are:

    karaf@root()> feature:install camel-jms camel-blueprint

2.2. Building the camel-jms project

You can download the quickstarts from the Fuse Software Downloads page.

Extract the contents of the zip file to a local folder, for example a new folder named quickstarts.

You can then build and install the /camel/camel-jms example as an OSGi bundle. This bundle contains a Blueprint XML definition of a Camel route that sends messages to an AMQ 7 JMS queue.

In the following example, $FUSE_HOME is the location of the unzipped Fuse distribution. To build this project:

  1. Invoke Maven to build the project:

    $ cd quickstarts
    
    $ mvn clean install -f camel/camel-jms/
  2. Create a JMS connection factory configuration so that the javax.jms.ConnectionFactory service is published in the OSGi runtime. To do this, copy quickstarts/camel/camel-jms/src/main/resources/etc/org.ops4j.connectionfactory-amq7.cfg into the $FUSE_HOME/etc directory. This configuration will be processed to create a working connection factory. For example:

    $ cp camel/camel-jms/src/main/resources/etc/org.ops4j.connectionfactory-amq7.cfg ../etc/
  3. Verify the published connection factory:

    karaf@root()> service:list javax.jms.ConnectionFactory
    [javax.jms.ConnectionFactory]
    -----------------------------
     felix.fileinstall.filename = file:$FUSE_HOME/etc/org.ops4j.connectionfactory-amq7.cfg
     name = artemis
     osgi.jndi.service.name = artemis
     password = admin
     pax.jms.managed = true
     service.bundleid = 251
     service.factoryPid = org.ops4j.connectionfactory
     service.id = 436
     service.pid = org.ops4j.connectionfactory.d6207fcc-3fe6-4dc1-a0d8-0e76ba3b89bf
     service.scope = singleton
     type = artemis
     url = tcp://localhost:61616
     user = admin
    Provided by :
     OPS4J Pax JMS Config (251)
    
    karaf@root()> jms:info -u admin -p admin artemis
    Property │ Value
    ─────────┼──────────────────────────
    product  │ ActiveMQ
    version  │ 2.4.0.amq-711002-redhat-1
    
    karaf@root()> jms:queues -u admin -p admin artemis
    JMS Queues
    ────────────────────────────────────
    df2501d1-aa52-4439-b9e4-c0840c568df1
    DLQ
    ExpiryQueue
  4. Install the bundle:

    karaf@root()> install -s mvn:org.jboss.fuse.quickstarts/camel-jms/7.0.0.redhat-SNAPSHOT
    Bundle ID: 256
  5. Confirm that it is working:

    karaf@root()> camel:context-list
     Context               Status              Total #       Failed #     Inflight #   Uptime
     -------               ------              -------       --------     ----------   ------
     jms-example-context   Started                   0              0              0   2 minutes
    karaf@root()> camel:route-list
     Context               Route               Status              Total #       Failed #     Inflight #   Uptime
     -------               -----               ------              -------       --------     ----------   ------
     jms-example-context   file-to-jms-route   Started                   0              0              0   2 minutes
     jms-example-context   jms-cbr-route       Started                   0              0              0   2 minutes
  6. As soon as the Camel routes have started, you can see a directory, work/jms/input, in your Fuse installation. Copy the files you find in this quickstart’s src/main/data directory to the newly created work/jms/input directory.
  7. Wait a few moments and you will find the same files organized by country under the work/jms/output directory:

    • order1.xml, order2.xml and order4.xml in work/jms/output/others
    • order3.xml and order5.xml in work/jms/output/us
    • order6.xml in work/jms/output/fr
  8. See the logs to check out the business logging:

    2018-05-02 17:20:47,952 | INFO  | ile://work/jms/input | file-to-jms-route                | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Receiving order order1.xml
    2018-05-02 17:20:48,052 | INFO  | umer[incomingOrders] | jms-cbr-route                    | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Sending order order1.xml to another country
    2018-05-02 17:20:48,053 | INFO  | umer[incomingOrders] | jms-cbr-route                    | 58 - org.apache.camel.camel-core - 2.21.0.fuse-000077 | Done processing order1.xml
  9. See that the queue was dynamically created:

    karaf@root()> jms:queues -u admin -p admin artemis
    JMS Queues
    ────────────────────────────────────
    DLQ
    17767323-937f-4bad-a403-07cd63311f4e
    ExpiryQueue
    incomingOrders
  10. Check Camel route statistics:

    karaf@root()> camel:route-info jms-example-context file-to-jms-route
    Camel Route file-to-jms-route
    	Camel Context: jms-example-context
    	State: Started
    	State: Started
    
    
    Statistics
    	Exchanges Total: 1
    	Exchanges Completed: 1
    	Exchanges Failed: 0
    	Exchanges Inflight: 0
    	Min Processing Time: 67 ms
    	Max Processing Time: 67 ms
    	Mean Processing Time: 67 ms
    	Total Processing Time: 67 ms
    	Last Processing Time: 67 ms
    	Delta Processing Time: 67 ms
    	Start Statistics Date: 2018-05-02 17:14:17
    	Reset Statistics Date: 2018-05-02 17:14:17
    	First Exchange Date: 2018-05-02 17:20:48
    	Last Exchange Date: 2018-05-02 17:20:48

2.3. Explanation of the camel-jms project

Camel routes are using the following endpoint URIs:

<route id="file-to-jms-route">
...
    <to uri="jms:queue:incomingOrders?transacted=true" />
</route>

<route id="jms-cbr-route">
    <from uri="jms:queue:incomingOrders?transacted=true" />
...
</route>

The jms component is configured by using this snippet:

<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
    <property name="connectionFactory">
        <reference interface="javax.jms.ConnectionFactory" />
    </property>
    <property name="transactionManager" ref="transactionManager"/>
</bean>

While the transactionManager reference is:

<reference id="transactionManager" interface="org.springframework.transaction.PlatformTransactionManager" />

As you can see, both the JMS connection factory and the Spring interface of PlatformTransactionManager are only references. There is no need to define them in Blueprint XML. These services are exposed by Fuse itself.

You have already seen that javax.jms.ConnectionFactory was created by using etc/org.ops4j.connectionfactory-amq7.cfg.

The transaction manager is:

karaf@root()> service:list org.springframework.transaction.PlatformTransactionManager
[org.springframework.transaction.PlatformTransactionManager]
------------------------------------------------------------
 service.bundleid = 21
 service.id = 527
 service.scope = singleton
Provided by :
 Red Hat Fuse :: Fuse Modules :: Transaction (21)
Used by:
 Red Hat Fuse :: Quickstarts :: camel-jms (256)

Check for other interfaces under which the actual transaction manager is registered:

karaf@root()> headers 21

Red Hat Fuse :: Fuse Modules :: Transaction (21)
------------------------------------------------
...
Bundle-Name = Red Hat Fuse :: Fuse Modules :: Transaction
Bundle-SymbolicName = fuse-pax-transx-tm-narayana
Bundle-Vendor = Red Hat
...

karaf@root()> bundle:services -p 21

Red Hat Fuse :: Fuse Modules :: Transaction (21) provides:
----------------------------------------------------------
objectClass = [org.osgi.service.cm.ManagedService]
service.bundleid = 21
service.id = 519
service.pid = org.ops4j.pax.transx.tm.narayana
service.scope = singleton
 ----
objectClass = [javax.transaction.TransactionManager]
provider = narayana
service.bundleid = 21
service.id = 520
service.scope = singleton
 ----
objectClass = [javax.transaction.TransactionSynchronizationRegistry]
provider = narayana
service.bundleid = 21
service.id = 523
service.scope = singleton
 ----
objectClass = [javax.transaction.UserTransaction]
provider = narayana
service.bundleid = 21
service.id = 524
service.scope = singleton
 ----
objectClass = [org.jboss.narayana.osgi.jta.ObjStoreBrowserService]
provider = narayana
service.bundleid = 21
service.id = 525
service.scope = singleton
 ----
objectClass = [org.ops4j.pax.transx.tm.TransactionManager]
provider = narayana
service.bundleid = 21
service.id = 526
service.scope = singleton
 ----
objectClass = [org.springframework.transaction.PlatformTransactionManager]
service.bundleid = 21
service.id = 527
service.scope = singleton

The transaction manager is available from these interfaces:

  • javax.transaction.TransactionManager
  • javax.transaction.TransactionSynchronizationRegistry
  • javax.transaction.UserTransaction
  • org.jboss.narayana.osgi.jta.ObjStoreBrowserService
  • org.ops4j.pax.transx.tm.TransactionManager
  • org.springframework.transaction.PlatformTransactionManager

You can use any of them in any context that you need. For example camel-jms requires that the org.apache.camel.component.jms.JmsConfiguration.transactionManager field be initialized. This is why the example uses:

<reference id="transactionManager" interface="org.springframework.transaction.PlatformTransactionManager" />

instead of, for example:

<reference id="transactionManager" interface="javax.transaction.TransactionManager" />