JMS consumer not receiving messages - jms

I just finished a tutorial on JMS, so it's super new to me and I'm trying to understand the basics. I'm using ActiveMQ Artemis if that matters. I created two simple applications, one named Producer and the other named Consumer. I run Producer first then run Consumer. The Consumer application never terminates and does not print the messages to the console. Weirdly, if I do not manually terminate Consumer and run Producer a second time, then I see the messages that Consumer should have received printed on the console. What's going on here? How do I get Consumer to receive and print messages from Producer?
Here's Producer:
public class Producer {
public static void main(String[] args) throws Exception {
InitialContext initialContext = null;
Connection connection = null;
initialContext = new InitialContext();
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
connection = cf.createConnection();
Session session = connection.createSession();
Queue queue = (Queue) initialContext.lookup("queue/myQueue");
Topic topic = (Topic) initialContext.lookup("topic/myTopic");
MessageProducer queueProducer = session.createProducer(queue);
MessageProducer topicProducer = session.createProducer(topic);
TextMessage queueMessage = session.createTextMessage("This message is for the queue2");
TextMessage topicMessage = session.createTextMessage("This message is for the topic2");
queueProducer.send(queueMessage);
topicProducer.send(topicMessage);
System.out.println("Message to queue sent: "+ queueMessage.getText());
System.out.println("Message to topic sent: "+ topicMessage.getText());
initialContext.close();
}
}
Here's Consumer:
public class Consumer {
public static void main(String[] args) throws Exception {
InitialContext initialContext = null;
Connection connection = null;
initialContext = new InitialContext();
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
connection = cf.createConnection();
Session session = connection.createSession();
Queue queue = (Queue) initialContext.lookup("queue/myQueue");
Topic topic = (Topic) initialContext.lookup("topic/myTopic");
MessageConsumer queueConsumer = session.createConsumer(queue);
MessageConsumer topicConsumer1 = session.createConsumer(topic);
MessageConsumer topicConsumer2 = session.createConsumer(topic);
connection.start();
TextMessage messageReceivedByQueueConsumer = (TextMessage) queueConsumer.receive();
TextMessage messageReceivedByTopicConsumer1 = (TextMessage) topicConsumer1.receive();
TextMessage messageReceivedByTopicConsumer2 = (TextMessage) topicConsumer2.receive();
System.out.println("Message received by queue consumer: "+ messageReceivedByQueueConsumer.getText());
System.out.println("Message received by topic consumer 1: "+ messageReceivedByTopicConsumer1.getText());
System.out.println("Message received by topic consumer 2: "+ messageReceivedByTopicConsumer2.getText());
connection.close();
initialContext.close();
}
}

What you're observing is, in fact, the expected behavior.
Since you run the Producer application first a message is being sent to a queue and a topic when no consumer/subscriber exists on either. The message sent to the queue is stored in the queue because that's how JMS queues work. The message sent to the topic is discarded since there are no subscriptions to receive the message. Again, this is how JMS topics work.
Then when your Consumer application runs the queueConsumer receives the message sent to the queue, but since you're invoking receive() with no timeout on topicConsumer1 the application will simply block forever since there are no messages in the topic consumer's subscription. This blocking prevents the message received from the queue from being printed.
You should run your consuming application first and then run your producer while the consuming application is still running. Then you should see all the messages received and printed as you assumed they would be.

Related

How do I get OpenMQ to redeliver messages?

I listened to an OpenMQ queue asynchronously. If I have an exception in the process of consuming a message, is there a way to get OpenMQ to push that message to me again?
#Bean
public JmsListenerContainerFactory jmsQueueListenerContainerFactory() {
DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
jmsListenerContainerFactory.setConnectionFactory(connectionFactory());
jmsListenerContainerFactory.setPubSubDomain(false);
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return jmsListenerContainerFactory;
}
MessageConsumer receiver = session.createConsumer(destination);
receiver.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage text = (TextMessage) message;
System.out.println("Received message: " + message.getText());
//The connection timed out when saving the message to the database
repository.save(text);
}
});
The reason it is not backed out is that you have acknowledged mode set to AUTO_ACKNOWLEDGE.
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
Change this to CLIENT_ACKNOWLEDGE like below:
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
Use message.acknowledge() to commit the message.
Use session.recover() to back out the message.

How to configure JmsListener on ActiveMQ for autoscaling using Qpid Sender

I have a kubernetes cluster with an activeMQ Artemis Queue and I am using hpa for autoscaling of micro services. The messages are send via QpidSender and received via JMSListener.
Messaging works, but I am not able to configure the Queue/Listener in a way, that autoscaling works as expacted.
This is my Qpid sender
public static void send(String avroMessage, String task) throws JMSException, NamingException {
Connection connection = createConnection();
connection.start();
Session session = createSession(connection);
MessageProducer messageProducer = createProducer(session);
TextMessage message = session.createTextMessage(avroMessage);
message.setStringProperty("task", task);
messageProducer.send(
message,
DeliveryMode.NON_PERSISTENT,
Message.DEFAULT_PRIORITY,
Message.DEFAULT_TIME_TO_LIVE);
connection.close();
}
private static MessageProducer createProducer(Session session) throws JMSException {
Destination producerDestination =
session.createQueue("queue?consumer.prefetchSize=1&heartbeat='10000'");
return session.createProducer(producerDestination);
}
private static Session createSession(Connection connection) throws JMSException {
return connection.createSession(Session.AUTO_ACKNOWLEDGE);
}
private static Connection createConnection() throws NamingException, JMSException {
Hashtable<Object, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
env.put("connectionfactory.factoryLookup", amqUrl);
Context context = new javax.naming.InitialContext(env);
ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("factoryLookup");
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setConnectionFactory(connectionFactory);
pooledConnectionFactory.setMaxConnections(10);
return connectionFactory.createConnection(amqUsername, amqPassword);
}
This is my Listener config
#Bean
public JmsConnectionFactory jmsConnection() {
JmsConnectionFactory jmsConnection = new JmsConnectionFactory();
jmsConnection.setRemoteURI(this.amqUrl);
jmsConnection.setUsername(this.amqUsername);
jmsConnection.setPassword(this.amqPassword);
return jmsConnection;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsConnection());
return factory;
}
And here is my Listener
#JmsListener(
destination = "queue?consumer.prefetchSize=1&heartbeat='10000'",
selector = "task = 'myTask'"
)
public void receiveMsg(Message message) throws IOException, JMSException {
message.acknowledge();
doStuff();
}
I send the message like this
QpidSender.send(avroMessage, "myTask");
This setting works. I can send different messages and as soon than there are more then 2, the second instance of my service starts and consumes the message. If later the message count is below 2, the service is terminated.
The problem is: I don't want the message to be acknowledged before the doStuff(). Because if something goes wrong or if the service is terminated before finishing doStuff(), the message is lost (right?).
But if I reorder it to
doStuff();
message.acknowledge();
the second instance can not receive a message from the broker, as long as the first service is still in doStuff() and hasn't acknowledged the message.
How do I configure this in a way, that more than one instance can consume a message from the queue, but the message isn't lost, if the service gets terminated or something else fails on doStuff()?
Use factory.setSessionTransacted(true).
See the javadocs for DefaultMessageListenerContainer:
* <p><b>It is strongly recommended to either set {#link #setSessionTransacted
* "sessionTransacted"} to "true" or specify an external {#link #setTransactionManager
* "transactionManager"}.</b> See the {#link AbstractMessageListenerContainer}
* javadoc for details on acknowledge modes and native transaction options, as
* well as the {#link AbstractPollingMessageListenerContainer} javadoc for details
* on configuring an external transaction manager. Note that for the default
* "AUTO_ACKNOWLEDGE" mode, this container applies automatic message acknowledgment
* before listener execution, with no redelivery in case of an exception.

How to send jms message from one server hosting an application to another server hosting another application both are hosted in Websphere servers

I understand if both the message queues (receiver and response) are present in the same server location I can use the JNDI connection factory and queue name
jms/myqueue_qcf1
jms/myqueue1, to connect to the queue and send message to jms/myqueue_qcf2, jms/myqueue2
But in case of Interserver connectivity, will this be the same
When the firewall b/w both the servers is opened.
The MQ myqueue2 is setup as remote mq in Websphere.
Any help with code reference would be appreciable.
public void myAppListener extends MessageListener{ //getting message from MQ1 -
//sent by some other application - MQ1 is Local
//in appServer1
private static final String JMS_LC_JNDI_NAME = "jms/liftcargo_lara_qcf";
private static final String JMS_LC_QUEUE_NAME = "jms/APP.OUT.REQUEST";
public void onMessage(Message msg){
try{
TextMessage requestMessage = (TextMessage) msg;
String reqMessage = requestMessage.getText();
String correlationId = requestMessage.getJMSCorrelationID();
sendXMLToNextAppMQ(reqMessage , correlationId)
}
}
public static void sendXMLToNextAppMQ(String message, String correlID) throws JMSException { //The MQ to which the message is forwarded to is a Remote MQ, in different server appServer2
try {
InitialContext context = new InitialContext();
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory)context.lookup(JMS_LC_JNDI_NAME);
System.out.println("Connection Factory data :: "+queueConnectionFactory.toString());
Queue queue = (Queue) context.lookup(JMS_LC_QUEUE_NAME);
System.out.println("Check Queue Name :: "+queue.toString());
QueueConnection queueConnection = queueConnectionFactory.createQueueConnection();
QueueSession session = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender queueSender = session.createSender(queue);
TextMessage message1 = session.createTextMessage();
message1.setText(message);
message1.setJMSType("Tunnel message from CCAM.LARA.OUT.REQUEST MQ to
LIFTCARGO.OUT.LARA.REQUEST MQ");
message1.setJMSDestination(queue);
message1.setJMSCorrelationID(correlID);
queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
queueSender.send(message1);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (JMSException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
In Method sendXMLToNextAppMQ (i.e., tunnel the msg recieved in MQ1 in appServer1 to MQ2 in appServer2) is there any other jndi properties needed to mention to connect appServer1 to MQ2 in appServer2 (firewall is opened b/w appServer1 & appServer2)
If your target request queue is on a different server, your application will still have the same code, but the name you supply will not be the name of a QLOCAL on the queue manager you are connected to, but instead will be a QREMOTE. If you are using JNDI to refer to your queues, you don't even have to change the name of the queue, only the referenced real MQ queue name need be changed.
For example, if you were using the command line JMSAdmin tool, you'd change the name in the QUEUE attribute only:-
DEFINE Q(myqueue2) QUEUE(Q.FOR.REQUESTS)
Then on your two queue managers, there would be definitions that look something like these:-
Local QM (QM1)
DEFINE QREMOTE(Q.FOR.REQUESTS) RNAME(WORK.QUEUE) RQMNAME(QM2) XMITQ(QM2)
DEFINE QLOCAL(QM2) USAGE(XMITQ) DESCR('Transmission queue for messages to QM2')
DEFINE QLOCAL(MY.REPLY.Q) DESCR('My application queue for responses')
DEFINE CHANNEL(TO.QM2) CHLTYPE(SDR) CONNAME('(qm2.machine.com(1414)') XMITQ(QM2)
DEFINE CHANNEL(TO.QM1) CHLTYPE(RCVR)
Remote QM (QM2)
DEFINE QLOCAL(WORK.QUEUE)
DEFINE QLOCAL(QM1) USAGE(XMITQ) DESCR('Transmission queue for messages to QM1')
DEFINE CHANNEL(TO.QM2) CHLTYPE(RCVR)
DEFINE CHANNEL(TO.QM1) CHLTYPE(SDR) CONNAME('qm1.machine.com(1414)') XMITQ(QM1)
In addition, it is good practice in a request/reply application, to provide the name of the queue where the response should be sent as part of the request message, and to code the responding application to read those fields (ReplyTo fields) and use them to send the reply message back, thus not requiring an extra QREMOTE definition on the Remote QM (QM2 in my example).

MQ MessageConsumer does not respond to receive() method

I have a java program I run to write messages to Mid-Tier IBM MQ's to test functionality before attaching our main programs to them. The write method looks like the following below:
private static void sendSingleMessage(ConnectionFactory connectionFactory,
String[] messages, String destination) throws Exception {
Connection connection = null;
try {
connection = connectionFactory.createConnection();
for (String payload : messages) {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(destination);
MessageProducer producer = session.createProducer(queue);
Message msg = session.createTextMessage(payload);
System.out.println("Sending text '" + payload + "'");
producer.send(msg);
session.close();
System.out.println("Message sent");
}
} finally {
if (connection != null) {
connection.close();
}
}
}
The connectionFactory is setup before this method executes, but within that method I set the MQConncetionFactory properties(host,port,channel,queuemanager, etc...) This send method works and I can see the queue depth increasing on my IBM MQ Explorer when I call it from my main method.
When I run a similar readSingleMessage method, the code gets stuck on the consumer.receive() and never finishes executing. See below:
private static void readSingleMessage(ConnectionFactory connectionFactory,
String[] messages, String destination) throws Exception {
Connection connection = null;
try {
connection = connectionFactory.createConnection();
for (String payload : messages) {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(destination);
MessageConsumer consumer = session.createConsumer(queue);
System.out.println("Recieving text '" + payload + "'");
consumer.receive();
session.close();
System.out.println("Received message");
}
} finally {
if (connection != null) {
connection.close();
}
}
}
Is there anyway I can further debug this, or find why I am able to write to the queue, but unable to read a message off of it?
You have to start the JMS Connection by calling the start() method on it. You cannot receive any messages until the connection is started. This is noted in the JMS Specification and Javadoc.
As an aside, if you use the JMS 2.0 "simplified" API and create a JMSContext object (an object which is essentially a combined Connection and Session) you do not need to call start to receive messages. A consumer crated from it can be used to receive messages without being explicitly started.

Network connection between two embedded brokers to share topic messages

I'm trying to create a network of embedded brokers, the topology I'm trying to achieve is as follows:
So I want to run Broker 1 which would be the initial receiver of all the messages and hold a topic %X%. Then I want to connect Broker 2 and Broker 3 to Broker 1 and make them listen to Broker 1 through a network connection.
Eventually I want to allow consumers to receive messages from %X% topic connecting to Broker 2 and Broker 3.
So far I've written the following code:
Broker 1:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Broker 2:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61617");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Broker 3:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61618");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Producer:
public class Producer {
private Connection connection;
public Producer() throws JMSException {
// Create a ConnectionFactory
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://localhost:61616");
connection = connectionFactory.createConnection();
connection.start();
....
}
public void produceMessage(int x) {
try {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createTopic("Testtopic");
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
String text = "Hello world " + x + "! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message = session.createTextMessage(text);
System.out.println("Sent message: "+ message.hashCode() + " : " + Thread.currentThread().getName());
producer.send(message);
session.close();
}
......
}
}
Consumer:
public class Consumer {
public Consumer() throws JMSException {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://localhost:61618"); // BROKER 3
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(session.createTopic("Testtopic"));
consumer.setMessageListener(new HelloMessageListener());
}
private static class HelloMessageListener implements MessageListener {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("Consumer " + Thread.currentThread().getName() + " received message: " + textMessage.getText());
.......
}
}
}
However, when I connect to tcp://localhost:61618 (which is Broker 3) by consumer, I cannot receive any message. In the meantime if a connect directly to
tcp://localhost:61616 (the initial receiver, Broker 1), consumer receives messages and everything goes well. I think I missed something in connectors configuration. Could you please help me with this?
Thanks,
Cheers
The networkConnectors look to have the wrong port in the uri.
should be:
On broker1:
.addNetworkConnector(tcp://localhost:61617)
.addNetworkConnector(tcp://localhost:61618)
You don't need the network connectors from 2, 3 back to 1.
I also suggest configuring the full object and adding some parameters to bolster the config... duplex="false", networkTTL=1, etc.

Resources