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

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 { ..

Related

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?

Clustered WildFly 10 domain messaging

I have three machines located in different networks:
as-master
as-node-1
as-node-2
In as-master I have WildFly as domain host-master and the two nodes have WildFly as domain host-slave each starting an instance in the full-ha server group. From the as-master web console I can see the two nodes in the full-ha profile runtime and if I deploy a WAR it gets correctly started on both nodes.
Now, what I'm trying to achieve is messaging between the two instances of the WAR, i.e. sending a message from a producer instance in as-node-1, consumers in all the nodes should receive the message.
This is what I tried: added a topic to WildFly domain.xml:
<jms-topic name="MyTopic" entries="java:/jms/my-topic"/>
Create a JAX-RS endpoint to trigger a producer bound to the topic:
#Path("jms")
#RequestScoped
public class MessageEndpoint {
#Inject
JMSContext context;
#Resource(mappedName = "java:/jms/my-topic")
Topic myTopic;
#GET
public void sendMessage() {
this.context.createProducer().send(this.myTopic, "Hello!");
}
}
Create a MDB listening to the topic:
#MessageDriven(activationConfig = {
#ActivationConfigProperty(
propertyName = "destination",
propertyValue = "java:/jms/my-topic"
),
#ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Topic")
)
)
public class MyMessageListener implements MessageListener {
private final static Logger LOGGER = /* ... */
public void onMessage(Message message) {
try {
String body = message.getBody(String.class)
LOGGER.info("Received message: " + body);
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}
But when I curl as-node-1/jms I see the log only in as-node-1, and when I curl as-node-2/jms I see the log only in as-node-2.
Shouldn't the message be delivered on all the nodes where the WAR is deployed? What am I missing?
As I've come with the exactly same question - put the answer here.
Yes, messages should be delivered to all nodes if a destination is a Topic.
With the default configuration ActiveMQ Artemis uses broadcast to discover and connect to other ActiveMQ instances on other nodes (within the same discovery-group):
<discovery-group name="dg-group1" jgroups-channel="activemq-cluster"/>
<cluster-connection name="my-cluster" discovery-group="dg-group1" connector-name="http-connector" address="jms"/>
Still need to ensure that a JNDI name for a jms-topic starts with the "jms" to match the address="jms" in the line above (what is OK in your case: "java:/jms/my-topic")
The only thing missed in your example to get it working on all nodes is :<cluster password="yourPassword" user="activemqUser"/>
(for sure activemqUser user must be added earlier, e.g. with the addUser.sh script).
This let ActiveMQ instances to communicate each other. So called Core Bridge connection is created between nodes. As stated in ActiveMQ manual :
..this is done transparently behind the scenes - you don't have to
declare an explicit bridge for each node
If everything is OK then the bridge may be found in server.log: AMQ221027: Bridge ClusterConnectionBridge#63549ead [name=sf.my-cluster ...] is connected.
Btw, if a destination is a Queue then ActiveMQ will not send a message to other nodes unless a message is not consumed locally.
P.s. as answered here this refers to a classic approach to distribute an event to all nodes in a cluster.

Topic not able to receive message

I have a non durable Topic client which is supposed to receive messages asynchronously using a listener.
When message is published on Topic, i can see on admin console that message is published and consumed but my client never receives it.
Client is able to establish connection properly as i can track it on console.
Any suggestions?
EDIT:
Did some more analysis and found that issue is with API used for connection.
I was able to listen to messages when i use following code:
TopicConnection conn;
TopicSession session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(monacoSubscriberEmsTopic);
conn.start();
tsubs = session.createSubscriber(topic);
tsubs.setMessageListener(listener);
But when i use following code then it doesn't work:
DefaultMessageListenerContainer listenerContainer = createMessageListenerContainer();
private DefaultMessageListenerContainer createMessageListenerContainer() {
DefaultMessageListenerContainer listenerContainer = new DefaultMessageListenerContainer();
listenerContainer.setClientId(clientID);
listenerContainer.setDestinationName(destination);
listenerContainer.setConnectionFactory(connectionFactory);
listenerContainer.setConcurrentConsumers(minConsumerCount);
listenerContainer.setMaxConcurrentConsumers(maxConsumerCount);
listenerContainer.setPubSubDomain(true);
listenerContainer.setSessionAcknowledgeModeName(sessionAcknowledgeMode);
if (messageSelector != null)
listenerContainer.setMessageSelector(messageSelector);
listenerContainer.setSessionTransacted(true);
return listenerContainer;
}
listenerContainer.initialize();
listenerContainer.start();
What is wrong with second approach?

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

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.

JMS on Websphere MQ and backout queue

I have MQ as an external JMS server on my Weblogic server. The thing is that I need to rollback the message and retry until backout threshold is reached. Then I need to move the message to backout queue.
The MessageDrivenContext.setRollbackOnly() method takes care of that just fine. The problem, however, is with the message on the backout queue - it is uncommited.
Moreover, the messages are being taken from backout queue and processed again as soon as new message appears on the main queue.
This suggest me that there is something teribly wrong with my approach. I cannot, however, change the fact, that I have to retry the onMessage() with the same message a couple of times and send it to backout queue is backout threshold was reached.
#MessageDriven( name="MQListener", mappedName = "jms.mq.SOME.QUEUE.NAME",
activationConfig =
{
#ActivationConfigProperty(propertyName = "destinationType",propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "jms.mq.SOME.QUEUE.NAME"),
#ActivationConfigProperty(propertyName = "connectionFactoryJndiName", propertyValue = "jms.mq.MQ"),
#ActivationConfigProperty(propertyName = "useJNDI", propertyValue = "true")
})
public class MQListener implements MessageListener {
#Resource
private MessageDrivenContext context;
#Override
public void onMessage(Message message) {
String messageContent="";
try {
messageId = message.getJMSMessageID();
if (message != null) {
messageContent = ((TextMessage)message).getText();
if(!doSomething(messageContent)){
// doSomething fails, I need to rollback the message and try again:
context.setRollbackOnly();
}
}
} catch (Exception e) {
throw new RuntimeException();
}
}
private boolean doSomething(String messageContent){
// ...
}
}
I am a tyro in EJB. But from what I can see in your code snippet, I think you are missing initialization of MessageDrivenContext. I think you will have to do either
context = getMessageDrivenContext();
context.setRollbackOnly();
or
getMessageDrivenContext().setRollbackOnly();

Resources