Consuming messages from a remote WildFly JMS Server

Consuming messages from a remote Wildfly JMS Server can be done in several ways. In this tutorial we will learn all possible approaches and compare the advantages/disadvantages.

In order to connect and consume messages from a remote JMS Server you have mainly three options:

  • Use a JMS Bridge: This approach is discussed in this tutorial: Configuring JMS Bridge with WildFly . The advantage of using a JMS Bridge is that is provides a more more flexible configuration which is a good fit if you need to be resilient to source or destination unavailability
  • Configure a remote Connector towards a remote JMS Server: This approach also provides resiliency in case of failure, as connection retries will happen. It’s simpler to configure than the bridge, on the other hand most remote connection options are a default. This approach is discussed in this tutorial.
  • Configuring remote connection properties in your MDB. This approach is configuration-less as you provide all connection details directly in your MDB. This approach can potentially scale better than the other options and is also  discussed in this tutorial.

Let’s see how to do it:

Creating a Remote Connector towards a remote JMS Server

So let’s say your remote Artemis MQ Server is listening on the host “remotehost” and has a netty connector active on port 5445. The first thing we need to do is creating an outbound-socket-binding for it:

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-artemis:add(host=remotehost, port=5445)

Then, in your JMS Server, you will create a remote-connector referencing the outbound-socket-binding:

/subsystem=messaging-activemq/server=default/remote-connector=remote-artemis:add(socket-binding=remote-artemis)

Finally, we will create a pooled-connection-factory referencing the remote-connector:

/subsystem=messaging-activemq/server=default/pooled-connection-factory=remote-artemis:add(connectors=[remote-artemis], entries=[java:/jms/remoteCF],user="user123" password="Password123")

As you could see from the pooled-connection-factory properties, we have provided an application user’s credentials in order to access the remote server. Finally, we’ll add a JMS Example Queue definition, that will act as a facade to the remote JMS Queue:

jms-queue add --queue-address=ExampleQueue --entries=queue/ExampleQueue java:jboss/exported/jms/queue/ExampleQueue

The expected outcome in your configuration is:

<subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0">
    <server name="default">
        <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>
        <remote-connector name="remote-artemis" socket-binding="remote-artemis"/>
        <in-vm-connector name="in-vm" server-id="0"/>
        <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"/>
        <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/>
        <jms-queue name="ExampleQueue" entries="queue/ExampleQueue java:jboss/exported/jms/queue/ExampleQueue"/>
        <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/>
        <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"/>
        <pooled-connection-factory name="remote-artemis" entries="java:/jms/remoteCF java:jboss/exported/jms/remoteCF" connectors="remote-artemis" user="user123" password="Password123"/>
    </server>
</subsystem>
  • . . .
        <outbound-socket-binding name="remote-artemis">
            <remote-destination host="remotehost" port="5445"/>
        </outbound-socket-binding>

So That’s all for your WildFly “client” JMS. Let’s go on the remote JMS server. The first thing we need to do, is activating a Netty Connector.

Why do we need to activate a NettyConnector ? The reason is that, when using the remote-connector (bound to a socket-binding) in your client JMS, the default Connector Factory is: org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory. If you have limitations on using this type of Connector you have to use a JMS Bridge which allows you more flexibility

In order to activate Netty Connector, execute the following Batch CLI:

batch
/subsystem=messaging-activemq/server=default/remote-acceptor=netty:add(socket-binding=messaging)
/socket-binding-group=standard-sockets/socket-binding=messaging:add(port=5445)
run-batch

Now let’s define the JMS Queue which will receive the messages:

jms-queue add --queue-address=ExampleQueue --entries=queue/ExampleQueue java:jboss/exported/jms/queue/ExampleQueue

Finally, we will need to create an application user to allow remote access to the JMS Server:

$ ./add-user.sh -a -u user123 -p Password123 -g guest

That’s all. Start both JMS Servers and verify that on your “client” JMS the connection factory to the Remote JMS Server is activated:

2017-04-20 09:57:50,882 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-3) WFLYJCA0007: Registered connection factory java:/jms/remoteCF

In order to test your outbound connection, you can deploy the following sample Servlet on your “client” JMS Server which sends a set of messages to the ExampleQueue:

@WebServlet("/ServletClient")
public class ServletClient extends HttpServlet {

   

    private static final int MSG_COUNT = 5;

    @Inject
    @JMSConnectionFactory("jms/remoteCF")
    private JMSContext context;

    @Resource(lookup = "java:jboss/exported/jms/queue/ExampleQueue")
    private Queue queue;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();

        try {
      
            
            out.write("<h2>Following messages will be send to the destination:</h2>");
            for (int i = 0; i < MSG_COUNT; i++) {
                String text = "This is message " + (i + 1);
                context.createProducer().send(queue, text);
                out.write("Message (" + i + "): " + text + "</br>");
            }
            out.write("<p><i>Go to your JBoss EAP Server console or Server log to see the result of messages processing</i></p>");
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

On the other hand, you could use this simple MDB on your remote JMS Server to receive messages:

@MessageDriven(name = "HelloWorldQueueMDB", activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/ExampleQueue"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")})
public class HelloWorldQueueMDB implements MessageListener {

    private static final Logger LOGGER = Logger.getLogger(HelloWorldQueueMDB.class.toString());

    public void onMessage(Message rcvMessage) {
        TextMessage msg = null;
        try {
            if (rcvMessage instanceof TextMessage) {
                msg = (TextMessage) rcvMessage;
                LOGGER.info("Received Message from queue: " + msg.getText());
            } else {
                LOGGER.warning("Message of wrong type: " + rcvMessage.getClass().getName());
            }
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
    }
}

Configure remote connection properties in your MDB

Another option for consuming messages from another WildFly JMS Server is to include the Connection properties directly in your MDB. This approach can be particularly useful if you are unable to modify the application server configuration and create a JMS Bridge or a Pooled Connection Factory to a remote JMS Server.

The following example, shows the modified MDB code which references a remote JMS Server through the connectionParameters properties:

@MessageDriven(activationConfig = {  
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/ExampleQueue"),  
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),  
@ActivationConfigProperty(propertyName = "user", propertyValue = "user123"),  
@ActivationConfigProperty(propertyName = "password", propertyValue = "Password123"),  
@ActivationConfigProperty(propertyName = "connectionParameters", propertyValue = "host=remotehost;port=5445"),  
@ActivationConfigProperty(propertyName = "connectorClassName", propertyValue = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory")  
}, mappedName = "ExampleQueue")

public class HelloWorldQueueMDB implements MessageListener {

    private static final Logger LOGGER = Logger.getLogger(HelloWorldQueueMDB.class.toString());

    public void onMessage(Message rcvMessage) {
        TextMessage msg = null;
        try {
            if (rcvMessage instanceof TextMessage) {
                msg = (TextMessage) rcvMessage;
                LOGGER.info("Received Message from queue: " + msg.getText());
            } else {
                LOGGER.warning("Message of wrong type: " + rcvMessage.getClass().getName());
            }
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
    }
}

Using Connection properties in your MDB requires code recompilation in case of change of the properties. On the other hand, you potentially consuming messages concurrently (With an MDB you have 15 sessions by default) while, on the other hand, using a JMS bridge you have a single consumer/producer moving messages. So using properties in your MDB can potentially scale better.

 References: https://developer.jboss.org/message/950533

Found the article helpful? if so please follow us on Socials