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.
Related
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.
I am polling for files for a service-activator, using a PseudoTransactionManager to move them into processed/failed directories.
If/when the move fails, I would like to log this, including the file name.
As the information being passed around the flows is the Message object, I tried enriching the file name onto its header, but as we make copies of it for each step, this won't work unless I can move the header-enricher between the inbound channel adapter and transaction manager.
In simplified form the main flow I now have is this:
inbound-channel-adapter -> a) header-enricher -> service-activator
Because I want the files moved to a processed or failed directory, there is a second flow:
inbound-channel-adapter -> b) pseudo transaction-manager -> logging-channel-adapter (in case of problems moving the processed file).
I think this follows because the transaction manager definition is nested within the channel adapter definition in the xml.
How can I pass this information through the example setup here to a logging channel adapter?
The transaction stuff only has access to the original message. You can add an error-channel to the poller; the default error channel (errorChannel) is a pub/sub channel and has a logging channel adapter subscribed to it.
When an exception occurs, an ErrorMessage is sent to the error channel (if configured); the payload is a MessagingException with cause and failedMessage properties. The failedMessage is the message at the point the failure occurred.
The default error flow will simply log the message so your "transaction" will "commit".
Instead, you need a custom error flow; log what you want and then re-throw the cause and your "transaction" will "rollback".
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.
I have a Spring Integration flow which sends a message out via a JMS Outbound Gateway which is configured to have a receive timeout of 45 seconds. I am trying to test the receive timeout period by sending a message out in a setup where the message is never consumed on the other side (therefore a response doesn't come back). However, when I run the test, the message is placed in the outbound queue but, the Outbound Gateway's receive timeout never occurs (after 45 seconds). Any ideas what reasons there could be for this happening (not happening)?
My stack is:
o.s.i:spring-integration-java-dsl:1.0.0.M3
o.s.i:spring-integration-jms:4.0.4.RELEASE
My IntegrationgConfig.class:
#Configuration
#EnableIntegration
public class IntegrationConfig {
...
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows
.from("test.request.ch")
.handle(Jms.outboundGateway(connectionFactory)
.receiveTimeout(45000)
.requestDestination("REQUEST_QUEUE")
.replyDestination("RESPONSE_QUEUE")
.correlationKey("JMSCorrelationID"))
.handle("testService",
"testMethod")
.channel("test.response.ch").get();
}
...
}
In terms of JMS configuration, the connection factory used is a standard CachingConnectionFactory which targets an MQConnectionFactory.
Thanks in advance for any help on this.
PM
--- UPDATE ---
I have turned on debugging and I can see that when the timeout occurs the following message is logged:
AbstractReplyProducingMessageHandler - handler 'org.springframework.integration.jms.JmsOutboundGateway#0' produced no reply for request Message: [Payload byte[835]][...]
Just need to find out how to capture this event in the flow?
--- UPDATE 2 ---
The message being sent out has an ERROR_CHANNEL header set on it to which I would expect the timeout exception to be routed to - but this routing does not happen?
Is it possible that the CachingConnectionFactory is handling the exception and not passing it back to the flow?
To make it working you need to add the second Lambda to the .handle() with Jms:
.handle(Jms.outboundGateway(connectionFactory)
.receiveTimeout(45000)
.requestDestination("REQUEST_QUEUE")
.replyDestination("RESPONSE_QUEUE")
.correlationKey("JMSCorrelationID"),
e -> e.requiresReply(true))
By default AbstractReplyProducingMessageHandler doesn't require reply even if receiveTimeout is exhausted, and we see that by logs shown by you.
However, I see that we should revise all MessageHandlerSpecs, because XML support changes the requires-reply for some components to true by default.
Feel free to raise a JIRA issue on the matter and we'll address it soon, because the GA release for Java DSL is planned over a week or two: https://spring.io/blog/2014/10/31/spring-integration-java-dsl-1-0-rc1-released
Thank you for the attention to this stuff!
1) Server sends a message to client.
2) Inbound channel adapter is configured to wait for "MANUAL" acknowledge mode operation from consumer
3) "TaskBundlereceiver" bean is implementing "ChannelAwareMessageListener" and in the implementation method, I am performing message acknowledgement.
I don't see "TaskBundlereceiver" getting executed. Am I missing something ?
Below is the configuration details of the steps that I have explained.
Appreciate your inputs.
#Override
public void onMessage(org.springframework.amqp.core.Message message, Channel channel) throws Exception
{
logger.debug("In onMessage method of the channel aware listener. message =["+message.getBody().toString()+"]");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}
XML Configuration :
<!-- Channel that receives the task bundle from the server for execution -->
<int:channel id="fromKServerChannel"/>
<int-amqp:inbound-channel-adapter id="taskBundleReceiverAdapter"
channel="fromKServerChannel"
error-channel="taskBundleErrorChannel"
acknowledge-mode="MANUAL"
expose-listener-channel="true"
queue-names="kanga_task_queue"
connection-factory="connectionFactory"
concurrent-consumers="20"/>
<int:chain input-channel="fromKServerChannel" output-channel="nullChannel">
<int:service-activator ref="taskBundleReceiver" method="onMessage"/>
<int:service-activator ref="taskBundleExecutor" method="executeBundle"/>
</int:chain>
It doesn't work that way; the listener is the adapter, not the service invoked via the service-activator. The adapter currently does not support passing the channel to the client for manual acks. The expose-listener-channel attribute is for use when using transactions, so a down-stack rabbit template can participate in the transaction.
Why do you want MANUAL acks? AUTO (default) means the ack will be done automatically by the container when the thread returns normally; if your service throws an exception, the message will be nacked.
So, that's how to control the ack.
If you really want to use MANUAL acks, you'll have to use a <rabbit:listener-container/> to invoke your taskBundleReceiver directly. It could then send a message to the executor using a messaging gateway.