I have a consumer application that use Spring Boot JMS to listen message from a queue. This application is connected to an ActiveMQ cluster with failover to have HA. But I have the following problem, when I shutdown one of the broker and the application is processing one the message this message is not dequeue from the queue, and when the application connect to the other broker the message is redelivered. The problem is that the message has been processed the first time and I don't need to process it again.
I've been searching for Acknowledgement modes and I tried to use client mode to force acknowledge before message processing. But I didn't work. Any idea??
I've declared the following beans:
#Bean
public ActiveMQConnectionFactory jmsConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
connectionFactory.setUserName(brokerName);
connectionFactory.setPassword(brokerPassword);
connectionFactory.setTrustAllPackages(true);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(jmsConnectionFactory());
jmsTemplate.setSessionTransacted(false);
jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
jmsTemplate.setDefaultDestinationName(REMOTE_T);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(
#Qualifier("jmsConnectionFactory") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setSessionTransacted(false);
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
configurer.configure(factory, connectionFactory);
return factory;
}
My listener's code is:
#JmsListener(destination = "Consumer.consumer1.VirtualTopic.TopicPrueba", containerFactory="jmsListenerContainerFactory")
public void receiveMessageFromContacts(Message message) {
try {
message.acknowledge();
TextMessage txtMessage = (TextMessage)message;
mensajesConsumer1++;
System.out.println("First Consumer:"+ txtMessage.getText()+ " received:"+mensajesConsumer1);
}catch(JMSException e) {
e.printStackTrace();
}
}
I'm not sure if I've understood correctly the client acknowledge mode.
Please help! :)
Thanks in advance!
Related
I am trying to read a message from Solace. I am able to read message successfully, but suppose while reading/processing the message the app crashes. How can I read that message again? With my below code I am not able to read that message again. Below is my configuration:
#JmsListener(destination = "myqueue", containerFactory = "jmsContainer", concurrency = "5-10")
public void onMessage(Message msg) {
String message;
if (msg instanceof TextMessage) {
message = ((TextMessage) msg).getText();
LOG.info("In here START " + message) ;
Thread.sleep(60000); //I crash my app while thread is sleeping here
LOG.info("In here END " + msg.getJMSDestination() ) ;
}
public class SolaceConfig {
#Bean("solaceJndiTemplate")
public JndiTemplate solaceJndiTemplate() {
JndiTemplate solaceJndiTemplate = new JndiTemplate();
// setting user name /password ommitted for brevity
solaceJndiTemplate.setEnvironment(properties);
return solaceJndiTemplate;
}
#Bean
public JndiObjectFactoryBean solaceConnectionFactory(){
JndiObjectFactoryBean solaceConnectionFactory = new JndiObjectFactoryBean();
solaceConnectionFactory.setJndiTemplate(solaceJndiTemplate());
solaceConnectionFactory.setJndiName(getJndiName());
return solaceConnectionFactory;
}
#Primary
#Bean
public CachingConnectionFactory solaceCachedConnectionFactory(){
CachingConnectionFactory solaceCachedConnectionFactory = new CachingConnectionFactory();
solaceCachedConnectionFactory.setTargetConnectionFactory((ConnectionFactory)solaceConnectionFactory().getObject());
solaceCachedConnectionFactory.setSessionCacheSize(10);
return solaceCachedConnectionFactory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate(solaceCachedConnectionFactory());
jmsTemplate.setDeliveryPersistent(true);
jmsTemplate.setExplicitQosEnabled(true);
return jmsTemplate;
}
#Bean
public DefaultJmsListenerContainerFactory jmsContainer() {
DefaultJmsListenerContainerFactory container = new DefaultJmsListenerContainerFactory();
container.setConnectionFactory(solaceCachedConnectionFactory());
//container.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return container;
}
When using the DMLC, you should enable transactions (set sessionTransacted) so that the acknowledgment is rolled back.
Otherwise, use a SimpleMessageListenerContainer instead.
See the javadocs https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jms/listener/DefaultMessageListenerContainer.html
It is strongly recommended to either set "sessionTransacted" to "true" or specify an external "transactionManager". See the AbstractMessageListenerContainer javadoc for details on acknowledge modes and native transaction options, as well as the 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.
I have configured an ActiveMQ message queue with transactions in a Spring Boot application.
Here is my beans configuration:
#Bean
public DefaultJmsListenerContainerFactory jmsListenerFactory(DefaultJmsListenerContainerFactoryConfigurer configurer,
#Qualifier("jmsTransactionManager") PlatformTransactionManager transactionManager,
JmsErrorHandler errorHandler) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory());
factory.setMessageConverter(messageConverter());
factory.setTransactionManager(transactionManager);
factory.setSessionTransacted(true);
factory.setErrorHandler(errorHandler);
return factory;
}
#Bean
public RedeliveryPolicy redeliveryPolicy() {
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setInitialRedeliveryDelay(20000);
redeliveryPolicy.setRedeliveryDelay(20000);
redeliveryPolicy.setMaximumRedeliveryDelay(20000);
redeliveryPolicy.setBackOffMultiplier(2);
redeliveryPolicy.setUseExponentialBackOff(true);
redeliveryPolicy.setMaximumRedeliveries(-1);
redeliveryPolicy.setDestination(defaultDestination());
return redeliveryPolicy;
}
#Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(getAddress());
connectionFactory.setRedeliveryPolicy(redeliveryPolicy());
return connectionFactory;
}
#Bean(initMethod = "start", destroyMethod = "stop")
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector(getAddress());
PersistenceAdapter persistenceAdapter = new KahaDBPersistenceAdapter();
persistenceAdapter.setDirectory(new File(...));
broker.setPersistenceAdapter(persistenceAdapter);
broker.setBrokerName(getName());
broker.setPersistent(true);
broker.setDataDirectoryFile(new File(...));
broker.setSchedulerSupport(true);
broker.setUseJmx(true);
return broker;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
jmsTemplate.setMessageConverter(messageConverter());
jmsTemplate.setSessionTransacted(true);
jmsTemplate.setSessionAcknowledgeMode(Session.SESSION_TRANSACTED);
jmsTemplate.setDefaultDestination(defaultDestination());
jmsTemplate.setPubSubDomain(false);
return jmsTemplate;
}
Although maximum redeliveries attribute is working as expected (unlimited) the delay setting is not, which is strange.
Any tips on what might be the issue?
EDIT
I found that the issue disappears when I comment out this:
factory.setTransactionManager(transactionManager);
from jmsListenerFactory. I am not sure if I understand the reason though and if it is safe to completely remove it.
I am setting up a durable JMS topic consumer using SpringBoot and activeMQ. I was able to get everything working (successfully running as durable consumer) using the spring boot #JmsListener annotation. But, because I would like to dynamically create listeners, I am trying to create them using the JmsListenerConfiguraion interface instead.
Using the code below the topic consumer is successfully created and consumes messages. But, the problem is that the consumer it creates is not durable. I am setting clientId, setSubscriptionDurable to true, and setting setPubSubDomain to true on the factory. What am I missing?
#Configuration
#EnableJms
public class ListenerConfigurer implements JmsListenerConfigurer {
#Autowired
private List<JmsListenerConfig> listenerConfigs;
#Autowired
private ConnectionFactory connectionFactory;
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
for(JmsListenerConfig jmsListenerConfig : listenerConfigs) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId(jmsListenerConfig.getEndpointName());
endpoint.setDestination(jmsListenerConfig.getEndpointName());
endpoint.setMessageListener(message -> {
TextMessage txtMessage = (TextMessage) message;
try {
jmsListenerConfig.getMessageReceiveHandler().handle(txtMessage.getText());
}catch (JMSException e){
e.printStackTrace();
}
});
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSubscriptionDurable(true);
factory.setPubSubDomain(true);
factory.setClientId(jmsListenerConfig.getClientUid());
DefaultMessageListenerContainer container = factory.createListenerContainer(endpoint);
endpoint.setupListenerContainer(container);
registrar.registerEndpoint(endpoint, factory);
registrar.setContainerFactory(factory);
}
}
}
I figured out my problem. I had to set a subscription name on the endpoint.
endpoint.setSubscription("some-trivial-subscription-name");
I am using CachingConnectionFactory to cache my JMS Broker Connections for IBM MQ. Messages under the local transaction are not rolled back even if I throw an explicit exception. But when I remove the Caching and use just a plain JMS Connection Factory the message is rolled back in case an Exception is thrown.
#Gary Russell specified that we can simply throw an exception to roll back any message in transaction.
Gary's Answer
edit:
This is how is set up my JMS Broker :
#Bean
public IntegrationFlow primaryInitiationListenerFlow() {
return IntegrationFlows.from(Jms
.messageDrivenChannelAdapter(
context.getBean("connection" + environment.getProperty("primaryConnection"), ConnectionFactory.class),
DefaultMessageListenerContainer.class)
.autoStartup(false)
.destination(environment.getProperty("sourceName"))
.configureListenerContainer(listenerContainerSpec -> listenerContainerSpec
.destinationResolver((session, destinationName, pubSubDomain) -> destinationName.toUpperCase().endsWith("TOPIC") ?
session.createTopic(destinationName) : session.createQueue(destinationName))
.subscriptionDurable(false))
.id(environment.getProperty("id") + "PrimaryIn")
.get())
.channel("idEnrichmentChannel")
.get();
}
#Bean
public ConnectionFactory connection301() {
MQConnectionFactory factory = new MQConnectionFactory();
try {
factory.setHostName("xxxxxxx");
factory.setPort(1416);
factory.setQueueManager("xxxxxxx");
factory.setChannel("xxxxxxx");
factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
} catch (JMSException e) {
e.printStackTrace();
}
return factory;
}
Here is my configuration with CachingConnectionFactory :
#Bean
public JmsTemplate template301() {
CachingConnectionFactory cachedConnection = new CachingConnectionFactory();
cachedConnection.setTargetConnectionFactory(connection301());
return new JmsTemplate(cachedConnection);
}
And this is post removal of CachingConnectionFactory :
#Bean
public JmsTemplate template301() {
return new JmsTemplate(connection301());
}
I'm trying to start ActiveMQ from java enabling virtual destinations but I think I have the configuration wrong.
public static void main(String[] args) throws Exception {
// Virtual Destination Interceptor
VirtualTopic virtualTopic = new VirtualTopic();
virtualTopic.setName("VirtualTopic.>");
virtualTopic.setPrefix("Consumer.*.");
VirtualDestinationInterceptor virtualDestinationInterceptor = new VirtualDestinationInterceptor();
virtualDestinationInterceptor.setVirtualDestinations(new VirtualDestination[] {virtualTopic});
// Create and Start the Broker
BrokerService broker = new BrokerService();
broker.setDestinationInterceptors(new DestinationInterceptor[] {virtualDestinationInterceptor});
broker.addConnector("tcp://localhost:61616");
broker.start();
}
This above should enable virtual destinations and allow me to send to a topic VirtualTopic.myTopicName and receive from Consumer.A.VirtualTopic.myTopicName.
(Note this replaces the activemq.xml that I do not have.)
From my code I'm sending with:
jmsTopicTemplate.send("VirtualTopic.myTopicName", session -> session.createTextMessage(jmsEvent));
And receiving with:
#JmsListener(destination = "Consumer.A.VirtualTopic.myTopicName", containerFactory = "jmsTopicListenerContainerFactory")
But I'm not receiving the message.
Through jconsole I see that the message has been enqueued to VirtualTopic.myTopicName but Consumer.A.VirtualTopic.myTopicName has a 0 count on dequeueing.
If changing the #JmsListener to:
#JmsListener(destination = "VirtualTopic.myTopicName", containerFactory = "jmsTopicListenerContainerFactory")
Then I'm receiving 5 messages (as I have 5 consumers).
Does anybody have any suggestion?
It really sounds like a configuration problem to me.
For completeness, those are my jmsTopicTemplate and jmsTopicListenerContainerFactory
#Bean
public JmsTemplate jmsTopicTemplate(#Qualifier("activeMQConnectionFactory") ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
jmsTemplate.setPubSubDomain(true);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory jmsTopicListenerContainerFactory(#Qualifier("activeMQConnectionFactory") ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("4-5");
factory.setPubSubDomain(true);
return factory;
}
Oh! I got the error.
The error is in the #JmsListenerContainerFactory:
It should not anymore listen from a topic as Virtual Destinations are effectively Queues. I needed to remove factory.setPubSubDomain(true); and with the following it works.
#Bean
public JmsListenerContainerFactory jmsTopicListenerContainerFactory(#Qualifier("activeMQConnectionFactory") ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("4-5");
return factory;
}
Furthermore ActiveMQ documentation says that virtual destinations are active by default and it is true.
Therefore the code for starting ActiveMQ can be simplified to:
public static void main(String[] args) throws Exception {
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.start();
}