Spring Integration DefaultMessageListenerContainer Transacted Message Not Rolledback - spring-integration

Expect the below configuration to rollback the JMS message to the backout queue, but it does not. The failed message is reaching the application's custom ErrorChannel. Please suggest why is the rollback not happening. Thanks
#Bean
public DefaultMessageListenerContainer mqAdapterListenerContainer(final ConnectionFactory mqCachingConnectionFactory) {
var defaultMessageListenerContainer = new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setConnectionFactory(mqCachingConnectionFactory);
defaultMessageListenerContainer.setSessionTransacted(true);
return defaultMessageListenerContainer;
}
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(mqAdapterListenerContainer)
.errorChannel(appErrorChannel))
.enrichHeaders(headerEnricherSpec -> headerEnricherSpec.errorChannel(appErrorChannel, true))

That JMS transaction can rollback only if listener throws exception, but since you do error handling via that appErrorChannel, there is no exception thrown to that JMS container.
So, you may still have a .errorChannel(appErrorChannel) and that error handling, but you need to re-throw some exception to let JMS container catch it rollback transaction respectively.

Related

Spring integration aws (sqs) to trigger spring integration flow

I have to listen a queue using spring integration flow and intgeration sqs. Once message is received from queue it should trigger a integration flow. Below is the things which I am trying but everythings fine in but afater receiving test it is not triggering any Integration flow. Please let me know where I am doing wrong:
UPDATED as per comment from Artem
Adapter for SQS.
#Bean
public MessageProducerSupport sqsMessageDrivenChannelAdapter() {
SqsMessageDrivenChannelAdapter adapter = new SqsMessageDrivenChannelAdapter(amazonSQSAsync, "Main");
adapter.setOutputChannel(inputChannel());
adapter.setAutoStartup(true);
adapter.setMessageDeletionPolicy(SqsMessageDeletionPolicy.NEVER);
adapter.setMaxNumberOfMessages(10);
adapter.setVisibilityTimeout(5);
return adapter;
}
Queue configured:
#Bean
public MessageChannel inputChannel() {
return new DirectChannel();
}
Now the main integration flow trigger point:
#Bean
public IntegrationFlow inbound() {
return IntegrationFlows.from("inputChannel").transform(i -> "TEST_FLOW").get();
}
}
Appreciate any type of help.
The sqsMessageDrivenChannelAdapter() must be declared as a #Bean
The inbound() must be declared as a #Bean
This one fully does not make sense IntegrationFlows.from(MessageChannels.queue()). What is the point to start the flow from anonymous channel? Who and how is going to produce messages to that channel?
Make yourself familiar with different channels: https://docs.spring.io/spring-integration/docs/current/reference/html/core.html#channel-implementations
Pay attention that QueueChannel must be consumed via polling endpoint.
Right, there is a default poller auto-configured by Spring Boot, but it is based on a single thread in the TaskScheduler and has a polling period as 10 millis.
I wouldn't recommend to hand off SQS messages to the QueueChannel: when consumer fails, you lose the data. It is better to process those messages in the consumer thread.
Otherwise your intention is not clear in the provided code.
Can you, please, share with us what error you get or anything else?
You also can turn on DEBUG logging level for org.springframework.integration to see how your messages are processed.

Spring Cloud Stream: how to republish to dead letter queue and also throw exception

I'm migrating a project that uses Spring AMQP to a project that uses Spring Cloud Stream with RabbitMQ.
In my old project, when some exception occurred while processing a message using #RabbitListener, that exception was thrown. If there was a dead letter queue binded, exception was still thrown (only once if there were retries, the last one I guess). This was very helpful for logging purposes.
In Spring Cloud there is a dead letter queue mechanism for #StreamListener if you define the properties:
spring.cloud.stream.bindings.input1.destination=dest1
spring.cloud.stream.rabbit.bindings.input1.consumer.auto-bind-dlq=true
spring.cloud.stream.rabbit.bindings.input1.consumer.republishToDlq=true
But if you have a method like this (is just an example):
#StreamListener("input1")
public void process(String message){
System.out.println("Trying...");
throw new RuntimeException();
}
Logs are:
Trying...
Trying...
Trying...
(end of log, no exception thrown)
Is there any way to throw the exception (only in the last retry)?
Thanks!
See the documentation about consumer properties.
Set ...consumer.max-attempts=1 to disable retry.
You can handle the exception, log it and then throw AmqpRejectAndDontRequeueException. This will send the message to dead letter queue
You are under #StreamListener where would you expect the exception to go? who is catch it?
you can do it something like that:
#StreamListener("input1")
public void process(String message){
try {
System.out.println("Trying...");
throw new RuntimeException();
// or the actual code that handle the message
} catch (RuntimeException re) {
// handle the exception, logging etc.
throw re
}
}

How to turn off automatic retry in Spring Integration Jms.inboundGateway

I am using
spring 4.2.4.RELEASE and
spring-integration-java-dsl:1.1.2.RELEASE
I have Jms.inboundGateway and some transformer. When transformer fails to transform a message, inbound gateway retries again and again. But I want to stop the entire flow in case of exception. Is it possible to do?
This is my flow configuration:
#Bean
public IntegrationFlow nonStop {
return IntegrationFlows
.from(Jms.inboundGateway(emsConnectionFactory)
.destination(myDestination)
.configureListenerContainer(spec -> spec
.sessionTransacted(true)
.subscriptionDurable(true)
.durableSubscriptionName(durableSubscriptionName)
.errorHandler((ErrorHandler) t -> {
t.printStackTrace();
throw new RuntimeException(t);
}))
.errorChannel(errorChannel)
.autoStartup(true)
.id(myNonStoppableFlow))
.filter(...)
.transform(...)
.handle(Jms.outboundAdapter(emsConnectionFactory)
.destination(myOnotherDestination))
.get();
}
One interesting notice. When errorHandler swallows an exception inbound gateway retires without any delay. When it throws Runtime exception there is delay about 5 seconds (which is not configured anywhere).
That retry in JMS is called like redelivery: http://www.javaworld.com/article/2074123/java-web-development/transaction-and-redelivery-in-jms.html.
So, any downstream exception makes for your message rollback and redelivery, which can be configured on Broker for the destination.
Actually if your .errorChannel(errorChannel) flow doesn't re-throw the exception, it will be treated as a successful handling and as commit.
From other side you should reconsider if you really need there a Jms.inboundGateway(). Because this one requires a reply, which doesn't look possible with your Jms.outboundAdapter() in the end.

How to rollback message taken from IBM MQ in spring integration

I have a spring integration flow like this:
1) message-driven-channel-adapter ->
1.1) output-channel connected to -> service-activator -> outbound-channel-adapter (for sending response)
1.2) error-channel connected to -> exception-type-router
1.2.1) message is sent to different queues depending on the exception type using outbound-channel-adapter
I have set acknowledge="transacted" in message-driven-channel-adapter. I want to introduce rollback for a specific type of exception, after error-channel.
First, I tried to connect the exception-type-router output to service-activator. But i get exception:
Code:
<service-activator id="rollBackActivator" input-channel="RollBackChannel"
ref="errorTransformer" method="rollBackMessage"/>
public void rollBackMessage(MessagingException message){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
System.out.println("Message rolled back:"+TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());
}
Exception:
org.springframework.messaging.MessageHandlingException: org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
Then, I tried with outbound-channel-adapter expression , But got another exception again
Code:
<outbound-channel-adapter id="rollbackOut" channel="RollBackChannel"
expression="T(org.springframework.transaction.interceptor.TransactionAspectSupport).currentTransactionStatus().setRollbackOnly()"/>
Exception:
org.springframework.messaging.MessageHandlingException: Expression evaluation failed: T(org.springframework.transaction.interceptor.TransactionAspectSupport).currentTransactionStatus().setRollbackOnly()
Please advise to implement rollback in this scenario.
The container uses local transactions on the session by default. There's no AOP involved. Simply throw an exception and the container will roll back the message.

Spring Integration Async Gateway with error-channel causes threads to park

I have an async gateway with an async executor and an error-channel defined. Also there is a service activator that handles exceptions.
<int:gateway id="myGateway"
service-interface="me.myGateway"
default-request-channel="requestChannel"
async-executor="taskScheduler"
error-channel="errorChannel"/>
<int:service-activator ref="errorChannelMessageHandler" method="handleFailedInvocation"
input-channel="errorChannel"/>
public interface MyGateway {
Future<Object> send(Message message);
}
#Component
public class ErrorChannelMessageHandler {
#ServiceActivator
public Object handleFailedInvocation(MessagingException exception) {
// do some stuff here
return null;
}
}
When an exception is being thrown, the thread blocks, waiting for a reply on the temporary reply channel the gateway has created.
How can I prevent the threads from blocking and when exceptions are thrown to have the error-channel service activator handle the message and perform some custom logic?
Later edit:
I am using SI 3.0.3.RELEASE
When an error channel is defined for the gateway, if exception occurs it will call this line (no 250 from MessagingGatewaySupport) errorFlowReply = this.messagingTemplate.sendAndReceive(this.errorChannel, errorMessage), which will do a blocking receive on a newly created temporary reply channel(lines 352-367 from MessagingTemplate). Is my service activator for the error-channel responsible for publishing a message on this newly create temporary channel, so the thread won't block on the receive?
Remove the error-channel so that the original exception is added to the Future so that the get() will propagate it.
If you want to do something on the error flow, throw an exception from the error flow so it's available in the Future, or set reply-timeout to 0. However, your Future will never get a value or exception in that case.

Resources