/ Management  

Discover (again!) the hidden power of JMS; Transform your world with queues

Hi there “Process Automation” fans,

Welcome to a new installment of “Process Automation” tips.

Last week we installed RabbitMQ; This week we dive into the OpenText JMS connector for a second time (read here about our first time with Apache ActiveMQ) to boost our loosely coupled experiences. Almost every project has a request for this (especially from “Architectural roles”)…That’s also why I wanted to spend time again exploring it! Just to provide a solid answer to the request.


Let’s get right into it…

Boot up your OPA machine and make sure your RabbitMQ service is up and running as explained last week. Once both services are up and running, you need to make sure to login into the shared ‘/system’ space with your ‘sysadmin’ account. From this space we will deploy 2 new platform packages from the ‘Application Deployer’ artifact:

rabbitmq_001

In my screenshot they are already deployed, but you can follow the deployment wizard yourself…Not a hard task to do!

After this deployment I restart my TomEE instance sudo systemctl restart tomee as I see some deliveries (JAR’s) dropping off on the server as well! Now move to your own specific organization with your own developer/administration account. Open the ‘User Manager’ artifact and attach yourself to a new role called ‘JMS Connector Administrator’:

rabbitmq_002

With this new role applied, you will now see a new artifact passing by with the brilliant name ‘JMS Connector Configuration’. Open it and create yourself a new configuration with input from the below screenshots:

rabbitmq_003

rabbitmq_004

We set the values now to test, but we’ll adapt when needed to correct values…This is always good practice to during R&D!

rabbitmq_005

Notes so far:

  • The JMS configuration is called cfg_rabbitmq…It’s saved on the XMLStore /OpenText/JMSConnector.
  • The provider URL value file:///usr/lib/rabbitmq/jndi/ is from the OPA administration documentation. Create that directory yourself with mkdir /usr/lib/rabbitmq/jndi, and make sure the TomEE user tomcat:tomcat has sufficient permission to it.
  • Use this for JMS vendor value com.sun.jndi.fscontext.RefFSContextFactory (part of fscontext.jar and providerutil.jar…see further down in this post). It’s a class for reading the JNDI .bindigs file where you define the ‘Connection Factory Name’!

This is the content for the JNDI binding details where you need to match your factory name and your queue name: vi /usr/lib/rabbitmq/jndi/.bindings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
conn_factory/ClassName=com.rabbitmq.jms.admin.RMQConnectionFactory
conn_factory/FactoryName=com.rabbitmq.jms.admin.RMQObjectFactory
conn_factory/RefAddr/0/Content=jms/ConnectionFactory
conn_factory/RefAddr/0/Type=name
conn_factory/RefAddr/0/Encoding=String
conn_factory/RefAddr/1/Content=com.rabbitmq.jms.admin.RMQConnectionFactory
conn_factory/RefAddr/1/Type=type
conn_factory/RefAddr/1/Encoding=String
conn_factory/RefAddr/2/Content=com.rabbitmq.jms.admin.RMQObjectFactory
conn_factory/RefAddr/2/Type=factory
conn_factory/RefAddr/2/Encoding=String
conn_factory/RefAddr/3/Content=localhost
conn_factory/RefAddr/3/Type=host
conn_factory/RefAddr/3/Encoding=String
conn_factory/RefAddr/4/Content=5672
conn_factory/RefAddr/4/Type=port
conn_factory/RefAddr/4/Encoding=String
myqueue/ClassName=jakarta.jms.Queue
myqueue/FactoryName=com.rabbitmq.jms.admin.RMQObjectFactory
myqueue/RefAddr/0/Content=jms/Queue
myqueue/RefAddr/0/Type=name
myqueue/RefAddr/0/Encoding=String
myqueue/RefAddr/1/Content=jakarta.jms.Queue
myqueue/RefAddr/1/Type=type
myqueue/RefAddr/1/Encoding=String
myqueue/RefAddr/2/Content=com.rabbitmq.jms.admin.RMQObjectFactory
myqueue/RefAddr/2/Type=factory
myqueue/RefAddr/2/Encoding=String
myqueue/RefAddr/3/Content=myqueue
myqueue/RefAddr/3/Type=destinationName
myqueue/RefAddr/3/Encoding=String

Make sure to use the correct port 5672; The OPA admin documentation quotes about port 5673! You can verify this port with command sudo rabbitmqctl status


The service container configuration part

The next step is to create a new service container of type ‘OpenText JMS Connector’ in the ‘System Resource Manager’ artifact. Use this as input to go through the wizard:

  • Groupname: sg_jms
    • Add the 2 available webservice interfaces
  • Service container name: sc_jms
    • You can let it start automagically
    • Connect it to the OS process…For ActiveMQ, we didn’t do this, but reading the documentation about “Configuring service containers for reliable messaging” it looks like the better option.
    • Make sure to set the JRE Configuration tab to this specific JAR file (use : for Unix, and ; for Windows):
      1
      /opt/opentext/ProcessAutomationCE/defaultInst/components/jmsconnector/lib/fscontext.jar:/opt/opentext/ProcessAutomationCE/defaultInst/components/jmsconnector/lib/providerutil.jar:/usr/lib/rabbitmq/lib/rabbitmq_server-3.13.7/lib/rabbitmq-jms-3.5.0.jar:/usr/lib/rabbitmq/lib/rabbitmq_server-3.13.7/lib/amqp-client-5.26.0.jar:/opt/tomee/apache-tomee-plus-10.0.0/lib/slf4j-api-2.0.16.jar
  • Select the configuration in the final step of the wizard: cfg_rabbitmq

You can download those dependent RabbitMQ JARS here and upload them to the correct location on the VM:

Start the new JMS service container and fingers crossed it’ll be green!? Well, mine is, but with a penalty of an endless loop on errors in the Application_Server.xml logfile of OPA with this stacktrace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<log4j:event logger="com.opentext.jmsconnector.poller.BaseJMSPollerThread" timestamp="1761560902071" level="ERROR" thread="jms-compatibility-poller-1 of destination rabbitmq.myqueue">
<log4j:message><![CDATA[Error processing jms message:NullMessageContent]]></log4j:message>
<log4j:MDC><![CDATA[host=opa processid=1302]]></log4j:MDC>
<log4j:throwable><![CDATA[com.opentext.jmsconnector.exceptions.JMSConnectorException: An error occurred. The error is 'invalid stream header: 48656C6C'.
at com.opentext.jmsconnector.poller.BaseJMSPollerThread.readMessageFromQueue(BaseJMSPollerThread.java:153)
at com.opentext.jmsconnector.poller.BaseJMSPollerThread.processMessages(BaseJMSPollerThread.java:273)
at com.opentext.jmsconnector.poller.BaseJMSPollerThread.processMessagesOnQueue(BaseJMSPollerThread.java:257)
at com.cordys.applicationserver.EJBContextProvider.invokeWithPlatformContext(EJBContextProvider.java:108)
at com.cordys.applicationserver.EJBContextProvider.lambda$invokeWithPlatformInformation$2(EJBContextProvider.java:100)
at com.cordys.applicationserver.EJBContextProviderInternal.lambda$invokeWithEJBContext$0(EJBContextProviderInternal.java:52)
at com.cordys.applicationserver.PlatformContextBean.invoke(PlatformContextBean.java:20)
... 15 more
Caused by: com.rabbitmq.jms.util.RMQJMSException: invalid stream header: 48656C6C
at com.rabbitmq.jms.client.RMQMessage.fromMessage(RMQMessage.java:1087)
at com.rabbitmq.jms.client.RMQMessage.convertJmsMessage(RMQMessage.java:841)
at com.rabbitmq.jms.client.RMQMessage.convertMessage(RMQMessage.java:835)
at com.rabbitmq.jms.client.RMQMessageConsumer.receive(RMQMessageConsumer.java:358)
at com.rabbitmq.jms.client.RMQMessageConsumer.receiveNoWait(RMQMessageConsumer.java:390)
at com.opentext.jmsconnector.poller.BaseJMSPollerThread.readMessageFromQueue(BaseJMSPollerThread.java:138)
... 29 more
Caused by: java.io.StreamCorruptedException: invalid stream header: 48656C6C
at java.base/java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:989)
at java.base/java.io.ObjectInputStream.<init>(ObjectInputStream.java:416)
at com.rabbitmq.jms.util.WhiteListObjectInputStream.<init>(WhiteListObjectInputStream.java:90)
at com.rabbitmq.jms.client.RMQMessage.fromMessage(RMQMessage.java:1061)
... 34 more
]]></log4j:throwable>

What could be wrong here? Is it the queue type? Is it my “Hello world…” test-message? Did we miss of small configuration part (which is probably the case!?)…Ohw boy! It’s time for a remote debug R&D session!

Interesting note! Converting the hex-value (from the stacktrace) 48656C6C to text gives Hell…The start of my test-message Hello world...! Looks to me like it starts to swallow, but spits it out again because of invalidity.

The R&D outcome…
First I “purged” the message queue from within RabbitMQ; That removed the errors, and the service container starts with a green icon! So, it’s my message! Time to #RTFM

These are the “small letters” for RabbitMQ is relation to OPA:

  • Only Classic Queue Types are supported
  • RabbitMQ JMS topic selector plugin supports topic selectors. It does not support message selectors
  • “Disable Message Selectors” configuration must be disabled for RabbitMQ as it does not support the message selectors
  • For RabbitMQ, provide the folder path of the JNDI bindings file
  • If the “Disable Message Selectors” is not disabled (so it’s enabled? so, it has a checkmark…Or NOT!?), then JMSXDeliveryCount must be set to 1
  • Interfaces of XA transactions support are not implemented. The distributed transactions are not supported

So, do we need to enable or disable this option?

rabbitmq_006

If I read correct with my binary/logic brains…It should NOT have a checkmark! BUT have a comment below as it’s terribly documented!

Eventually I suspect my message, and wrote my own JMS client in Java for sending a message to my queue.

This seems to send a “different” but valid message (with header content_type: application/octet-stream):

rabbitmq_007

Restarting the JMS connector swallows this message like candy (with an aftertaste for later!)! 🍬

Before I forget! I detached (or “unbind”) the RabbitMQ exchange myexchange from the queue. After this it also works better, and my queue is bound to the default jms.durable.queues exchange.
This is what I also found about “jms.durable.queues”: JMS-emulation layer (created by the RabbitMQ JMS adapter to behave like a Java Message Service broker). Sounds like the OPA way of calling JMS!

So far, my OPA JMS service container is green. I’ve purged all the message in the queue as we need to move on, exploring next territories.


First OPA service call

I guess it’s time to fire our first ‘SendMessage’ SOAP call. Open the artifact ‘Web Service Interface Explorer’ and have a look for namespace http://schemas.cordys.com/jmsconnector/operations/3.0.

Time to make a test request on ‘SendMessage’ like this:

1
2
3
4
5
6
7
8
9
10
11
<SOAP:Envelope>
<SOAP:Body>
<SendMessage>
<!--It's a 2-part value!; The DestinationManager.Name plus Destination.Name-->
<destination>rabbitmq.myqueue</destination>
<!--The documentation quotes Queue|Topic-->
<jmstype>Queue</jmstype>
<message>Hello world...</message>
</SendMessage>
</SOAP:Body>
</SOAP:Envelope>

This is a response you can expect:

1
2
3
4
5
<data>
<SendMessageResponse>
<messageid>ID:e4ac14d3-5196-48ea-9c6d-34e970e4a247</messageid>
</SendMessageResponse>
</data>

You can now validate this message in RabbitMQ:

rabbitmq_008

If you want to receive this message again, do a call like this:

1
2
3
4
5
6
7
<SOAP:Envelope>
<SOAP:Body>
<GetMessage>
<destination>rabbitmq.myqueue</destination>
</GetMessage>
</SOAP:Body>
</SOAP:Envelope>

With a response:

1
2
3
4
5
6
7
8
9
10
<data>
<GetMessageResponse>
<message>
<![CDATA[Hello world...]]>
</message>
<messageid>ID:e4ac14d3-5196-48ea-9c6d-34e970e4a247</messageid>
<jmstype>Queue</jmstype>
<fromdestination>rabbitmq.myqueue</fromdestination>
</GetMessageResponse>
</data>

HOWEVER,…We see that RabbitMQ is sooooo blazing fast that our send message is directly picked up again by the poller of our JMS service container, which instantly wants to start our trigger! Yes, that’s the one with all the test values which I also see passing by in the logging: Error creating soap operation test:test in organization test for user test…AHA!


Triggers

Yes, what about those triggers we saw in the screenshots above!? What can we do with these so-called “Inbound Message” triggers? Well, I made a first example with test values, but we can make it better with an example like this:

rabbitmq_009

Have a look in the documentation as it’s even possible to pass data (like {$inputmessage}) from the JMS message as input to that BPM!!

Restart the JMS service container after this change. Now send a message via SendMessage, and you instantly see our trigger doing its job with a BPM instance in the PIM artifact:

rabbitmq_010

It’s magic…And it also works slightly different from what we saw with ActiveMQ managing those messages!

I also played with this polling mechanism:

rabbitmq_011

However, it’s again different from ActiveMQ as I still see a direct instance of my BPM passing by in the PIM after sending a new message. Well, it’s not that big of a problem; My loop is closed…I can send a message from an external party to RabbitMQ, the OPA JMS service container picks it up, removes the message from the queue, and starts my advanced BPM logic!

I call it a party! 🥳


That is a queuey(?) “DONE” where we all saw (again!) the power of a loosely coupled JMS principle to place messages on a queue and getting them off the queue via the FiFo technique. We (at least I) will have great benefits on this knowledge for my current project where the queue will be shared with a second external consumer where we want reliable message sharing across both platforms! Have a good weekend and I see you in the next one…🍺

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The Process Automation guy”?