I`m trying to look at the messages from a queue using a Browser.
Code is like:
javax.naming.InitialContext ctx = new javax.naming.InitialContext();
javax.jms.QueueConnectionFactory qcf = (javax.jms.QueueConnectionFactory)ctx.lookup('java:/XAConnectionFactory');
javax.jms.QueueConnection connection = qcf.createQueueConnection('admin', 'admin'); // qcf.createQueueConnection();
javax.jms.QueueSession session = connection.createQueueSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE);
connection.start();
// It is a "special" queue and it is not looked up from JNDI but constructed directly
javax.jms.Queue queue = (javax.jms.Queue)ctx.lookup('/queue/myQueue');
javax.jms.QueueBrowser browser = session.createBrowser(queue);
TreeMap<Date, javax.jms.Message> messageMap = new TreeMap<Date, javax.jms.Message>();
int counter = 0;
Enumeration<javax.jms.Message> enumeration = browser.getEnumeration();
while (enumeration.hasMoreElements()) {
counter++;
javax.jms.Message message = enumeration.nextElement();
messageMap.put(new Date(message.getJMSTimestamp()), message);
}
connection.stop();
ctx.close();
session.close();
connection.close();
The problem is that I always get only 1 message in the enumeration, even though when looking with the jmx-console and invoke listMessagesAsJSON I get tons of messages.
Any ideas on what am I doing wrong ?
It could be that you are hitting a bug as Sergiu said.
You could as a workaround define consumer-window-size on your connection factory differently. Maybe have a connection factory just for this use-case... or maybe upgrade the version of HornetQ.
When setting the consumer-window-size (like I did in my app) it seems that you can hit bug https://issues.jboss.org/browse/HORNETQ-691 .
Related
I am trying to retrieve messages from an ActiveMQ queue. I set the session to Session.CLIENT_ACKNOWLEDGE. When I receive the message from the server it is auto-acknowledged although I have not called acknowledge on the message. Below is sample code of what I am doing:
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination destination = session.createQueue(queue);
consumer = session.createConsumer(destination);
connection.start();
Message message = consumer.receive(1);
The code snippet above works and I do get the message from the queue. The problem is that messages disappear from the queue although I have not acknowledged the message. Any pointers why this is happening. I have been debugging this issue almost all this afternoon with no luck. This is a Spring boot application.
I got the client_ack to work in jmsTemplate. Below is the code.
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
session.recover();
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
The issue I am now seeing is that my app does not see these messages after it restarts. I tried running session.recover() but it is not making any difference. Thanks for your help.
The working code using jmsTemplate is as below:
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
session.recover();
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
This appears to be a problem with the springframework Constants class. It uses introspection to get the integer value of CLIENT_ACKNOWLEDGE but always return a value of 1, so session is always AUTO_ACKNOWLEDGE
To anybody who might run into this issue, I did the following to resolve the issue:
Set the session to non-transacted. This is the default and only option if you are using jmsTemplate. If you are creating your own consumer, you need to create the session as non-transacted when creating the jms connection
If you are creating your own consumer, you need to set the session to CLIENT_ACKNOWLEDGE.
Check the maximum number of redeliveries to the required value. This will differ depending on the product: ActiveMQ, IBM MQ, etc.
call session.recover() after retrieving the message.
I updated the working code that is using jmsTemplate in the original post.
The requirement is like to process the messages from dead letter queue by exposed a REST service API(Spring Boot).
So that once REST service is called, one message will be consumed from the DL queue and will publish in the main queue again for processing.
#RabbitListener(queues = "QUEUE_NAME") consumes the message immediately which is not required as per the scenario. The message only has to be consumed by the REST service API.
Any suggestion or solution?
I do not think RabbitListener will help here.
However you could implement this behaviour manually.
Spring Boot automatically creates RabbitMq connection factory so you could use it. When http call is made just read single message from the queue manually, you could use basic.get to synchronously get just one message:
#Autowire
private ConnectionFactory factory
void readSingleMessage() {
Connection connection = null;
Channel channel = null;
try {
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
GetResponse response = channel.basicGet(QUEUE_NAME, true);
if (response != null) {
//Do something with the message
}
} finally {
//Check if not null
channel.close();
connection.close();
}
}
If you are using Spring; you can avoid all the boilerplate in the other answer using RabbitTemplate.receive(...).
EDIT
To manually ack/reject the message, use the execute method instead.
template.execute(channel -> {
GetResponse got = channel.basicGet("foo", false);
// ...
channel.basicAck(got.getEnvelope().getDeliveryTag(), false);
return null;
});
It's a bit lower level, but again, most of the boilerplate is taken care of for you.
I'm trying to reuse a JMSContext to send multiple messages using the same context as shown in this IBM MQ tutorial.
context = cf.createContext();
destination = context.createQueue(QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
try {
TextMessage message = context.createTextMessage("Message " + i + ".\n");
producer.send(destination, message);
} catch (Exception ignore) {}
}
context.close();
Say the connection is dropped at some point. Will the context auto recovers or will I need to reconstruct the context again?
UPDATE --
This is how the current connection factory is being constructed:
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(JmsConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
cf.setStringProperty (CommonConstants.WMQ_HOST_NAME, config.getHost());
cf.setIntProperty (CommonConstants.WMQ_PORT, config.getPort());
cf.setStringProperty (CommonConstants.WMQ_CHANNEL, config.getChannel());
cf.setIntProperty (CommonConstants.WMQ_CONNECTION_MODE, CommonConstants.WMQ_CM_CLIENT);
cf.setStringProperty (CommonConstants.WMQ_QUEUE_MANAGER, config.getQueueManager());
cf.setBooleanProperty (JmsConstants.USER_AUTHENTICATION_MQCSP, false);
cf.setIntProperty (JmsConstants.PRIORITY, 0);
return cf.createContext();
Reconnect works like this (see also comment of #JoshMc):
On the client, set the reconnect option like this:
cf.setIntProperty(CommonConstants.WMQ_CLIENT_RECONNECT_OPTIONS, CommonConstants.WMQConstants.WMQ_CLIENT_RECONNECT);
On the server, stop the queue manager like this:
endmqm -r
Have u tried with creating JMSContext from existing one?
JMSContext#createContext(int sessionMode)
It will create new JMSContext but reuse the same connection.
Reference:
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/intro_jms_model.htm
https://docs.oracle.com/javaee/7/api/javax/jms/JMSContext.html
I am using jpos client (In one of the class of java Spring MVC Program) to connect the ISO8585 based server, however due to some reason server is not able to respond back, due to which my program keeps waiting for the response and results in hanging my program. So what is the proper way to implement connection timeout?
My client program look like this:
public FieldsModal sendFundTransfer(FieldsModal field){
try {
JposLogger logger = new JposLogger(ISO_LOG_LOCATION);
org.jpos.iso.ISOPackager customPackager = new GenericPackager(ISO_PACKAGER);
ISOChannel channel = new PostChannel(ISO_SERVER_IP, Integer.parseInt(ISO_SERVER_PORT), customPackager);// live
logger.jposlogconfig(channel);
channel.connect();
log4j.info("Connection established using PostChannel");
ISOMsg m = new ISOMsg();
m.set(0, field.getMti());
//m.set(2, field.getField2());
m.set(3, field.getField3());
m.set(4, field.getField4());
m.set(11, field.getField11());
m.set(12, field.getField12());
m.set(17, field.getField17());
m.set(24, field.getField24());
m.set(32, field.getField32());
m.set(34, field.getField34());
m.set(41, field.getField41());
m.set(43, field.getField43());
m.set(46, field.getField46());
m.set(49, field.getField49());
m.set(102,field.getField102());
m.set(103,field.getField103());
m.set(123, field.getField123());
m.set(125, field.getField125());
m.set(126, field.getField126());
m.set(127, field.getField127());
m.setPackager(customPackager);
System.out.println(ISOUtil.hexdump(m.pack()));
channel.send(m);
log4j.info("Message has been send");
ISOMsg r = channel.receive();
r.setPackager(customPackager);
System.out.println(ISOUtil.hexdump(r.pack()));
channel.disconnect();
}catch (Exception err) {
System.out.println("sendFundTransfer : " + err);
}
return field;
}
Well the real proper way would be to use Q2. Given you don't need a persistent connection you coud just set a timeout for the channel.
PostChannel channel = new PostChannel(ISO_SERVER_IP, Integer.parseInt(ISO_SERVER_PORT), customPackager);// live
channel.setTimeout(timeout); //timeout in millies.
This way channel will autodisconnect if nothing happens during the time specified by timeout , and your call to receive will throw an exception.
The alternative is using Q2 and a mux (see QMUX, for which you need to run Q2, or ISOMUX which is kind of deprecated).
Whenever I start the following code:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "direct_logs";
channel.exchangeDeclare(exchangeName, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, "red");
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel){
#Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException{
String message = new String(body, "UTF-8");
System.out.println(message);
System.out.println("message received");
}
};
channel.basicConsume(queueName, true, consumer);
It does not start an endless loop, as is implied in the documentation. Instead, it stops right away.
The only way I can have it consume for some time is to replace channel.basicConsume with a loop, as follows:
DateTime startedAt = new DateTime();
DateTime stopAt = startedAt.plusSeconds(60);
long i=0;
try {
while (stopAt.compareTo(new DateTime()) > 0) {
channel.basicConsume(queueName, true, consumer);
i++;
}
}finally {
System.out.println(new DateTime());
System.out.println(startedAt);
System.out.println(stopAt);
System.out.println(i);
}
There must be a better way to listen to messages for a while, correct? What am I missing?
It stops listening right away.
Are you sure it's stopping? What basicConsume does is register a consumer to listen to a specific queue so there is no need to execute it in a loop. You only execute it once, and the handleDelivery method of the instance of Consumer you pass will be called whenever a message arrives.
The Threads that the rabbitmq library creates should keep the JVM from exiting. In order to exit the program you should actually call connection.close()
Here is a complete receiver example from rabbitmq: https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/java/Recv.java
It's actually pretty much the same as yours.
i had the same issue. the reason was that i was calling connection.close at the end. however, the basicConsume() method does not block on the current thread, rather on other threads, so the code after it, i.e. the connection.close() is called immediately.