Spring integration and JDBC in single transaction - spring

So the setup is following:
<tx:advice id="txAdvice2" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="ListenerExecutionFailedException"/>
</tx:attributes>
</tx:advice>
<int-amqp:inbound-channel-adapter channel="input-channel" queue-names="probni" message-converter="jsonMessageConverter"
channel-transacted="true"
advice-chain="txAdvice2" />
<int:chain input-channel="input-channel" output-channel="output-channel">
<int:service-activator ref="h1Handler" method="handle" />
<int:service-activator ref="h2Handler" method="handle" />
<int:service-activator ref="h3Handler" method="handle" />
<int:splitter />
</int:chain>
<int-amqp:outbound-channel-adapter channel="output-channel" exchange-name="outputit" amqp-template="rabbitTemplate" />
If during the execution of this thread (since all this chain amqpIN-process-amqpOUT shold execute in single thread) I throw ListenerExecutionFailedException, dataSourceTransactionManager will do commit, but amqp will also requeue the message because the exception is propagated.
How can I tell rabbit to ACK the message as successful in this case?
Also, I saw that I had to put in no-rollback-for attribute actual exception class, since my inner-exception is only stored in "cause" attribute which is not inspected by the RuleBasedTransactionAttribute.
One more thing, if I make config like this:
<int-amqp:inbound-channel-adapter channel="input-channel" queue-names="probni" message-converter="jsonMessageConverter"
channel-transacted="true"
transaction-manager="dataSourceTransactionManager"
transaction-attribute="transactionAttribute" />
transactionAttribute which is RuleBasedTransactionAttribute is not considered at all and dataSourceTransactionManager is always rollbacked even if I have no-rollback-for set correctly.
Thanks!

You can add a custom ErrorHandler to the listener container (you have to configure the container externally and provide a reference in the container attribute).
The default error handler is a ConditionalRejectingErrorHandler with a DefaultExceptionStrategy that considers certain LEFE cause exceptions as fatal:
private boolean isCauseFatal(Throwable cause) {
return cause instanceof MessageConversionException
|| cause instanceof org.springframework.messaging.converter.MessageConversionException
|| cause instanceof MethodArgumentNotValidException
|| cause instanceof MethodArgumentTypeMismatchException
|| cause instanceof NoSuchMethodException
|| cause instanceof ClassCastException
|| isUserCauseFatal(cause);
}
Starting with version 1.6.4 you can subclass the default DefaultExceptionStrategy and add your cause(s) to isUserCauseFatal().
Before 1.6.4 you had to provide your own FatalExceptionStrategy (or error handler implementation).
For fatal causes, the handler throws a AmqpRejectAndDontRequeueException which tells the container to nack (and not requeue) the message.
EDIT
By the way, there is no need for you to wrap the exception, the container will do that for you...
protected Exception wrapToListenerExecutionFailedExceptionIfNeeded(Exception e, Message message) {
if (!(e instanceof ListenerExecutionFailedException)) {
// Wrap exception to ListenerExecutionFailedException.
return new ListenerExecutionFailedException("Listener threw exception", e, message);
}
return e;
}
EDIT2
My mistake, the ErrorHandler can be specified using the error-handler attribute.
EDIT3
Alternatively, just throw an AmqpRejectAndDontRequeueException (which will be wrapped in the LEFE).

Related

Spring Integration JMS with JTA rollback although exception is handled in ErrorHandler

I am using Spring Integration with JTA support through Atomikos and JMS bound to different Webshpere MQ and a Oracle Database.
Obviosly for others it seems to be the same thread as in Spring Integration JMS with JTA rollback when message goes to errorChannel but it isn't not at all.
The flow is the following:
message-driven-channel-adapter receive the message within transaction
some transformations
ServiceActivator with deeper business logic
DataBase Update
Everything works really fine expect for one szenario:
If an unchecked exception occurs (maybe due to inkonsistent data which shouldn't be) within the ServiceActivator the message is rethrown and handled within an ErrorHandler (via ErrorChannel).
There - in some cases - the orgininal incoming message should be send to a DeadLetter Queue (Webshere MQ). This is done with outbound-channel-adapter.
See enclosed configuration:
<jms:message-driven-channel-adapter id="midxMessageDrivenAdapter"
channel="midxReplyChannel"
acknowledge="auto"
transaction-manager="transactionManager"
connection-factory="connectionFactory"
concurrent-consumers="1"
error-channel="errorChannel"
max-concurrent-consumers="${cmab.integration.midx.max.consumer}"
idle-task-execution-limit="${cmab.integration.midx.idleTaskExecutionLimit}"
receive-timeout="${cmab.integration.midx.receiveTimeout}"
destination="midxReplyQueue"/>
................
<int:service-activator input-channel="midxReplyProcessChannel" ref="processMidxReplyDbWriter" />
<int:service-activator input-channel="errorChannel" ref="inputErrorHandler" />
<jms:outbound-channel-adapter id="deadLetterOutboundChannelAdapter"
channel="errorDlChannel" destination="deadLetterQueue"
delivery-persistent="true" connection-factory="nonXAConnectionFactory"/>
Some important hints:
message-driven-channel-adapter:
connectionFactory is a MQXAQueueConnectionFactory wihtin an AtomikosConnectionFactoryBean
transaction-manager is Spring JtaTransactionManager
outbound-channel-adapter:
connection-factory is a nonXAConnectionFactory.
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
.....
cachingConnectionFactory.setTargetConnectionFactory(mqQueueConnectionFactory);
return cachingConnectionFactory;
And now the error which I observed:
Although I handled the unchecked exception in my ErrorHandler (ref="inputErrorHandler"; send the message to DeadLetter-Queue) an rollback is initiated and the message-driven-channel-adapter receives the message again and again.
Curious is the fact that indeed the message is delivered to the DeadLetterQueue via the outbound-channel-adapter. The destination (deadLetterQueue) contains the failed messages.
Question: What I'm doing wrong? The failed original incoming message is rolled back although I handled the exception in my ErrorHandler.
Any help really appreciate. Many thanks in advance.
concerning to my comment see enclose code of my InputErrorHandler:
if (throwable instanceof MessageHandlingException) {
MessageHandlingException messageHandlingException = (MessageHandlingException) throwable;
if (messageHandlingException.getCause() != null
&& (messageHandlingException.getCause() instanceof CmabProcessDbException
|| messageHandlingException.getCause() instanceof CmabReplyFormatException)) {
String appMessage = ((CmabException) messageHandlingException.getCause()).getAppMessagesAsString();
LOGGER.error(String.format("cmab rollback exception occured: %s", appMessage));
LOGGER.error("******** initiate rollback *********");
throw throwable.getCause();
} else {
ErrorDto payload = fillMessagePayload(messageHandlingException, throwableClassName);
sendMessageToTargetQueue(payload);
}
As I mentioned the "business" ServiceActivator throws an unchecked exception so in this case the ELSE-Statements are calling.
Within that I build up a Message with MessagBuilder and sends it ti the errorDlChannel (s. above the outbound-channel-adapter!).
private void sendMessageToDeadLetterQueue(Message<?> message, String description, String cause) {
.......
Message<?> mb =
MessageBuilder.withPayload(message.getPayload())
.copyHeaders(message.getHeaders())
.setHeaderIfAbsent("senderObject", this.getClass().getName())
.setHeaderIfAbsent("errorText", description)
.setHeaderIfAbsent("errorCause", errorCause).build();
errorDlChannel.send(mb);
}
That's all. This is for this case the last statement. There is nothing anything else in the main method of my ErrorHandler. No rethrow or other stuff.
So this is the reason of my confusion. For me the exception is handled by sending it to the errorDlChannel (outbound-channel-adapter -> DeadLetterQueue).
I saw the message on the DeadLetter Queue but nevertheless a jta rollback occurs...and IMO this shouldn't bee.

RabbitMQ listener stops listening messages when MessageListener throws exception

I am using Spring AMQP for processing the messages in RabbitMQ. Below is the issue:
1. (say) there are 3 messages in ready state inside RabbitMQ
2. First one is picked up by MessageListener and starts processing. (say) It ends up throwing an exception
3. In this case, the container remains up but the remaining 2 messages are not processed until i restart the container. Also the first messages stays in unacknowledged state.
Is it the expected behavior? If not, how to make sure that other 2 messages will be processed irrespective first one failed processing?
MQ configuraion:
<rabbit:connection-factory id="connectionFactory" host="localhost" username="guest" password="guest" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:listener-container
connection-factory="connectionFactory"
concurrency="1"
acknowledge="auto">
<rabbit:listener queue-names="testQueue" ref="myProcessorListener " />
</rabbit:listener-container>
MessageListener class:
public class MyProcessorListener implements MessageListener{
....
#Override
public void onMessage(Message message) {
try{
...Some logic...
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
The message is redelivered over and over again; in order to reject it (and discard or route to a dead letter queue), you need to throw AmqpRejectAndDontRequeueException or set the container's requeue-rejected property to false. When configuring with Java it's defaultRequeueRejected.
You can also use a custom error handler.
This is all explained in the reference manual.

Spring poller roll back on exception after a particular retry count

My poller fetches data from DB and pass it to a service activator.
If any exception happens in the service activator method,i should roll back the data fetched to its previous state and should again send the same data to the service activater only for a specific retry-count(say 3). **Is it possible to do this in a xml configuration. ? For details i will share the poller configurations and the service activator.
poller.xml
<int-jdbc:inbound-channel-adapter id="datachannel"
query="select loyalty_id,process_id,mobile_uid from TBL_RECEIPT where r_cre_time
=(select min(r_cre_time) from TBL_RECEIPT where receipt_status=0)"
data-source="dataSource" max-rows-per-poll="1"
update="update TBL_RECEIPT set receipt_status=11 where process_id in (:process_id)">
<int:poller fixed-rate="5000">
<int:transactional/>
</int:poller>
</int-jdbc:inbound-channel-adapter>
<int:channel id="errors">
<int:queue/>
</int:channel>
<bean id="poller" class="main.java.com.as.poller.PollerService" />
<int:channel id="executerchannel">
<int:dispatcher task-executor="taskExecutor" />
</int:channel>
<task:executor id="taskExecutor" pool-size="2" />
<int:service-activator input-channel="datachannel"
output-channel="executerchannel" ref="poller" method="getRecordFromPoller">
<int:request-handler-advice-chain>
<int:retry-advice recovery-channel="errors" />
</int:request-handler-advice-chain>
</int:service-activator>
<int:service-activator input-channel="executerchannel"
ref="poller" method="getDataFromExecuterChannel">
</int:service-activator>
service activator method
#SuppressWarnings({ "unchecked", "rawtypes" })
#ServiceActivator
public void processMessage(Message message) throws IOException {
int capLimit = Integer.parseInt(env.getProperty("capping_limit"));
List<Map<String, Object>> rows = (List<Map<String, Object>>) message
.getPayload();
for (Map<String, Object> row : rows) {
String loyaltyId = (String) row.get("loyalty_id");
String processId = (String) row.get("process_id");
String xid=(String)row.get("mobile_uid");
i have heard about int:transactional being used in poller configuration.But when i added it,its taking the same record even after successful transaction.(means its getting rolled back everytime).
Can anyone please help me on this ?
You can add a retry request-handler-advice to the service activator.
To make retry stateful (throw the exception so the transaction will rollback) you need to provide a RetryStateGenerator. Otherwise the thread will simply be suspended during the retries.
Regardless of using stateful or stateless retry, you really should use a transactional poller so that the update is only applied after success.
means its getting rolled back everytime
The transactional poller will only rollback if an exception is thrown. So if you're seeing the same row, something must be failing.
Turn on DEBUG logging for all of org.springframework to follow the message flow and transaction activity.

Spring integration chain error handling

Need help in error handling in a chain during splitter and aggregator flow for a synchronous channel.
Below is the Use case and it will be synchronous channel. So in the chain there will be a set of service activator to perform the business logic. Now if there is any exception in the service activator present in the chain, I want that to be handled in the chain itself and continue with the other splitted messages.
Inorder to do that, I have tried adding header enricher for error handler in the chain.But did not work. Any suggestion.
Object1 contains List < Object2 >
Flow:
List < Object1 > --> Splitter1 (for Object1) --> Splitter2 (for Object2) --> Chain --> Aggregator --> Aggregator
Code
<int:chain input-channel="ch3" output-channel="ch10" >
<int:header-enricher>
<int:error-channel ref="exception1" ></int:error-channel>
</int:header-enricher>
<int:service-activator ref="myService" method="method1"></int:service-activator>
<int:service-activator ref="myService" method="method2"></int:service-activator>
<int:service-activator ref="myService" method="method3"></int:service-activator>
<int:service-activator ref="myService" method="method4"></int:service-activator>
</int:chain>
<!-- Exception channel for chain and the output should go to the chain output channel -->
<int:chain input-channel="exception1" output-channel="ch10" >
<int:service-activator ref="exp" method="myException"></int:service-activator>
</int:chain>
Unfortunately it doesn't work that way. The error-channel header is for the asynchronous cases, too. It allows to override the default behavior in the MessagePublishingErrorHandler for PollableChannel and channels with Executor. In other words when we really can't do try...catch if talk in raw Java words.
So, to fix your requirements you really should rely on the try...catch function for that particular <service-activator>. It is called like ExpressionEvaluatingRequestHandlerAdvice and must be configured as on the <request-handler-advice-chain>.
In your case you should configure that Advice like:
<bean class="ExpressionEvaluatingRequestHandlerAdvice">
<property name="trapException" value="true"/>
<property name="onFailureExpression" value="#exception"/>
<property name="failureChannel" value="myErrorChannel"/>
</bean>
The trapException="true" allows do not re-throw the exception to the top level. In your case to the <splitter>.
The onFailureExpression says what to send to the failureChannel from the catch block.
The failureChannel is your desired error-channel to handle <service-activator> failures.
The source code looks like, by the way:
try {
Object result = callback.execute();
if (this.onSuccessExpression != null) {
this.evaluateSuccessExpression(message);
}
return result;
}
catch (Exception e) {
Exception actualException = this.unwrapExceptionIfNecessary(e);
if (this.onFailureExpression != null) {
Object evalResult = this.evaluateFailureExpression(message, actualException);
if (this.returnFailureExpressionResult) {
return evalResult;
}
}
if (!this.trapException) {
throw actualException;
}
return null;
}
Since we prevent re-throw with trapException="true", we end up on the return null. And having <service-activator> with null-payload loyalty we allow our <splitter> to go ahead with other messages.
HTH

Spring splitter/aggregator handling exceptions

Version : spring-integration-core - 2.2.3
Here is the simplified version of my splitter/aggregator setup.
<task:executor id="taskExecutor" pool-size="${pool.size}"
queue-capacity="${queue.capacity}"
rejection-policy="CALLER_RUNS" keep-alive="120"/>
<int:channel id="service-requests"/>
<int:channel id="service-request"/>
<int:channel id="channel-1">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:channel id="channel-2">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:gateway id="myServiceRequestor" default-reply-timeout="${reply.timeout}"
default-reply-channel="service-aggregated-reply"
default-request-channel="service-request"
service-interface="com.blah.blah.MyServiceRequestor"/>
<int:splitter input-channel="service-request"
ref="serviceSplitter" output-channel="service-requests"/>
<!-- To split the request and return a java.util.Collection of Type1 and Type2 -->
<bean id="serviceSplitter" class="com.blah.blah.ServiceSplitter"/>
<int:payload-type-router input-channel="service-requests" resolution-required="true">
<int:mapping
type="com.blah.blah.Type1"
channel="channel-1"/>
<int:mapping
type="com.blah.blah.Type2"
channel="channel-2"/>
</int:payload-type-router>
<!-- myService is a bean where processType1 & processType2 method is there to process the payload -->
<int:service-activator input-channel="channel-1"
method="processType1" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:service-activator input-channel="channel-2"
method="processType2" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:publish-subscribe-channel id="service-reply" task-executor="taskExecutor"/>
<!-- myServiceAggregator has a aggregate method which takes a Collection as argument(aggregated response from myService) -->
<int:aggregator input-channel="service-reply"
method="aggregate" ref="myServiceAggregator"
output-channel="service-aggregated-reply"
send-partial-result-on-expiry="false"
message-store="myResultMessageStore"
expire-groups-upon-completion="true"/>
<bean id="myResultMessageStore" class="org.springframework.integration.store.SimpleMessageStore" />
<bean id="myResultMessageStoreReaper" class="org.springframework.integration.store.MessageGroupStoreReaper">
<property name="messageGroupStore" ref="myResultMessageStore" />
<property name="timeout" value="2000" />
</bean>
<task:scheduled-tasks>
<task:scheduled ref="myResultMessageStoreReaper" method="run" fixed-rate="10000" />
</task:scheduled-tasks>
If the processType1/processType2 method in mySevice throws a RuntimeException, then it tries to send the message to an error channel(i believe spring does it by default) and the message payload in error channel stays on in heap and not getting garbage collected.
Updated More Info:
For my comment on error channel. I debugged the code and found that ErrorHandlingTaskExecutor is trying to use a MessagePublishingErrorHandler which inturn sending the message to the channel returned by MessagePublishingErrorHandler.resolveErrorChannel method.
Code snippet from ErrorHandlingTaskExecutor.java
public void execute(final Runnable task) {
this.executor.execute(new Runnable() {
public void run() {
try {
task.run();
}
catch (Throwable t) {
errorHandler.handleError(t); /// This is the part which sends the message in to error channel.
}
}
});
}
Code snipper from MessagePublishingErrorHandler.java
public final void handleError(Throwable t) {
MessageChannel errorChannel = this.resolveErrorChannel(t);
boolean sent = false;
if (errorChannel != null) {
try {
if (this.sendTimeout >= 0) {
sent = errorChannel.send(new ErrorMessage(t), this.sendTimeout);
.....
When i take a heap dump, I always see the reference to the payload message(which i believe is maintained in the above channel) and not getting GC'ed.
Would like to know what is the correct way to handle this case or if i'm missing any in my config?
Also is it possible to tell spring to discard the payload(instead of sending it to error channel) in case of any exception thrown by the service activator method?
Looking forward for your inputs.
Thanks.
You don't have an error-channel defined on your gateway so we won't send it there, we'll just throw an exception to the caller.
However, the partial group is sitting in the aggregator and will never complete. You need to configure a MessageGroupStoreReaper as shown in the reference manual (or set a group-timeout in Spring Integration 4.0.x) to discard the partial group.

Resources