Spring integration chain error handling - spring

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

Related

Tracing input & output of message channels of Spring Integration

I have build a small application using Spring Integration. I have also written some jUnit for more service and other classes.
I have used XML configuration for the channel and en-point configuration, I am wondering if I can test the input and output of a perticular channel.
Is there a way we can test the input and output of the channels?..
Update
I am trying to the below flow. How shall i proceed?
<int:channel id="getPresciption" />
<int:channel id="respPrescription" />
<int:channel id="storePrcedureChannell" />
<int-http:inbound-gateway
request-channel="getPresciption" reply-channel="respPrescription"
supported-methods="GET" path="/getAllPresciption">
<int-http:request-mapping
consumes="application/json" produces="application/json" />
</int-http:inbound-gateway>
<int:service-activator
ref="prescriptionServiceActivator" method="buildPrescription"
input-channel="getPresciption" output-channel="storePrcedureChannell" />
<int:service-activator
ref="prescriptionServiceActivator" method="storePrescription"
input-channel="storePrcedureChannell"></int:service-activator>
SO how can i write the Test contex?
Let below are the methods which are called by channel flow.
public Message<List<Prescription>> buildPrescription() {
//Do some processing
}
public Message<List<Prescription>> storePrescription(Message<List<Prescription>> msg) {
//Do something and return the List.
}
First of all you can take a look into the Spring Integration Testing Framework: https://docs.spring.io/spring-integration/reference/html/testing.html#test-context
And then use a MockIntegration.mockMessageHandler() together with the MockIntegrationContext.substituteMessageHandlerFor() to replace a real handler in the endpoint and verify incoming data from the channel.
If that is still hard for you, you always can inject those channels into your test class and add to them a ChannelInterceptor and verify messages in its preSend() implementation.

Spring integration and JDBC in single transaction

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).

Problems with unit testing a spring integration program

I am trying to unit test an xpath router, but having problems with it. here is my context file:
<int:channel id="toTransactionTypeRouterChannel" />
<int-xml:xpath-router id="transactionsTypeRouter"
input-channel="toTransactionTypeRouterChannel" resolution-required="false"
evaluate-as-string="true" default-output-channel="errorChannel">
<!-- Select node name of the first child -->
<int-xml:xpath-expression
expression="name(/soapNs:Envelope/soapNs:Body/schNs:processArchiveRequest/schNs:fulfillmentRequest/schNs:requestDetail/*[1])"
namespace-map="archiveNamespaceMap" />
<int-xml:mapping value="sch:bulkRequestDetail"
channel="bulkChannel" />
<int-xml:mapping value="sch:transactionalRequestDetail"
channel="transactionChannel" />
</int-xml:xpath-router>
<int:channel id="bulkChannel" />
<int:channel id="transactionChannel" />
<int:transformer input-channel="bulkChannel"
output-channel="consoleOut" expression="'Bulk channel has received the payload' " />
<int:transformer input-channel="transactionChannel"
output-channel="consoleOut" expression="'Transaction channel has received payload' " />
<int:transformer input-channel="errorChannel"
output-channel="consoleOut" expression="'Error channel has received payload' " />
As you can see here, there are 2 different routes(bulk,trans.) + error channel.Here is my unit test case for trans channel route:
#Test
public void testTransactionFlow() throws Exception {
try {
Resource bulkRequest = new FileSystemResource("src/main/resources/mock-message-examples/SampleProcessArchiveTransRequest.xml");
String transRequestStr= extractResouceAsString(bulkRequest);
toTransactionTypeRouterChannel.send(MessageBuilder.withPayload(transRequestStr).build());
Message<?> outMessage = testChannel.receive(0);
assertNotNull(outMessage);
context file for junit
<int:bridge input-channel="transactionChannel"
output-channel="testChannel"/>
<int:channel id="testChannel">
<int:queue/>
</int:channel>
As you can see, in the junit context file, I am connecting transactional channel to the test channel.in the junit test case, I am sending a payload to the router in the junit method, and trying to receive it from the input channel and use it for assertion. however the assertion fails, as the message from transaction channel directly goes to consoleOut before getting routed to inputChannel as given in the junit coontext file. How do I intercept the message before it goes to consoleOut? I also tried adding wireTap interceptors but they didnt work:
WireTap wireTap = new WireTap(someChannel);
boolean w = wireTap.isRunning();
transactionChannel.addInterceptor(wireTap);
Basically, I need a separate flow for unit testing.
With that configuration, you are just adding a second consumer to transactionChannel - messages will be round-robin distributed to the transformer and bridge.
You can unsubscribe the transformer for your test case by autowiring it by id as an EventDrivenConsumer and stop() it before sending your message.

DeferredResult from JMS Request/Reply with ListenableFuture

I was looking for opportunities to improve the Rest Api we have exposed to external clients. During that exercise, I found I am barely taking advantage of the decision our integration team took to have backend integrations with JMS request/reply instead of traditional blocking SOAP request/reply.
Currently all the interactions to message broker are done using jmsOutboundGateway, because of which requesting thread has to wait for completion. In order to scale RestAPI, I want to send JMS reply using DeferredResult from Spring MVC controller. The controller interaction with message broker is depicted below:
Controller --> GatewayProxy --> JMSOutboundGateway
I am looking for opportunities to use ListenableFuture as return type of GatewayProxy, but I am unable to find a proper mean of achieving it using spring integration.
Below is the integration flow I am calling from controller:
<int:gateway
service-interface="ae.emaratech.ngx.service.PermitSearchService"
default-request-channel="permit_search_input_channel"
default-reply-timeout="${broker.jms.gateway.min.consumers}"/>
<int:channel id="permit_search_input_channel" />
<int:chain input-channel="permit_search_input_channel">
<int:header-enricher>
<int:header name="person_number" expression="payload"/>
</int:header-enricher>
<int:transformer expression="#formatString(#api_messages['FIND_PERM_BY_PERSNO_MSG'],headers)"/>
<int:header-filter header-names="JMS_*,jms_*,priority" pattern-match="true" />
<int:header-enricher>
<int:header name="jms_type" type="java.lang.String" value="1" overwrite="true"/>
</int:header-enricher>
<jms:outbound-gateway
request-destination="permitsInboundQueue"
reply-destination="permitsOutboundQueue"
receive-timeout="${broker.jms.gateway.timeout}"
correlation-key="Correlation_ID"
connection-factory="brokerConnectionFactory">
<jms:reply-listener concurrent-consumers="${broker.jms.gateway.min.consumers}" max-concurrent-consumers="${broker.jms.gateway.max.consumers}"/>
</jms:outbound-gateway>
<int-xml:xpath-filter throw-exception-on-rejection="true">
<int-xml:xpath-expression expression="not(boolean(/*/ErrorDetails))"/>
</int-xml:xpath-filter>
<int-xml:xslt-transformer
xsl-resource="classpath:/META-INF/spring/integration/permit-to-json.xsl"
result-type="StringResult" >
</int-xml:xslt-transformer>
<int:transformer expression="payload.toString()"/>
</int:chain>
Not sure what problem you have, but the feature looks like:
ListenableFuture<String> result = this.asyncGateway.async("foo");
result.addCallback(new ListenableFutureCallback<String>() {
#Override
public void onSuccess(String result) {
...
}
#Override
public void onFailure(Throwable t) {
...
}
});
It is available since version 4.1.

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