Configuring a Remote HornetQ JMS Server as a Resource Adapter
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:
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:
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:
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
TODO
- Only messages sent to one node end being processed by the MDB. Figure this out. It is probably something related to load balancing. Or, do I need to cluster the remote HQ nodes?
- https://home.feodorov.com:9443/wiki/Wiki.jsp?page=JBossEJB3MDBContainer
- https://home.feodorov.com:9443/wiki/Wiki.jsp?page=How2ConfigureJBossEJB3MDBContainerWithRemoteProvider
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 the 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:
Declare org.hornetq Dependency
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:
<subsystem xmlns="urn:jboss:domain:ee:..."> ... <global-modules> <module name="org.hornetq" slot="main"/> </global-modules> </subsystem>
For more details on global modules, see 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:
An example that uses InitialContext to look up the destinations in JNDI is available here:
Another equivalent example that uses the @Resource annotations to get the remote destination injected by the container is available here
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.
Working example:
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. Inject the ConnectionFactory to use to send response messages with:
@Resource(name = "java:/RemoteJmsXA") private ConnectionFactory cf;
3. Inject the external JNDI context to use to lookup the remote destinations with:
@Resource(lookup = "java:global/remote-hornetq") private InitialContext externalContext;
where the "lookup" value is the same value used to configure the external-context name (see <external-context> name).
4. The code that sends the response message is similar to (connection closing, error handling, etc. are rudimentary and it should not use as such in production code):
@Override public void onMessage(Message message) { ... // process the message // compute the response Queue responseQueue = (Queue)externalContext.lookup("/jms/queue/remote-outbound"); c = cf.createConnection(); Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer p = s.createProducer(responseQueue); Message m = s.createTextMessage("..."); p.send(m); c.close(); ... }
5. Declare the dependency of the deployment artifact (or a global dependency) on org.hornetq as described here "Declare org.hornetq Dependency".