RabbitMQ append exception details for messages going to DLQ - java

I have simple RabbitMQ configuration in Spring (not SpringBoot):
#Bean
public Queue queueTest() {
return QueueBuilder.durable("test")
.withArgument("x-dead-letter-exchange", "myexchange")
.withArgument("x-dead-letter-routing-key", "mykey")
.build();
}
#Bean
public Queue queueTestDlq() {
return new Queue("test.dlq", true);
}
#Bean
public Binding bindingTest(DirectExchange directExchange, Queue queueTest) {
return BindingBuilder
.bind(queueTest)
.to(directExchange)
.with("testkey");
}
#Bean
public Binding bindingTestDlq(DirectExchange directExchange, Queue queueTestDlq) {
return BindingBuilder
.bind(queueTestDlq)
.to(directExchange)
.with("testdlqkey");
}
Processing works properly and message is moved to DLQ in case of exception.
How can I append exception details (eg. message, stacktrace) to message headers for message going to DLQ?

Related

DefaultJmsListnerContainerFactory concurrency not working

This is my JMS configuration:
#EnableJms
#Configuration
public class VmsJmsConfig implements JmsListenerConfigurer {
#Value("${spring.activemq.broker-url}")
String brokerUrl;
#Value("${spring.activemq.ssl.trustStorePath}")
String trustStorePath;
#Value("${spring.activemq.ssl.trustStorePass}")
String trustStorePass;
#Bean
public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory(ConnectionFactory conFactory) {
DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
defaultJmsListenerContainerFactory.setConnectionFactory(conFactory);
defaultJmsListenerContainerFactory.setConcurrency("10-20");
return defaultJmsListenerContainerFactory;
}
#Bean("conFactory")
public ConnectionFactory activeMQSslConnectionFactory() throws Exception {
ActiveMQSslConnectionFactory activeMQSslConnectionFactory = new ActiveMQSslConnectionFactory(brokerUrl);
activeMQSslConnectionFactory.setTrustStore(trustStorePath);
activeMQSslConnectionFactory.setTrustStorePassword(trustStorePass);
return activeMQSslConnectionFactory;
}
#Bean
public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(messageConverter());
return factory;
}
#Bean
public MessageConverter messageConverter() {
return new MappingJackson2MessageConverter();
}
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
}
}
My app is listening to an ActiveMQ queue. It consumes a message, transforms it, sends a downstream request, waits for the response, and sends that response to another queue.
I want it to consume multiple messages at the same time and process them in parallel, but no matter how many consumer I set in setConcurrency() it only consumes 1 message at a time, and there are more than 1000 messages pending in queue.
I tried changing the concurency, but no luck. But when I comment downstream call, it consumes 10-20 messages at a time, I couldn't find reason for that.

Redeliver message in JMS

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.

Giving incorrect(non-existing) queue name while publishing in RabbitMQ

I'm using AyncRabbitTemplate for publishing messages. Giving an incorrect(non-existing) queue name while publishing - it drops the message silently.
I have tried enabling "confirm" and "mandate" on the AyncRabbitTemplate and added the required callback methods as below:
#Bean
AsyncRabbitTemplate template() {
RabbitTemplate rabbit = rabbitTemplate();
rabbit.setChannelTransacted(true); //to throw error when channel shuts down in case of incorrect exchange names
AsyncRabbitTemplate asyncRabbitTemplate = new AsyncRabbitTemplate(rabbit, rpcReplyMessageListenerContainer(connectionFactory()));
asyncRabbitTemplate.setEnableConfirms(true);
asyncRabbitTemplate.setMandatory(true); //if the message cannot be delivered to a queue an AmqpMessageReturnedException will be thrown
return asyncRabbitTemplate;
}
#Bean
public SimpleMessageListenerContainer rpcReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueueNames(Constants.REPLY_QUEUE);
simpleMessageListenerContainer.setTaskExecutor(Executors.newCachedThreadPool());
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return simpleMessageListenerContainer;
}
#Bean
public RabbitTemplate rabbitTemplate() {
return new RabbitTemplate(connectionFactory());
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
return connectionFactory;
}
And the callback methods as:
RabbitConverterFuture<String> future = this.asyncRabbitTemplate.convertSendAndReceive("",Constants.SNS_QUEUE, "This is the request message ",new MessagePostProcessor() {
#Override
public Message postProcessMessage(Message message) {
message.getMessageProperties().setTimestamp(new Date());
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
return message;
}
});
ListenableFuture<Boolean> future2 = future.getConfirm();
future2.addCallback(new ListenableFutureCallback<Boolean>() {
#Override
public void onSuccess(Boolean result) {
System.out.println("Publish Result " + result);
}
#Override
public void onFailure(Throwable ex) {
System.out.println("Publish Failed: " + ex);
}
});
As discussed in the documentation you have to enable returned messages on the connection factory.
The send will not throw an exception but undeliverable messages will be returned to the ReturnCallback (if mandatory is true).
Confirms are not sent for undeliverable messages. A negative confirmation is only received if there is some kind of problem in the broker; they are rare.

Re downloading of message when using spring integration inbound channel adapter

I am reading mails using spring mail inbound channel adapter once message is read i am performing some db operations in service activator of corresponding channel. My requirement is if any db operation fails adapter should read same message again.
Mail configuration :
#Bean
public DirectChannel inputChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow pop3MailFlow() {
String url = "[url]";
return IntegrationFlows
.from(Mail.pop3InboundAdapter(url)
.javaMailProperties(p -> p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory")),e -> e.autoStartup(true)
.poller(Pollers.fixedDelay(2000).transactionSynchronizationFactory(transactionSynchronizationFactory())))
.channel(inputChannel())
.handle(inboundEmailProcessor(),"messageProcess")
.get();
}
#Bean
public InboundEmailProcessor inboundEmailProcessor() {
return new InboundEmailProcessor();
}
#Bean
public TransactionSynchronizationFactory transactionSynchronizationFactory() {
TransactionSynchronizationFactory synchronizationFactory = new DefaultTransactionSynchronizationFactory(expressionEvaluatingTransactionSynchronizationProcessor());
return synchronizationFactory;
}
#Bean
public ExpressionEvaluatingTransactionSynchronizationProcessor expressionEvaluatingTransactionSynchronizationProcessor() {
ExpressionEvaluatingTransactionSynchronizationProcessor processor = new ExpressionEvaluatingTransactionSynchronizationProcessor();
ExpressionParser parser = new SpelExpressionParser();
processor.setAfterRollbackExpression(parser.parseExpression("new com.muraai.ex.config.Exp().process(payload)"));
return processor;
}
public class InboundEmailProcessor {
#Autowired
AttachmentsRepository attachmentsRepository;
#Transactional(rollbackFor = Exception.class)
public void messageProcess() {
// some db operations
// if it fails the same message should be read again
}
}
I thought this would work but its not working. Is there any way to achieve my requirement
public class Exp {
public void process(MimeMessage message) throws MessagingException {
message.setFlag(Flags.Flag.SEEN, false);
}
}
You need IMAP for that; with POP3, the server always marks them read.
You can add a spring-retry interceptor advice to the poller's advice chain and/or send the failed message to an error channel.
The retry advice can be configured for number of retries, back off policy etc.

SQS message acknowledgement

My Sring Boot application listens Amazon SQS queue. Right now I need to implement correct message acknowledgement - I need to receive a message, do some business logic a only after that in case of success I need to ack the message(delete the message from the queue). For example, in case of error in my business logic the message must be re-enqueued.
This is my SQS config:
/**
* AWS Credentials Bean
*/
#Bean
public AWSCredentials awsCredentials() {
return new BasicAWSCredentials(accessKey, secretAccessKey);
}
/**
* AWS Client Bean
*/
#Bean
public AmazonSQS amazonSQSAsyncClient() {
AmazonSQS sqsClient = new AmazonSQSClient(awsCredentials());
sqsClient.setRegion(Region.getRegion(Regions.US_EAST_1));
return sqsClient;
}
/**
* AWS Connection Factory
*/
#Bean
public SQSConnectionFactory connectionFactory() {
SQSConnectionFactory.Builder factoryBuilder = new SQSConnectionFactory.Builder(
Region.getRegion(Regions.US_EAST_1));
factoryBuilder.setAwsCredentialsProvider(new AWSCredentialsProvider() {
#Override
public AWSCredentials getCredentials() {
return awsCredentials();
}
#Override
public void refresh() {
}
});
return factoryBuilder.build();
}
/**
* Registering QueueListener for queueName
*/
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory());
messageListenerContainer.setMessageListener(new MessageListenerAdapter(new MyQueueListener()));
messageListenerContainer.setDestinationName(queueName);
return messageListenerContainer;
}
My queue listener:
public class MyQueueListener {
public void handleMessage(String messageContent) throws JMSException {
//do some job
//TODO: ack the message
}
}
Right now I don't know how to ack the message from my listener.
Normally DefaultMessageListenerContainer acknowledges the message before or after execution of handleMessage automatically. So you don't need to do anything.
But recommended with DefaultMessageListenerContainer is to use transactions instead of client ack mode. Not sure if Amazon SQS has such option.

Categories