jmsjndispring-jmsactivemq-artemisjboss-eap-7

Connect Tomcat to ActiveMQ embedded in JBoss EAP 7.2 using http-remoting Protocol


I have following systems defined in my docker-compose.yml for better reproducibility.

version: '2.2'
services:
  jmstool:
    image: spx01/jmstool-activemq
    container_name: jmstool
    ports:
      - 8181:8080
    volumes:
      - ./context.xml:/usr/local/tomcat/conf/context.xml
  jboss:
    image: daggerok/jboss-eap-7.2
    container_name: jboss
    environment:
      - PREPEND_JAVA_OPTS=-Djboss.server.default.config=standalone-full.xml
    ports:
      - 8080:8080
    volumes:
      - ./configure.cli:/tmp/configure.cli

The tool jmstool is a Spring application deployed on a Tomcat 9 to communicate over JMS with different queues. I could get it work with a standalone activeMQ.

After startup of these containers I add an application user and two queues to messaging subsystem of the running JBoss:

docker exec jboss jboss-eap-7.2/bin/add-user.sh -a -u myUser -p "myPa##word"
docker exec jboss jboss-eap-7.2/bin/jboss-cli.sh --file=/tmp/configure.cli

with content of configure.cli

connect
/subsystem=messaging-activemq/server=default/jms-queue=ImportantMessages:add(entries=[java:jboss/exported/ImportantMessages])
/subsystem=messaging-activemq/server=default/jms-queue=ImportantMessagesOut:add(entries=[java:jboss/exported/ImportantMessagesOut])

The standalone-full.xml looks good and quite similar to [this other example][2].

    <subsystem xmlns="urn:jboss:domain:messaging-activemq:4.0">
        <server name="default">
            <journal pool-files="10"/>
            <security-setting name="#">
                <role name="guest" send="true" consume="true" create-non-durable-queue="true" delete-non-durable-queue="true"/>
            </security-setting>
            <address-setting name="#" dead-letter-address="jms.queue.DLQ" expiry-address="jms.queue.ExpiryQueue" max-size-bytes="10485760" page-size-bytes="2097152" message-counter-history-day-limit="10"/>
            <http-connector name="http-connector" socket-binding="http" endpoint="http-acceptor"/>
            <http-connector name="http-connector-throughput" socket-binding="http" endpoint="http-acceptor-throughput">
                <param name="batch-delay" value="50"/>
            </http-connector>
            <in-vm-connector name="in-vm" server-id="0">
                <param name="buffer-pooling" value="false"/>
            </in-vm-connector>
            <http-acceptor name="http-acceptor" http-listener="default"/>
            <http-acceptor name="http-acceptor-throughput" http-listener="default">
                <param name="batch-delay" value="50"/>
                <param name="direct-deliver" value="false"/>
            </http-acceptor>
            <in-vm-acceptor name="in-vm" server-id="0">
                <param name="buffer-pooling" value="false"/>
            </in-vm-acceptor>
            <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/>
            <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/>
            <jms-queue name="ImportantMessages" entries="java:jboss/exported/ImportantMessages"/>
            <jms-queue name="ImportantMessagesOut" entries="java:jboss/exported/ImportantMessagesOut"/>
            <connection-factory name="InVmConnectionFactory" entries="java:/ConnectionFactory" connectors="in-vm"/>
            <connection-factory name="RemoteConnectionFactory" entries="java:jboss/exported/jms/RemoteConnectionFactory" connectors="http-connector"/>
            <pooled-connection-factory name="activemq-ra" entries="java:/JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="in-vm" transaction="xa"/>
        </server>
    </subsystem>

Now I try to configure this remote connection in the context.xml of the Spring application. I (sadly) have to use the http-remoting Protocol, not the connection via tcp.

<Context>
<JarScanner scanClassPath="false" scanAllFiles="false" scanAllDirectories="false"/>

<Resource
        name="jms/ImportantMessages"
        auth="Container"
        type="org.apache.activemq.command.ActiveMQQueue"
        factory="org.apache.activemq.jndi.JNDIReferenceFactory"
        physicalName="ImportantMessages"/>

<Resource
        name="jms/ImportantMessagesOut"
        auth="Container"
        type="org.apache.activemq.command.ActiveMQQueue"
        factory="org.apache.activemq.jndi.JNDIReferenceFactory"
        physicalName="ImportantMessagesOut"/>

<Environment name="spring.jms.jndi-name"
             value="jms/RemoteConnectionFactory"
             type="java.lang.String"
             override="false"/>

<Environment name="jmstool.outgoingQueues"
             value="java:comp/env/jms/ImportantMessages"
             type="java.lang.String"
             override="false"/>

<Environment name="jmstool.incomingQueues"
             value="java:comp/env/jms/ImportantMessagesOut"
             type="java.lang.String"
             override="false"/>

</Context>

The exception in the jmstool log does not surprise me:

ackOff{interval=5000, currentAttempts=14, maxAttempts=unlimited}. Cause: Could not create Transport. Reason: java.io.IOException: Transport scheme NOT recognized: [http-remoting]

This error still occurs when I add [jboss-remoting][3], [jboss-remoting3][4] or [activemq-optional][5] to Tomcat's lib folder.

Does anybody knows what I'm doing wrong?

EDIT: Justin's answer helped me. After adding artemis-jms-client-all.jar to tomcats lib file and reconfiguring the context.xml the connection could be established. Furthermore the client cannot lookup the queues because of following exception. Where can I configure the address property of the queues?

When I call

new JndiLocatorDelegate().lookup("java:comp/env/jms/ImportantMessages", javax.jms.Queue.class)

this exception is thrown

        Caused by: java.lang.IllegalArgumentException: address cannot be null
            at org.apache.activemq.artemis.jms.client.ActiveMQDestination.setSimpleAddress(ActiveMQDestination.java:414)
            at org.apache.activemq.artemis.jms.client.ActiveMQDestination.setAddress(ActiveMQDestination.java:399)
            at org.apache.activemq.artemis.jms.client.ActiveMQDestination.buildFromProperties(ActiveMQDestination.java:540)
            at org.apache.activemq.artemis.jndi.JNDIStorable.setProperties(JNDIStorable.java:58)
            at org.apache.activemq.artemis.jndi.JNDIReferenceFactory.getObjectInstance(JNDIReferenceFactory.java:65)
            at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:94)
            at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321)
            at org.apache.naming.NamingContext.lookup(NamingContext.java:840)
            ... 68 more

When I set an not empty string to address="asdf" of the queue defining Resource nodes, I get the exception

DefaultMessageListenerContainer  : Could not refresh JMS Connection for destination 'java:comp/env/jms/ImportantMessagesOut' - retrying using FixedB
ackOff{interval=5000, currentAttempts=9, maxAttempts=unlimited}. Cause: Failed to create session factory; nested exception is ActiveMQConnectionTimedOutException[errorType=CONNECTION_TIMEDOUT message=AMQ219013:
Timed out waiting to receive cluster topology. Group:null]

Solution

  • The first thing to note is that by integrating with JBoss EAP you're not working with ActiveMQ 5.x. You're working with ActiveMQ Artemis. Therefore you need to use the integration classes from ActiveMQ Artemis, e.g.:

    <Context>
    <JarScanner scanClassPath="false" scanAllFiles="false" scanAllDirectories="false"/>
    
    <Resource
            name="jms/ImportantMessages"
            auth="Container"
            type="org.apache.activemq.artemis.jms.client.ActiveMQQueue"
            factory="org.apache.activemq.artemis.jndi.JNDIReferenceFactory"
            address="ImportantMessages"/>
    
    <Resource
            name="jms/ImportantMessagesOut"
            auth="Container"
            type="org.apache.activemq.artemis.jms.client.ActiveMQQueue"
            factory="org.apache.activemq.artemis.jndi.JNDIReferenceFactory"
            address="ImportantMessagesOut"/>
    
    <Resource
            name="jms/connectionFactory"
            auth="Container"
            userName="myUser"
            password="myPa##word"
            type="org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory"
            factory="org.apache.activemq.artemis.jndi.JNDIReferenceFactory"
            brokerURL="tcp://jboss:8080?httpEnabled=true"/>
    
    <Environment name="jmstool.outgoingQueues"
                 value="java:comp/env/jms/ImportantMessages"
                 type="java.lang.String"
                 override="false"/>
    
    <Environment name="jmstool.incomingQueues"
                 value="java:comp/env/jms/ImportantMessagesOut"
                 type="java.lang.String"
                 override="false"/>
    
    </Context>
    

    This is discussed in the ActiveMQ Artemis documentation on Tomcat integration.

    Take note of the httpEnabled=true on the connection factory's brokerURL. This is an important parameter which enables the client to use the http-acceptor defined on JBoss EAP.

    Also, be sure to include the artemis-jms-client-all.jar with your application.

    You can potentially get an equivalent solution using the JNDI implementation from JBoss EAP (i.e. org.wildfly.naming.client.WildFlyInitialContextFactory). Generally speaking this would be the recommended approach when integrating JMS with JBoss EAP. However, the aforementioned org.apache.activemq.artemis.jndi.JNDIReferenceFactory provides simple, direct integration with Tomcat. Furthermore, I've not confirmed that using JBoss EAP JNDI is actually possible in this scenario, and it's almost certainly more complicated than the direct integration I outlined above.