Configuring a Remote HornetQ JMS Server as a Resource Adapter

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

This article describes the configuration required to make a cluster of two HornetQ-based JMS nodes, running as messaging subsystems within EAP 6.4 instances, accessible to a third JBoss EAP 6.4. The HornetQ cluster will be accessible to the third JBoss instance via a resource adapter, and thus available to the MDBs deployed on that JBoss instance.

The procedure consists in declaring netty connections from the resource adapter node to the HornetQ nodes and using those connections from a pooled connection which exposes the remote cluster as a resource adapter.

Procedure

Declare the Outbound Socket Bindings

On the client node (the node the resource adapter gets deployed on) declare the outbound socket bindings corresponding to the HornetQ nodes. Note that the configuration shown below implies that the HornetQ node 1 runs with a port offset of 100, and HornetQ node 2 runs with a port offset of 200.

<outbound-socket-binding name="remote-hornetq-node-1-socket-binding">
    <remote-destination host="${remote.hornetq.node.one.address:127.0.0.1}" 
                        port="${remote.hornetq.node.one.hornetq.port:5545}"/>
</outbound-socket-binding>
<outbound-socket-binding name="remote-hornetq-node-2-socket-binding">
    <remote-destination host="${remote.hornetq.node.two.address:127.0.0.1}"
                        port="${remote.hornetq.node.two.hornetq.port:5645}"/>
</outbound-socket-binding>

Declare the Netty Connectors

Declare the netty connectors corresponding to the HornetQ nodes in the messaging subsystem configuration.

<subsystem xmlns="urn:jboss:domain:messaging:...">
    <hornetq-server>
        ...
        <connectors>
            ...
            <netty-connector name="remote-hornetq-node-1-connection" 
                             socket-binding="remote-hornetq-node-1-socket-binding"/>
            <netty-connector name="remote-hornetq-node-2-connection" 
                             socket-binding="remote-hornetq-node-2-socket-binding"/>
        </connectors>

       ...
</subsystem>     

Declare the Resource Adapter

Declare the resource adapter that will balance among the HornetQ nodes. It is declared as a pooled connection factory. The semantics of the system properties used in declaration will be explained in the System Properties section.

<subsystem xmlns="urn:jboss:domain:messaging:...">
    <hornetq-server>
        ...
        <jms-connection-factories>
            ...
            <pooled-connection-factory name="hornetq-remote-ra">
                <inbound-config>
                    <use-jndi>true</use-jndi>
                    <jndi-params>
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory;
java.naming.provider.url=
remote://${remote.hornetq.node.one.address}:${remote.hornetq.node.one.remoting.port},
remote://${remote.hornetq.node.two.address}:${remote.hornetq.node.two.remoting.port};
java.naming.security.principal=${remoting.user.name};
java.naming.security.credentials=${remoting.password}
                    </jndi-params>
                </inbound-config>  
                <transaction mode="xa"/>  
                <user>${jms.user.name}</user>  
                <password>${jms.password}</password>  
                <connectors>  
                    <connector-ref connector-name="remote-hornetq-node-1-connection"/>  
                    <connector-ref connector-name="remote-hornetq-node-2-connection"/>  
                </connectors>  
                <entries>  
                    <entry name="java:/RemoteJmsXA"/>  
                </entries>  
            </pooled-connection-factory>
       </jms-connection-factories>
       ...
</subsystem>     

The above declaration refers to a series of system properties, which must be set appropriately and are explained in the next section.

Note that "java.naming.security.principal" and "java.naming.security.credentials" used in <jndi-params> are not necessary if remoting security is disabled. This is how remoting security can be disabled:

Disabling Remoting Authentication

Likewise, <user>${jms.user.name}</user> and <password>${jms.password}</password> are not used if HornetQ security is disabled on the remote hosts. This is how HornetQ security can be disabled:

Disabling HornetQ Security


TODO: Properly explain in the proper place what <use-jndi>true</use-jndi> is. Use the HornetQ documentation. <use-jndi>true</use-jndi> establishes that JNDI should be used to locate the destination for incoming connections. Link back to MDB#useJNDI

TODO: Properly explain in the proper place what <ha>true</ha> is. Use the HornetQ documentation. Link back to MDB#hA

System Properties

<system-properties>
        
    <property name="remote.hornetq.node.one.address" value="127.0.0.1"/>
    <property name="remote.hornetq.node.one.hornetq.port" value="5545"/>
    <property name="remote.hornetq.node.one.remoting.port" value="4547"/>
    <property name="remote.hornetq.node.two.address" value="127.0.0.1"/>
    <property name="remote.hornetq.node.two.hornetq.port" value="5645"/>
    <property name="remote.hornetq.node.two.remoting.port" value="4647"/>
    <property name="remoting.user.name" value="some-remoting-user"/>
    <property name="remoting.password" value="some-remoting-password"/>
    <property name="jms.user.name" value="some-jms-user"/>
    <property name="jms.password" value="some-jms-password"/>
</system-properties>
  • "remote.hornetq.node.one.address" the address of the first HornetQ node.
  • "remote.hornetq.node.one.hornetq.port" the netty connector port of the first HornetQ node.
  • "remote.hornetq.node.one.remoting.port" the remoting port of the first HornetQ node. The default value is 4447 and if the node runs with an offset of 100, the value should be 4547.
  • "remote.hornetq.node.two.address" the address of the second HornetQ node.
  • "remote.hornetq.node.two.hornetq.port" the netty connector port of the second HornetQ node.
  • "remote.hornetq.node.two.remoting.port" the remoting port of the second HornetQ node. The default value is 4447 and if the node runs with an offset of 200, the value should be 4647.
  • "remoting.user.name"/"remoting.password" the remoting credentials. Remoting can be configured to allow unauthenticated access as described here: "Disabling Remoting Authentication".
  • "jms.user.name"/"jms.password" credentials required to create JMS connections. HornetQ can be configured to allow anonymous connections, as described here: "HornetQ Configuration - Disabling Security"

Resource Adapter Deployment Smoke Test

22:44:44,499 INFO  [org.hornetq.ra] (MSC service thread 1-7) HornetQ resource adaptor started
22:44:44,499 INFO  [org.jboss.as.connector.services.resourceadapters.ResourceAdapterActivatorService$ResourceAdapterActivator] (MSC service thread 1-7) IJ020002: Deployed: file://RaActivatorhornetq-remote-ra
22:44:44,500 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-1) JBAS010401: Bound JCA ConnectionFactory [java:/RemoteJmsXA]

Configure an MDB to Use the Remote JMS Server Resource Adapter

import org.jboss.ejb3.annotation.ResourceAdapter;
...
@MessageDriven(...)
@ResourceAdapter("hornetq-remote-ra")

For more details about the @ResourceAdapter annotation see:

MDB - @ResourceAdapter

Activation Configuration

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),  
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "/jms/queue/remote-inbound"),  
    @ActivationConfigProperty(propertyName = "useJNDI",propertyValue = "true")  
})

"/jms/queue/remote-inbound" is a queue deployed on the remote HornetQ server, and it is accessible to the MDB under the original name because the MDB container infrastructure performs a remote lookup in the external JNDI context. TODO more on "useJNDI" and the relationship with the pooled connection factory configuration.

MDB Deployment Smoke Test

23:10:00,636 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-6) JBAS015876: Starting deployment of "mdb-remote-ra.jar" (runtime-name: "mdb-remote-ra.jar")
23:10:00,816 INFO  [org.jboss.as.ejb3] (MSC service thread 1-6) JBAS014142: Started message driven bean 'MDB' with 'hornetq-remote-ra' resource adapter

GitHub Example

https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb-with-remote-resource-adapter

TODO

Sending Messages to Remote Destinations

This section describes the configuration required if local components need to send messages to remote destinations, via the connection factory exposed by the resource adapter deployed as described above. The components that want to send messages will must use "java:/RemoteJmsXA" connection factory, deployed as described in the "Declare the Resource Adapter" section. However, the destinations are deployed on the remote HornetQ nodes, and without additional configuration, the local components do not have access to them. In order to enable access, we need to declare an external JNDI context, as described below:

Declare an External JNDI Context

Declare locally the external JNDI Context that contains the remote destinations on the remote HornetQ nodes. Details on how to declare (import) an external JNDI context are available here:

Importing an External JNDI Context

Declare Required Module Dependencies

A dependency on "org.hornetq" is required to correctly resolve the HornetQ destination implementation classes, after they are looked up in JNDI. The dependency can be declared in the component's jboss-deployment-structure.xml deployment descriptor:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.hornetq" slot="main"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

or globally, among the global modules:

<global-modules>
    <module name="org.hornetq" slot="main"/>
</global-modules>

Sending/Receiving Messages from an Arbitrary Component

The JNDI API usage to access objects bound in the external JNDI context (in this particular case, destinations) is described here:

Accessing the External JNDI Context

A working example is available here:

https://github.com/NovaOrdis/playground/tree/master/jboss/messaging/sending-and-receiving-from-remote-destinations

Sending Messages from an MBD

The following example contains an MDB that receives messages from a remote "inbound" queue, processes them, and sends response messages on a remote "outbound" queue. Both "inbound" and "outbound" queues are deployed on the remote HornetQ server.

Full example:

https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb-with-remote-resource-adapter-that-replies-to-response-queue

Relevant sections:

1. The MDB must be configured to receive inbound messages from the remote inbound queue over the remote HornetQ server resource adapter as described here: Configure an MDB to Use the Remote JMS Server Resource Adapter.

2.

MDB that Sends Messages to the Origin Queue

@MessageDriven(name = "InQueueMDB", activationConfig = {
	@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
	@ActivationConfigProperty(propertyName = "destination", propertyValue = "${hornetq.in.queue.short}"),  
	@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
	@ActivationConfigProperty(propertyName = "useJNDI",propertyValue = "true"),  
	@ActivationConfigProperty(propertyName = "hA", propertyValue = "true")  
},mappedName = "${hornetq.inf.queue.full}")  
@ResourceAdapter("hornetq-remote-ra")  
public class InQueueMDB implements MessageListener {  

    @Resource(lookup = "java:global/remote")  
    
    private InitialContext context;  

    @Resource( name = "${hornetq.jms.connection}")  
    private ConnectionFactory  qcf;

    public void onMessage(Message message) {  

        try {  
	     if (message instanceof TextMessage) {
                 Object obj =  (Queue) context.lookup("/jms/queue/outQueue");  
		qConnection = (QueueConnection) qcf.createConnection("quickuser","quick123+");  
		qSession = qConnection.createQueueSession(true, Session.SESSION_TRANSACTED);  
		qSender = qSession.createSender(outQueue);
		qSender.send(message);

For this code to work, "org.hornetq.ra" EAP module must contain the "" dependency:

<module xmlns="urn:jboss:module:1.1" name="org.hornetq.ra">
    ...
    <dependencies>  
         ...
	 <module name="org.jboss.remote-naming"/>  

Also, org.hornetq must be added to global modules so the JMS API is visible to the application:

https://kb.novaordis.com/index.php/WildFly_Modules#Global_Modules


        <subsystem xmlns="urn:jboss:domain:ee:1.2">
            <spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
            <jboss-descriptor-property-replacement>true</jboss-descriptor-property-replacement>
            <annotation-property-replacement>false</annotation-property-replacement>
            <global-modules>
                <module name="org.hornetq" slot="main"/>
            </global-modules>
        </subsystem>

<global-modules>
	<module name="org.jboss.common-core" slot="main"/>
	<module name="org.hornetq" slot="main"/>
</global-modules>