Can Message driven beans (MDB) listen on "external" MQ? - jms

I am trying to understand concepts related to MDB, MQ, JMS. I did research on SO before asking this question.
Is this possible scenario:
MDB deployed on Application Server, say on JBOSS (on physical Server-A).
MQ (say ApacheMQ) on a difference physical server-B.
So can the MDB deployed in physical server-A get messages from physical server-B?
If this is possible, then does MDB use JMS API's?
I have heard Jboss has MQ, which i presume MQ withing Jboss application server; however i want MDB in a different server and MQ server on a different physical server.
Appreciate your help in understanding this.

There are 4 real parts to this.
1) Your application. An MDB is an application which implements the J2EE Message Driven Bean API, which in its simplest form means it has an onMessage() function which will be invoked by the application server when messages arrive.
2) The J2EE application server, JBOSS is an example. This receives messages from the MQ client, and forwards them to the MDB.
3) The MQ client. This is the code written by the MQ provider (IBM/Apache/etc) which implements the JCA RA and JMS parts of J2EE. Applications can interact with this client via JMS to put and get messages, though when you're interacting as an MDB you'll be driven via your onMessage() method. This client hands messages to the application server, which drives the applications.
4) The MQ server. IBM MQ calls this a 'queue manager', and this can exist anywhere. The client from #3 will connect to the queue manager over the network.
#1, #2, and #3 need to be on the same physical machine (and run in the same JVM). #4 can be anywhere as its accessed over the network.
To address your points:
So can the MDB deployed in physical server-A get messages from physical server-B?
Yes
If this is possible, then does MDB use JMS API's?
The MDB is driven by the application server using the J2EE API, JMS is just a part of this.
By the way, 'MQ' is the name of product rather than a concept. The generic name is 'Messaging Provider'. IBM MQ and ApacheMQ are both messaging providers.

An example of an MDB that is activated by a remote MQ instance:
/**
* WebSphereMQ.java
*
* Created on Sep 21, 2012, 9:11:29 AM
*
* To the extent possible under law, Red Hat, Inc. has dedicated all copyright to this
* software to the public domain worldwide, pursuant to the CC0 Public Domain Dedication. This
* software is distributed without any warranty.
*
* See <http://creativecommons.org/publicdomain/zero/1.0/>.
*
*/
package org.jboss.sample.mq;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.MessageDriven;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.jboss.ejb3.annotation.Pool;
import org.jboss.ejb3.annotation.ResourceAdapter;
import org.jboss.logging.Logger;
/**
*
*/
#MessageDriven(name = "WebSphereMQ", activationConfig = {
#ActivationConfigProperty(propertyName = "maxPoolDepth", propertyValue="100"),
#ActivationConfigProperty(propertyName = "maxMessages", propertyValue="1"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "hostName", propertyValue = "10.0.0.150"),
#ActivationConfigProperty(propertyName = "port", propertyValue = "1414"),
#ActivationConfigProperty(propertyName = "userName", propertyValue = "redhat"),
#ActivationConfigProperty(propertyName = "password", propertyValue = "redhat"),
#ActivationConfigProperty(propertyName = "channel", propertyValue = "SYSTEM.DEF.SVRCONN"),
#ActivationConfigProperty(propertyName = "queueManager", propertyValue = "REDHAT.QUEUE.MANAGER"),
#ActivationConfigProperty(propertyName = "useJNDI", propertyValue = "true"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/queue/gssQueue"),
#ActivationConfigProperty(propertyName = "transportType", propertyValue = "CLIENT") })
#Pool(value="MQpool")
#ResourceAdapter(value="wmq.jmsra.7.5.0.4.rar")
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class WebSphereMQ implements MessageListener {
private static final Logger logger = Logger.getLogger(WebSphereMQ.class);
/*
* (non-Javadoc)
*
* #see javax.jms.MessageListener#onMessage(javax.jms.Message)
*/
public void onMessage(Message message) {
try {
logger.info("Received message: " + message.getJMSMessageID() + " : " + ((TextMessage)message).getText());
try {
Thread.sleep(10000);
} catch(Exception e) {
logger.info("interrupted");
}
}
}

For people are using Glassfish4/Payara-Server, this is the equivalent for #Doug Grove #ResourceAdapter :
#MessageDriven(
name = "foo",
activationConfig = {
#ActivationConfigProperty(propertyName = "resourceAdapter", propertyValue = "activemq-rar-x-x-x"),
:
:
when activemq-rar-x-x-x is the ActiveMQ resource adapter deployed at AS.
Regards.

Related

Spring Boot IBM Queue - Discover all Destinations

I am writing a small spring boot application that is supposed to monitor queues on an external IBM Queue installation.
I am able to connect via MQXAQueueConnectionFactory, but I have not found a way to discover all remote queues/destinations on that Host programmatically. I don't want to add them fix in my code.
How can I get a list of all existing queues in order to add listeners? I have to mention that an access via REST-API is not possible because this feature has been disabled by the administration.
You can use the IBM MQ Programmable Command Formats. If you installed the IBM MQ samples, the tools/pcf/samples/PCF_DisplayActiveLocalQueues.java gives you an idea for your use case.
Here is how I use it in my unit tests to find all the queues with messages:
import java.io.IOException;
import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.constants.CMQCFC;
import com.ibm.mq.constants.MQConstants;
import com.ibm.mq.headers.MQDataException;
import com.ibm.mq.headers.pcf.PCFMessage;
import com.ibm.mq.headers.pcf.PCFMessageAgent;
public class MqUtils {
public static void queuesWithMessages(MQQueueManager qmgr) {
try {
PCFMessageAgent agent = new PCFMessageAgent(qmgr);
try {
PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q);
// NOTE: You can not use a queue name pattern like "FOO.*" together with
// the "addFilterParameter" method. This is a limitation of PCF messages.
// If you want to filter on queue names, you would have to do it in the
// for loop after sending the PCF message.
request.addParameter(CMQC.MQCA_Q_NAME, "*");
request.addParameter(CMQC.MQIA_Q_TYPE, MQConstants.MQQT_LOCAL);
request.addFilterParameter(CMQC.MQIA_CURRENT_Q_DEPTH, CMQCFC.MQCFOP_GREATER, 0);
for (PCFMessage response : agent.send(request)) {
String queueName = (String) response.getParameterValue(CMQC.MQCA_Q_NAME);
if (queueName == null
|| queueName.startsWith("SYSTEM")
|| queueName.startsWith("AMQ")) {
continue;
}
Integer queueDepth = (Integer) response.getParameterValue(CMQC.MQIA_CURRENT_Q_DEPTH);
// Do something with this queue that has messages
}
} catch (MQException | IOException e) {
throw new RuntimeException(e);
} finally {
agent.disconnect();
}
} catch (MQDataException e) {
throw new RuntimeException(e);
}
}
}
And this should give you ideas how to configure the MQQueueManager (see also IBM docs):
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQException;
import com.ibm.mq.MQQueueManager;
#Configuration
static class MQConfig {
#Bean(destroyMethod = "disconnect")
public MQQueueManager mqQueueManager() throws MQException {
MQEnvironment.hostname = "the.host.com";
MQEnvironment.port = 1415;
MQEnvironment.channel = "xxx.CL.FIX";
return new MQQueueManager("xxx");
}
}
The chapter Using with IBM MQ classes for JMS explains how you can use PCF messages in pure JMS.

listening to IBM mq using message driven beans in Websphere application server

I have an application in springboot which is using jms to receive messages from ibm mq synchronously i.e., using .receive() method which is running fine, Now, I am implementing another process to run in background to receive async messages which is older than 2 minutes, from same queue which uses #Async and Message driven beans(onMessage()) .
My method implementation for mdb is as below:
#Service
#Slf4j
#MessageDriven(mappedName = "REPL.AI", activationConfig = {
#ActivationConfigProperty(propertyName = "connectionFactoryLookup", propertyValue = "jms/queueConnectionFactory"),
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "port", propertyValue = "1414")})
public class MessageBean implements MessageListener {
#Autowired
private AsyncMessageReceiver asyncMessageReceiver;
#Override
#Async("AsyncExecutor")
public void onMessage(Message message) {
log.info("ONMESSAGE-START");
TextMessage msg = null;
try {
if (message instanceof TextMessage) {
msg = (TextMessage) message;
log.info("received an async message");
asyncMessageReceiver.processIntoText(msg); //calling other method for further processing
}
} catch (Exception e) {
log.error("Exception occurs in onMessage(): " + e);
}
log.info("ONMESSAGE-END");
}
}
Also I have created a listener port '1414' in WAS server console to bind the mdb to port.
All configuration is already provided.
problem is, mdb is not receiving any messages from the queue , nor it is throwing any error.
I can see in logs
MDB Listener 1414 started successfully for JMSDestination jms/ReplQueue.
after this I dont see any exception and not any incoming messages too,messages have been sent through sender.
any pointers for this?

Wildfly - MDB stops processing other messages after error occur in one message

To simplify the problem I've configured an MDB with only one session.
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.slf4j.Logger;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
#MessageDriven(activationConfig = {
#ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/jms/queue/adwordsReportRequest"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "veiculo = 'adwords'"),
#ActivationConfigProperty(propertyName = "maxSession", propertyValue = "1"),
#ActivationConfigProperty(propertyName = "transactionTimeout", propertyValue = "7500") })
public class ReportRequestReceiveDispatcherAdwords implements MessageListener {
#Resource
private MessageDrivenContext mdc;
#Inject
private Logger logger;
private Integer count = 0;
public ReportRequestReceiveDispatcherAdwords() {
// TODO Auto-generated constructor stub
}
public void onMessage(Message inMessage) {
TextMessage msg = null;
String text = null;
try {
if (inMessage instanceof TextMessage) {
msg = (TextMessage) inMessage;
text = msg.getText();
if (count >= 10 && count <= 100) {
logger.error("Count: {} - {} - Marcando como erro!", ++count, text);
throw new RuntimeException("teste de erro");
} else
logger.info("Count: {} - {}", ++count, text);
} else {
logger.warn("Message of wrong type: " + inMessage.getClass().getName());
}
} catch (Exception e) {
mdc.setRollbackOnly();
}
}
}
The Queue is configured with 3 attempts and 2 minutes of delay between them.
<address-setting name="#" dead-letter-address="jms.queue.DLQ" expiry-address="jms.queue.ExpiryQueue" max-delivery-attempts="3" redelivery-delay="120000" max-size-bytes="10485760" page-size-bytes="2097152" message-counter-history-day-limit="10"/>
Executing this code with 600 messages in the queue:
14:10 --> it stopped the processing at the first error.
14:12 --> processes 3 messages and stops.
14:14 --> processes 10 messages and stops.
14:16 --> processes 14 messages and stops.
14:18 --> processes 10 messages and stops.
14:20 --> processes 16 messages and stops.
14:22 --> processes 9 messages and stops.
14:24 --> processes 8 messages and stops.
14:26 --> processes 7 messages and stops.
14:28 --> processes 6 messages and stops.
14:30 --> processes 6 messages and stops.
14:32 --> processes 6 messages and stops.
14:34 --> It goes out of the loop that generates error, so it finish the processing of the other messages until the queue is empty.
Why the MDB stops if still there is messages in the queue? When a message comes back to the queue after a rollback, it goes to the top blocking te rest? It's not making any sense to me. I couldn't find any doc where this behavior is described.
According to the specification:
Other subsequent messages will be delivery regularly, only the
cancelled message will be sent asynchronously back to the queue after
the delay.
https://activemq.apache.org/artemis/docs/2.0.0/undelivered-messages.html

JMS Topic subscription Tomee 1.7.1

I was experimenting with Message Driven Beans in order to receive Topic subscription messages from an external ActiveMQ instance.
My tests started first with Queue subscriptions which is working pretty nice.
Then I wanted to try Topic subscriptions but I cannot get it working.
This is what I have:
conf/tomee.xml
<tomee>
<Resource id="MyJmsResourceAdapter" type="ActiveMQResourceAdapter">
BrokerXmlConfig =
ServerUrl = tcp://192.168.1.176:61616
</Resource>
<Resource id="MyJmsConnectionFactory" type="javax.jms.ConnectionFactory">
ResourceAdapter = MyJmsResourceAdapter
</Resource>
<Container id="MyJmsMdbContainer" ctype="MESSAGE">
ResourceAdapter = MyJmsResourceAdapter
</Container>
<Resource id="MyQueue" type="javax.jms.Queue"/>
<Resource id="MyTopic" type="javax.jms.Topic"/>
</tomee>
This is the MDB:
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
#MessageDriven(mappedName = "MyTopic", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic")
}
)
public class TestMDBTopic implements MessageListener {
public TestMDBTopic() {
super();
}
public void onMessage(Message message) {
System.out.println("TOPIC\tMESSAGE: " + message);
}
}
I do not know why but from the log I can see that TomEE creates a queue instead of a topic:
Nov 19, 2014 11:17:00 PM org.apache.openejb.config.AutoConfig logAutoCreateResource
INFO: Auto-creating a Resource with id 'MyTopic' of type 'javax.jms.Queue for 'TestMDBTopic'.
Another proof for this is that the server will not start when I add duration configuration:
#ActivationConfigProperty(propertyName = "subscriptionDurability", propertyValue = "Durable")
The server then complains that this doesn't fit to the configured type javax.jms.Queue.
I have also tried to make a TopicConsumer ina stupid simple mail method which was working perfectly.
Also when I remove the queue configuration (MyQueue) from the entire configuration files this doesn't make a difference.
Anybody any idea what I'm doing wrong?
I had exactly the same issue. I fixed the issue by removing the mappedName property in the #MessageDriven annotation. Tomee must be keying off of the mappedName and assuming it represents a queue destination. Anyways, I am now able to send messages to the topic and they are consumed by the listener.

How to filter by message type before sending a JMS message or send to a particular server?

Assume Active MQ is the broker and there are 6 servers where JMS listeners are attached.
When a Topic is send then all the six servers will consume the messages.
Now i want a particular server only consume the message instead of all six using selector and it should be filtered before sending the JMS.
How to define a selector in JMS so that the topic will be consumed by a particular server instead of all.
You need JMS message selectors. There is no need to filter before sending, but upon receiving. An example where only server with id "serv_5" receives the message:
Producer:
...
Message message = session.createMessage();
message.setObjectProperty("server_id", "serv_5");
producer.send(message);
Consumer (MDB):
#MessageDriven(mappedName="jms/YourQueue", activationConfig = {
#ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
#ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(
propertyName="messageSelector",
propertyValue="server_id = 'serv_5'")
})
public class YourMessageBean implements MessageListener { ..

Resources