I am trying to write a test for a class used to send a JMS-message to ActiveMQ. What I am trying to accomplish is to get a method in the class under test to send the message to an ActiveMQ instance in localhost, and then pick the message up in the test and verify that it is correct.
I have chosen this as my broker url: vm://localhost?broker.persistent=true, which means that a local ActiveMQ instance will be created, and the messages stored in a KahaDB (which is also created.) (I tried using broker.persistent=false, but since the method under test has a finally-clause that closes the connection, the in-memory messages are then lost before I can retrieve them.)
In order to retrieve the message and verify it, I have the following code:
//call method under test to send a message
//create a ConnectionFactory with url vm://localhost?broker.persistent=true
final Connection connection = connectionFactory.createConnection();
connection.start();
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
final Destination dest = session.createQueue("my.queue");
final MessageConsumer messageConsumer = session.createConsumer(dest);
Message message = messageConsumer.receive(1000);
messageConsumer.close();
session.close();
connection.close();
My problem is that upon running this code, the messages are not being removed from KahaDb! Upon multiple test runs, the message added the first time will be read again and again. Am I missing something here, or is this a bug in KahaDB/ActiveMQ? I am using ActiveMQ 5.7.0.
Try
final Session session =
connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
otherwise you get a "transacted" session.
Otherwise, if you really want to have a "transacted" session, you have to call
// 2nd parameter is ignored, if the session is transacted
final Session session =
connection.createSession(true, -1);
// Read messages
session.commit();
messageConsumer.close();
session.close();
connection.close();
in order to remove all messages you have read during this session.
For your reference, there is an excellent overview from Javaworld regarding Transactions and redelivery in JMS. It covers additional possibilities as well (using Session.CLIENT_ACKNOWLEDGE to acknowledge messages individually, for example).
You've created a transacted session but never called commit. In this case when the close method is called the in-flight transaction is rolled back and so the message that you received is placed back into the queue and will be redelivered to another consumer. You can test this by querying the redelivered count on the message and see that it increases each time. To consume the message call session.commit() before closing the session.
Related
I am using RMQ and it's JMS client to publish messages to RMQ (this is a requirement i have, I can't use their java client instead of JMS client).
So, basically I do this:
RMQConnectionFactory factory = new RMQConnectionFactory() ;
factory.setUsername(props.getProperty("rmq.username"));
factory.setPassword(props.getProperty("rmq.password"));
factory.setHost(props.getProperty("rmq.host"));
factory.setVirtualHost(props.getProperty("rmq.virtualHost"));
factory.setPort(Integer.parseInt(props.getProperty("rmq.port")));
Connection connection = factory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
String queueName = managerProps.getProperty("rmq.queue.name");
Queue queue = session.createQueue(queueName);
producer = session.createProducer(queue);
TextMessage msg = session.createTextMessage(text);
msg.setText(text);
producer.send(msg);
I have a policy set up on RMQ overflow: reject-publish, so if it's over the limit RMQ is supposed to send a nack when the queue is full, but I don't seem to get it.
The question is - how do I determine if the message was rejected? I assume the producer.send(msg) to be synchronous and throw exception if the message is not published, but I don't get any exceptions, it just looks like everything got published.
JMS spec has a send(msg, CompletionListener) with a listener with two methods onCompletion and onException, but it doesn't look like RMQ JMS client implemented this method.
Is there another way to make sure that message made it through?
RabbitMQ use Publisher Confirms to guarantee that a message isn't lost, so if your Queue overflow behavior is reject-publish, the confirm channel will got a nack. It is also contains in many AMQP client.
But in JMS client, I have check the code in rabbitmq-jms-client, and no send implementaion contains CompletionListener. So if you want to enjoy reliable publish, please use AMQP client.
I did some digging, the CompletionListener is part of JMS 2.0 and RMQ only implements JMS 1.1, that's the reason it's not there.
But it looks like I can do something with transactions. I would need to change the code like this:
RMQConnectionFactory factory = new RMQConnectionFactory() ;
// ... skipping the code here
connection.start();
// set session to be transacted
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
String queueName = managerProps.getProperty("rmq.queue.name");
Queue queue = session.createQueue(queueName);
producer = session.createProducer(queue);
TextMessage msg = session.createTextMessage(text);
msg.setText(text);
producer.send(msg);
// commit transaction
session.commit();
This will work if the queue is not full, but will throw an exception after a rejected message with this:
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method(reply-code=406, reply-text=PRECONDITION_FAILED - partial tx completion, class-id=90, method-id=20)
I can then catch the exception and do what I need to do to resend/save the message.
I want to know if its required to call JMS Connection start() before we do QueueBrowser browse(). Could not find anything in javadoc about start() as a pre-op to browse() and each vendor samples for browse seems to be different. Some of them calls while other’s don’t.
I ask this as ActiveMQ does not browse messages if I don’t perform start().
ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://**:**");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, 1);
TextMessage message = session.createTextMessage();
message.setText("This is a sample message");
Queue dest = new ActiveMQQueue("Sample");
MessageProducer producer = session.createProducer(dest);
producer.send(message);
QueueBrowser browser = session.createBrowser(dest);
Enumeration<Message> messages = browser.getEnumeration();
/* Iteration code here
* If connection.start() is'nt called, no element in returned collection
* If connection.start() is called, the returned collection contains
* queue elements.
*/
..
Could not find java doc talking anything specific to start before peek on the queue. Any idea ?
Yes, Connection.Start() is required. QueueBrowser is similar to MessageConsumer with only difference being QueueBrowser does not remove message from JMS provider. Without application calling Connection.Start method JMS provider will not deliver messages.
How can I check whether a queue exists on a JMS server using the Java API? I don't want to send or receive any data to the queue for now, just verify that the queue exists. Also, the queue may be empty.
Here is my code sample. I have removed the error handling for simplicity.
Connection connection = null;
Session session = null;
connection = factory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//I was hoping this next line would throw an exception if the queue does not exist
Queue queue = session.createQueue(queueName);
My JMS server is TIBCO EMS. I'm hoping for a solution that works on versions 5-7.
Solution
I followed the recommendation in the accepted answer but created a browser instead. The following line threw an exception as desired:
QueueBrowser browser = session.createBrowser(queue);
This is dependent on the provider, but you wont know in most cases until you create the session type, such as session.createConsumer. Simply creating a consumer this way will not consume any messages until you do a receive. And it is here the behavior may change from provider to provider and configuration of the server.
For example with ActiveMQ, assuming there are no permissions blocking the user you are connecting with, the queue is created automatically when you create the session type.
With WebSphere MQ, the queue has to be defined by an admin. If it does not exist, the queue manager will return an exception with a reason code of 2085 (UNKNOWN_OBJECT_NAME).
Outside of this, you'd need to see if the particular provider had a way to access a list of queues. Using the above examples, ActiveMQ you can get the list of queues using JMX, with WebSphere MQ, you can do this if you have permissions to send PCF commands to the queue manager.
Try creating a consumer or producer off the Session passing in the queue object you just created:
session.createConsumer(queue);
This should throw an InvalidDestinationException if the queue (or topic) does not exist.
I have a producer which connects to ActiveMQ broker to send me messages to the client.
Since it expects some response from the client, it first creates a temp queue and associates it to the JMS replyto header.
It then sends the message over to the broker and waits for the response on temp queue from the client.
Receives the response from the client over the temp queue, performs required actions and then exits.
This works fine most of the times, but sporadically the application throws error messsages saying " Cannot use queue created from another connection ".
I am unable to identify what could cause this to happen as the temp queue is being created from the current session itself.
Did anyone else come across this situation and knows how to fix it?
Code snippet:
Connection conn = myJmsTemp. getConnectionFactory().createConnection();
ses = conn.createSession(transacted,ackMode);
responseQueue = ses.createTemporaryQueue();
...
MyMessageCreator msgCrtr = new MyMessageCreator(objects,responseQueue);
myJmsTemp.send(dest, msgCrtr);
myJmsTemp.setReceiveTimeout(timeout);
ObjectMessage response = (ObjectMessage)myJmsTemplate.receive(responseQueue);
Here MyMessageCreator implements MessageCreator interface.
All am trying to do is send a message to the broker and wait for a response from the client over the temp queue. Also am using a pooled connection factory to get the connection.
You get an error like this if you have a client that is trying to subscribe as a consumer on a temporary destination that was created by a different connection instance. The JMS spec defines that only the connection that created the temp destination can consume from it, so that's why the limitation exists. As for the reason you are seeing it its hard to say without seeing your code that encounters the error.
Given that your update says you are using the Pooled connection factory I'd guess that this is the root of you issue. If the consume call happens to use a different connection from the Pool than the one that created the temp destination then you would see the error that you mentioned.
I am using Glassfish JMS.
I am able to add messages to a queue.
I can see the messages using the QueueBrowser object.
However the MessageConsumer (nor the QueueReceiver) cannot receice any message and return null.
Message expiration is set to 0 and I remember to open the connection.
Any ideas?
Here is the code:
Session session = null;
Connection conn = null;
try
{
InitialContext jndi = new InitialContext();
ConnectionFactory qFactory = (ConnectionFactory)jndi.
lookup("myConnectionFactory");
conn = qFactory.createConnection();
conn.start();
Queue queue = (Queue)jndi.lookup("myQueueName");
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
QueueReceiver mc = ((QueueSession)session).createReceiver(queue);
Object m = mc.receive(2000);
//m is NULL!
QueueBrowser browser = session.createBrowser(queue);
for(Enumeration e = browser.getEnumeration(); e.hasMoreElements(); )
{
//there are many messages here...
}
That would be good to have the client code.
Similar thing happened to me when not properly committing/closing the connection on the sender side. The message would be visible when using the admin console, however, not available yet to the MDB.
Hope it helps.
Does this code run in the appserver? If it does, I'd obtain the required objects via annotations, and for a message receiver I'd use a MDB.
If this is a piece of standalone code, I had a hell of a time getting a JNDI based client working, I reverted to using the "raw" Java API.
I witnessed the same behavior happening after the first session commit, meaning that before the messages where received correctly. In my case the issue was that I was re-creating the receiver while keeping the same session.
As pointed out in this article:
Creating temporary destinations, consumers, producers and connections
are all synchronous request-response operations with the broker and so
should be avoided for processing each request as it results in lots of
chat with the JMS broker.
The solution was as simple as reusing the same receiver.