I'm pretty new to JavaEE, and I want to make a message driven bean that consumes a message and a producer that produces a message. I got most of this code from a JavaEE manual, but it doesn't want to compile on my Glassfish server.
Here is the code for the MDB:
#MessageDriven(activationConfig = {
#ActivationConfigProperty(
propertyName = "destination",
propertyValue = "myQueue"),
#ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Queue")
})
public class MessageDrivenBean implements MessageListener {
public MessageDrivenBean() {
}
#Override
public void onMessage(Message message) {
try {
System.out.println("Message received: " + message.getBody(String.class));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
And the code for the producer:
#Stateless
public class MsgProducer {
public MsgProducer() {
}
public void send() {
try {
// Gets the JNDI context
System.out.println("In the producer");
InitialContext jndiContext = new InitialContext();
// Looks up the administered objects
ConnectionFactory connectionFactory = (ConnectionFactory) jndiContext.lookup("jms/myConnectionFactory");
Destination queue = (Destination) jndiContext.lookup("jms/myQueue");
// Creates the needed artifacts to connect to the queue
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
// Sends a text message to the queue
TextMessage message = session.createTextMessage("Text message sent at " + new Date());
producer.send(message);
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The method "send()" is called in another EJB.
But when I run the glassfish server the code doesn't compile and with a lot of warnings but what I can read from it is "EJB Container initialisation error". Does anybody know what to do with it?
Thank you!
PS: part of the error:
java.lang.RuntimeException: EJB Container initialization error
at org.glassfish.ejb.startup.EjbApplication.loadContainers(EjbApplication.java:210)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:267)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:75)
java.lang.RuntimeException: EJB Container initialization error
at org.glassfish.ejb.startup.EjbApplication.loadContainers(EjbApplication.java:210)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:267)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:75)
Exception while loading the app : EJB Container initialization error
java.lang.Exception
at com.sun.enterprise.connectors.inbound.ConnectorMessageBeanClient.setup(ConnectorMessageBeanClient.java:189)
at org.glassfish.ejb.mdb.MessageBeanContainer.<init>(MessageBeanContainer.java:227)
at org.glassfish.ejb.mdb.MessageBeanContainerFactory.createContainer(MessageBeanContainerFactory.java:39)
at org.glassfish.ejb.startup.EjbApplication.loadContainers(EjbApplication.java:198)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:267)
at org.glassfish.ejb.startup.EjbDeployer.load(EjbDeployer.java:75)
Solved it! The problem was that my propertyValue was javax.jms.Queue while I needed jakarta.jms.Queue.
Related
I'm having a hard time setting the Client ID on my consumers in ActiveMQ Artemis.
I'm using Spring to create the ConnectionFactory and ContainerFactory and then using it to set the ClientID. For some reason it does not appear on the Artemis Console.
Here is the code defining the factory beans on my configuration class:
#Bean
public ConnectionFactory myConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(host);
connectionFactory.setUser(user);
connectionFactory.setPassword(password);
// I have tried setting it here
connectionFactory.setClientID("myClient");
connectionFactory.setGroupID("myClient");
return connectionFactory;
}
#Bean
public DefaultJmsListenerContainerFactory myContainerFactory(#Qualifier("myConnectionFactory") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setRecoveryInterval(10000);
// I have tried setting it here
factory.setClientId("myClient");
configurer.configure(factory, connectionFactory);
return factory;
}
And here is the code where I create a bunch of listeners according to a ListerEnum:
for (ListersEnum listener : ListenersEnum.values()) {
IListener handler = BeanUtil.getBean(listener.getHandler());
DefaultJmsListenerContainerFactory containerFactory = BeanUtil.getBean<DefaultJmsListenerContainerFactory>(listener.getContainerFactory());
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId(listener.getId());
endpoint.destination = listener.getQueue();
endpoint.setMessageListener(message -> handler.onReceive(message));
DefaultMessageListenerContainer messageListenerContainer = containerFactory.createListenerContainer(endpoint);
// I have tried setting it here also
messageListenerContainer.setClientId("myClient");
endpoint.setupListenerContainer(messageListenerContainer);
jmsListenerEndpointRegistry.registerListenerContainer(endpoint, containerFactory);
messageListenerContainer.start();
}
And then on the Artemis Console, there is no Client ID set:
Besides that, I have noticed the Creation Time on the console is updated each second. Like the connection is being refresh every time. Is this normal behavior? Does it have anything to do with the client ID?
My dependencias are:
springBootVersion = '1.5.15.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-artemis'
compile 'org.apache.activemq:artemis-commons:1.4.0'
compile 'org.apache.activemq:artemis-core-client:1.4.0'
compile 'org.apache.activemq:artemis-jms-client:1.4.0'
compile 'org.apache.activemq:artemis-selector:1.4.0'
Any help is appreciated
EDIT
I have changed my myContainerFactory to:
public DefaultJmsListenerContainerFactory myContainerFactory(#Qualifier("myConnectionFactory") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setRecoveryInterval(10000);
// I have tried setting it here
factory.setClientId("myClient");
// Commented this so Spring wont set its default values
// configurer.configure(factory, connectionFactory);
factory.setConnectionFactory(connectionFactory);
return factory;
}
Then I started getting
2019-01-24 17:50:28.612 ERROR 25504 --- [enerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'TEST_QUEUE' - retrying using FixedBackOff{interval=5000, currentAttempts=9, maxAttempts=unlimited}. Cause: Client id has already been set
I tried hacking it by making my ClientID like "myClient" + new Date().getTime() but got the same result. The strange part is that if I remove the clientId altogether, on the artemis console I only see one connection... So how it is trying to create another ones with the same client id? Any ideas?
Edit 2
I made an example outside of Spring to see if this would be a Spring problem... But it doesn't seem so. I got the same results: The client ID does not show on the console and if I set it on the Connection I get the Client ID already set error. Can it be something about my artemis console...? I'm using Apache ActiveMQ Artemis 2.6.2. Please halp :s
Heres the code in case anyone want to try:
import javax.jms.*;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
public class Consumer {
public static void main(String[] args) throws JMSException {
Connection connection = null;
Session session = null;
MessageConsumer consumer = null;
ConnectionFactory factory = null;
ActiveMQConnectionFactory artemisCf = null;
try {
artemisCf = new ActiveMQConnectionFactory("tcp://localhost:61616?retryInterval=1000reconnectAttempts=-1",
"user", "pass");
artemisCf.setRetryInterval(1000);
artemisCf.setReconnectAttempts(-1);
artemisCf.setClientID("TESTE");
factory = (ConnectionFactory) artemisCf;
connection = factory.createConnection();
// If I uncomment this I get the "Client id has already been set" error
// connection.setClientID("TESTE");
session = connection.createSession(true, 2);
Queue queue = session.createQueue("QUEUE.TESTE.CONNCETION");
consumer = session.createConsumer(queue);
consumer.setMessageListener(new Listener("Me"));
connection.start();
Thread.sleep(2000000);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (consumer != null && session != null && connection != null) {
consumer.close();
session.close();
connection.close();
}
}
}
public static class Listener implements MessageListener {
private String listenerName;
public Listener(String consumerName) {
this.listenerName = consumerName;
}
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println(listenerName + " received " + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
Hello I have problem with my jms code when I try to send over 1000 messages to MDB. Following code:
#Stateless(mappedName = "RequestProcessingQueue")
public class RequestProcessingQueue {
private static final Logger logger = Logger.getLogger(RequestProcessingQueue.class);
#Resource(mappedName = "jmsRequestsFactory")
private ConnectionFactory connectionFactory;
#Resource(mappedName = "jmsRequestsDestination")
private Queue queue;
public void add(String participant, String password, List<Long> documents) throws JmsAppException {
try {
logger.debug("requests to process " + documents);
Connection connecton = connectionFactory.createConnection();
connecton.start();
Session session = connecton.createSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = (QueueSender) session.createProducer(queue);
Message msg = msg = session.createMessage();
msg.setStringProperty("participant", participant);
msg.setStringProperty("password", password);
for (Long id : documents) {
msg.setLongProperty("request", id);
sender.send(msg);
}
sender.close();
session.close();
connecton.close();
} catch (JMSException e) {
throw new JmsAppException(e);
} catch (Throwable e) {
throw new JmsAppException("Fatal error occured while sending request to be processed", e);
}
}
}
throws
MQJMSRA_DS4001: JMSServiceException on send message:sendMessage: Sending message failed. Connection ID: 2979509408914231552 com.sun.messaging.jms.ra.DirectSession._sendMessage(DirectSession.java:1844) / sendMessage: Sending message failed. Connection ID: 2979509408914231552 com.sun.messaging.jmq.jmsserver.service.imq.IMQDirectService.sendMessage(IMQDirectService.java:1955) / transaction failed: [B4303]: The maximum number of messages [1 000] that the producer can process in a single transaction (TID=2979509408914244096) has been exceeded. Please either limit the # of messages per transaction or increase the imq.transaction.producer.maxNumMsgs property. com.sun.messaging.jmq.jmsserver.data.handlers.DataHandler.routeMessage(DataHandler.java:467)'}
at jms.example.RequestProcessingQueue.add(RequestProcessingQueue.java:48)
I do not understand why cus when I create session I pass false as first param indicating that session is non transactional mode.
Your code does not work because the basic JMS API was designed to work in any environment, not just from within an EJB container. Runtime environment programming restrictions and behaviour are described in the EJB specifications and JavaDoc, in particular javax.jms.Connection.createSession(boolean transacted, int acknowledgeMode).
Your code can be simplified (assuming you're using at least Java 7) to:
#TransactionAttribute(TransactionAttributeType.NOTSUPPORTED)
public void add(String participant, String password, List<Long> documents) throws OgnivoException {
try (Connection connection = connectionFactory.createConnection();
Session session = connection.createSession();
// session.start() not required
MessageProducer sender = session.createProducer(queue)) {
logger.debug("requests to process " + documents);
for (Long id : documents) {
Message msg = msg = session.createMessage();
msg.setStringProperty("participant", participant);
msg.setStringProperty("password", password);
msg.setLongProperty("request", id);
sender.send(msg);
}
} catch (JMSException e) {
throw new JmsAppException(e);
}
// Don't catch throwable because it hides bugs
}
Remember that EJB methods are automatically associated with a transaction unless you specify otherwise. Additionally, be sure to check the javadoc for javax.jms.Connection.createSession() and associated methods, particularly the sections describing behaviour in different runtime environments.
I want to process all the messages from a JMS Queue in Glassfish 3 in a synchronous way so I have tried to change the property Maximum Active Consumers from -1 to 1 in JMS Physical Destination in Glassfish window. I think setting this I will have only one Consumer executing OnMessage() at the same time. The problem I have reached its that when I change that property I got this error:
[I500]: Caught JVM Exception: org.xml.sax.SAXParseException: Content is not allowed in prolog.
[I500]: Caught JVM Exception: com.sun.messaging.jms.JMSException: Content is not allowed in prolog.
sendMessage Error [C4038]: com.sun.messaging.jms.JMSException: Content is not allowed in prolog.
If anyone know another way to make the method onmessage() synchronous will be appreciated. This is my Consumer Class:
#MessageDriven(mappedName = "QueueListener", activationConfig = {
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class MessageBean implements MessageListener {
#Override
public void onMessage(Message message) {
long t1 = System.currentTimeMillis();
write("MessageBean has received " + message);
try{
TextMessage result=(TextMessage)message;
String text=result.getText();
write("OTAMessageBean message ID has resolved to " + text);
int messageID=Integer.valueOf(text);
AirProcessing aP=new AirProcessing();
aP.pickup(messageID);
}
catch(Exception e){
raiseError("OTAMessageBean error " + e.getMessage());
}
long t2 = System.currentTimeMillis();
write("MessageBean has finished in " + (t2-t1));
}
}
I had the same problem, the only solution I found was to set up a Schedule which polls the messages from the queue every ten seconds:
#Stateless
public class MyReceiver {
#Resource(mappedName = "jms/MyQueueFactory")
private QueueConnectionFactory connectionFactory;
#Resource(mappedName = "jms/MyQueue")
private Queue myQueue;
private QueueConnection qc;
private QueueSession session;
private MessageConsumer consumer;
#PostConstruct
void init() {
try {
qc = connectionFactory.createQueueConnection();
session = qc.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
consumer = session.createConsumer(myQueue);
qc.start();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
#PreDestroy
void cleanup() throws JMSException {
qc.close();
}
#Schedule(hour = "*", minute = "*", second = "*/10", persistent = false)
public void onMessage() throws JMSException {
Message message;
while ((message = consumer.receiveNoWait()) != null) {
ObjectMessage objMsg = (ObjectMessage) message;
Serializable content;
try {
content = objMsg.getObject();
//Do sth. with "content" here
message.acknowledge();
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
}
JMS is async by nature, you don't have a specific config for telling io to behave synchronously. You can simulate it by adding message delivery and consumption confirmations everywhere, but that's not really how JMS is intended to work. Try RMIenter link description here or maybe HTTP (or something on top of it like a SOAP or REST web service)
There are two programs: subscriber and publisher...
Subscriber is able to put the message onto the topic and the message is sent successfully.
When I check the activemq server on my browser it shows 1 msg enqueued . But when I run the consumer code, it is not receiving the message
Here is the producer code:
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class producer {
private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
// JMS messages are sent and received using a Session. We will
// create here a non-transactional session object. If you want
// to use transactions you should set the first parameter to 'true'
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("testt");
MessageProducer producer = session.createProducer(topic);
// We will send a small text message saying 'Hello'
TextMessage message = session.createTextMessage();
message.setText("HELLO JMS WORLD");
// Here we are sending the message!
producer.send(message);
System.out.println("Sent message '" + message.getText() + "'");
connection.close();
}
}
After I run this code the output at the console is:
26 Jan, 2012 2:30:04 PM org.apache.activemq.transport.failover.FailoverTransport doReconnect
INFO: Successfully connected to tcp://localhost:61616
Sent message 'HELLO JMS WORLD'
And here is the consumer code:
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class consumer {
// URL of the JMS server
private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
// Name of the topic from which we will receive messages from = " testt"
public static void main(String[] args) throws JMSException {
// Getting JMS connection from the server
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("testt");
MessageConsumer consumer = session.createConsumer(topic);
MessageListener listner = new MessageListener() {
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received message"
+ textMessage.getText() + "'");
}
} catch (JMSException e) {
System.out.println("Caught:" + e);
e.printStackTrace();
}
}
};
consumer.setMessageListener(listner);
connection.close();
}
}
After I run this code it doesnt show anything.
Can someone help to me to overcome this problem?
Your issue is that your consumer is running and then shutting down immediately.
Try adding this into your consumer:
consumer.setMessageListener(listner);
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
connection.close();
This will wait until you hit a key before stopping.
Other things to consider:
Use a finally block for the close
Java naming conventions encourage using uppercase for the first letter of a class
The main problem (besides the app closing down to quickly) is that you are sending to a Topic. Topics don't retain messages so if you run your application that produces and then run the consumer, the consumer won't receive anything because it was not subscribed to the topic at the time the message was sent. If you fix the shutdown issue and then run the consumer in one terminal and then run the producer you should then see the message received by your consumer. If you want message retention then you need to use a Queue which will hold onto the message until someone consumes it.
Your producer class is correct. It runs smoothly.
But, your consumer is incorrect & you have to modify it.
First, add setClientID("any_string_value") after creating connection object;
eg: Connection connection = connectionFactory.createConnection();
// need to setClientID value, any string value you wish
connection.setClientID("12345");
secondly, use createDurableSubscriber() method instead of createConsumer() for transmitting message via topic.
MessageConsumer consumer = session.createDurableSubscriber(topic,"SUB1234");
Here is the modified comsumer class:
package mq.test;
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class consumer {
// URL of the JMS server
private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
// Name of the topic from which we will receive messages from = " testt"
public static void main(String[] args) throws JMSException {
// Getting JMS connection from the server
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
// need to setClientID value, any string value you wish
connection.setClientID("12345");
try{
connection.start();
}catch(Exception e){
System.err.println("NOT CONNECTED!!!");
}
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("test_data");
//need to use createDurableSubscriber() method instead of createConsumer() for topic
// MessageConsumer consumer = session.createConsumer(topic);
MessageConsumer consumer = session.createDurableSubscriber(topic,
"SUB1234");
MessageListener listner = new MessageListener() {
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received message"
+ textMessage.getText() + "'");
}
} catch (JMSException e) {
System.out.println("Caught:" + e);
e.printStackTrace();
}
}
};
consumer.setMessageListener(listner);
//connection.close();
}
}
Now, your code will run successfully.
just some:
work with a queue not a topic. messages in topics will be discarded when no consumer is available, they are NOT persistend.
add connection.start() after setting the message listener. you should start a connection when all consumers/producers are properly set up.
wait some time before before closing the connection again.
the topic will probably be your most important source of failure.
I've been working with JMS and ActiveMQ. Everything is working wonders. I am not using spring, nor can I.
The interface javax.jms.MessageListener has only one method, onMessage. From within a implementation, there is a chance an exception will be thrown. If in fact an exception gets thrown, then I say the message wasn't properly processed and needs to be re-tried. So, I need ActiveMQ to wait for a little while and then, retry. i.e. I need the thrown exception to rollback the JMS transaction.
How can I accomplish such a behaviour?
Maybe there is some configuration in ActiveMQ I wasn't able to find.
Or... maybe could do away with registering MessageListeners to consumers and consume the messages myself, in a a loop like:
while (true) {
// ... some administrative stuff like ...
session = connection.createSesstion(true, SESSION_TRANSACTED)
try {
Message m = receiver.receive(queue, 1000L);
theMessageListener.onMessage(m);
session.commit();
} catch (Exception e) {
session.rollback();
Thread.sleep(someTimeDefinedSomewhereElse);
}
// ... some more administrative stuff
}
in a couple of threads, instead of registering the listener.
Or... I could somehow decorate/AOP/byte-manipulate the MessageListeners to do this themselves.
What route would you take and why?
note: I don't have full control over the MessageListeners code.
EDIT
A test for proof of concept:
#Test
#Ignore("Interactive test, just a proof of concept")
public void transaccionConListener() throws Exception {
final AtomicInteger atomicInteger = new AtomicInteger(0);
BrokerService brokerService = new BrokerService();
String bindAddress = "vm://localhost";
brokerService.addConnector(bindAddress);
brokerService.setPersistenceAdapter(new MemoryPersistenceAdapter());
brokerService.setUseJmx(false);
brokerService.start();
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(bindAddress);
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setInitialRedeliveryDelay(500);
redeliveryPolicy.setBackOffMultiplier(2);
redeliveryPolicy.setUseExponentialBackOff(true);
redeliveryPolicy.setMaximumRedeliveries(2);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
activeMQConnectionFactory.setUseRetroactiveConsumer(true);
activeMQConnectionFactory.setClientIDPrefix("ID");
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(activeMQConnectionFactory);
pooledConnectionFactory.start();
Connection connection = pooledConnectionFactory.createConnection();
Session session = connection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);
Queue helloQueue = session.createQueue("Hello");
MessageConsumer consumer = session.createConsumer(helloQueue);
consumer.setMessageListener(new MessageListener() {
#Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
switch (atomicInteger.getAndIncrement()) {
case 0:
System.out.println("OK, first message received " + textMessage.getText());
message.acknowledge();
break;
case 1:
System.out.println("NOPE, second must be retried " + textMessage.getText());
throw new RuntimeException("I failed, aaaaah");
case 2:
System.out.println("OK, second message received " + textMessage.getText());
message.acknowledge();
}
} catch (JMSException e) {
e.printStackTrace(System.out);
}
}
});
connection.start();
{
// A client sends two messages...
Connection connection1 = pooledConnectionFactory.createConnection();
Session session1 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection1.start();
MessageProducer producer = session1.createProducer(helloQueue);
producer.send(session1.createTextMessage("Hello World 1"));
producer.send(session1.createTextMessage("Hello World 2"));
producer.close();
session1.close();
connection1.stop();
connection1.close();
}
JOptionPane.showInputDialog("I will wait, you watch the log...");
consumer.close();
session.close();
connection.stop();
connection.close();
pooledConnectionFactory.stop();
brokerService.stop();
assertEquals(3, atomicInteger.get());
}
If you want to use SESSION_TRANSACTED as your acknowledgement mode, then you need to setup a RedeliveryPolicy on your Connection/ConnectionFactory. This page on ActiveMQ's website also contains some good info for what you might need to do.
Since you aren't using Spring, you can setup a RedeliveryPolicy with something similar to the following code (taken from one of the above links):
RedeliveryPolicy policy = connection.getRedeliveryPolicy();
policy.setInitialRedeliveryDelay(500);
policy.setBackOffMultiplier(2);
policy.setUseExponentialBackOff(true);
policy.setMaximumRedeliveries(2);
Edit
Taking your code snippet added to the answer, the following shows how this works with transactions. Try this code with the Session.rollback() method commented out and you'll see that using SESION_TRANSACTED and Session.commit/rollback works as expected:
#Test
public void test() throws Exception {
final AtomicInteger atomicInteger = new AtomicInteger(0);
BrokerService brokerService = new BrokerService();
String bindAddress = "vm://localhost";
brokerService.addConnector(bindAddress);
brokerService.setPersistenceAdapter(new MemoryPersistenceAdapter());
brokerService.setUseJmx(false);
brokerService.start();
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(bindAddress);
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setInitialRedeliveryDelay(500);
redeliveryPolicy.setBackOffMultiplier(2);
redeliveryPolicy.setUseExponentialBackOff(true);
redeliveryPolicy.setMaximumRedeliveries(2);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
activeMQConnectionFactory.setUseRetroactiveConsumer(true);
activeMQConnectionFactory.setClientIDPrefix("ID");
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(activeMQConnectionFactory);
pooledConnectionFactory.start();
Connection connection = pooledConnectionFactory.createConnection();
final Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
Queue helloQueue = session.createQueue("Hello");
MessageConsumer consumer = session.createConsumer(helloQueue);
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
switch (atomicInteger.getAndIncrement()) {
case 0:
System.out.println("OK, first message received " + textMessage.getText());
session.commit();
break;
case 1:
System.out.println("NOPE, second must be retried " + textMessage.getText());
session.rollback();
throw new RuntimeException("I failed, aaaaah");
case 2:
System.out.println("OK, second message received " + textMessage.getText());
session.commit();
}
} catch (JMSException e) {
e.printStackTrace(System.out);
}
}
});
connection.start();
{
// A client sends two messages...
Connection connection1 = pooledConnectionFactory.createConnection();
Session session1 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection1.start();
MessageProducer producer = session1.createProducer(helloQueue);
producer.send(session1.createTextMessage("Hello World 1"));
producer.send(session1.createTextMessage("Hello World 2"));
producer.close();
session1.close();
connection1.stop();
connection1.close();
}
JOptionPane.showInputDialog("I will wait, you watch the log...");
consumer.close();
session.close();
connection.stop();
connection.close();
pooledConnectionFactory.stop();
assertEquals(3, atomicInteger.get());
}
}
You need to set the acknowledgment mode to Session.CLIENT_ACKNOWLEDGE, the client acknowledges a consumed message by calling the message's acknowledge method.
QueueSession session = connection.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
Then, after processing the message to need to call the Message.acknowledge() method in order to remove that message.
Message message = ...;
// Processing message
message.acknowledge();
A little late, but here goes -
I would not use a MessageListener but rather a global pools to manage listening and processing.
ListeningPool -> listener -> submit processing task -> ProcessingPool -> execute and acknowledge or close without acknowledgment.
Maintain 2 thread pools, one for listeners and one for processors.
Have a listening Runnable implementation that listens to a queue in a while true loop, and consumer.receive(timeout) method. In the finally block, close connection, session and consumer if there was no message received. If a message is received, submit a task to processing pool with all the conn, session, message and consumer arguments.
Have a processing implementation that takes in the message, connection, session and consumer. Do your processing and acknowledge if all ok. If not, close without acknowledgement. This would trigger a redelivery according to your server's redelivery policy.
Initialize your listening pool with all the Listener Tasks, listening for messages, each for one queue. Initialize your processing pool with parameters acceptable to your application runtime.
public class CustomMessageListener implements Runnable {
private ConnectionFactory connectionFactory;
private MessageProcessor processor;
private long backOff;
private boolean stopped = false;
private Executor processPool;
public CustomMessageListener(ConnectionFactory connectionFactory,
long backOff, MessageProcessor processor, Executor processPool) {
this.connectionFactory = connectionFactory;
this.backOff = backOff;
this.processor = processor;
this.processPool = processPool;
}
#Override
public void run() {
while (!stopped) {
listen();
}
}
public void stop() {
this.stopped = true;
}
public void listen() {
Connection c = null;
Session s = null;
MessageConsumer consumer = null;
boolean received = false;
try {
c = connectionFactory.createConnection();
s = c.createSession(false, Session.CLIENT_ACKNOWLEDGE);
consumer = s.createConsumer(...);
Message message = consumer.receive(backOff); // waits maximum backOff ms for a message
if (message != null) {
received = true;
// submit a task to processing pool...
executor.submit(processor.process(message, s, consumer, c));
}
} catch (JMSException ex) {
// log your exception
} finally {
if (!received) {
// close conn, session, consumer
}
}
}
}
public class MessageProcessor {
public Runnable process(Message msg, Session s, MessageConsumer consumer, Connection conn) {
return () - > {
try {
//do your processing
msg.acknowledge(); // done
} catch (JMSException ex) {
// log your exception
} finally {
// close your resources
}
};
}
}
You can call stop() to stop listening for more messages, for a graceful shutdown. Include a queueName in the constructor to listen for a particular queue.
If your session is transacted,then "acknowledgeMode" is ignored anyways..So, just leave your session transacted and use session.rollback and session.commit to commit or rollback your transaction.