Spring Integration error-channel doesn't receive all exceptions - java

I am using spring integration's error channel to capture exceptions in the framework. But of the two channels of the same type, one's thrown exceptions are received by the error handler while other's are not.
<int:gateway id="myGateway" service-interface="com.si.MyGateway" request-
channel="myRequestChannel" error-channel="errorHandlingChannel">
<!-- Redirects exceptions to error-channel -->
<int:chain input-channel="myRequestChannel" output-channel="lastChannel">
<int:header-enricher>
<int:error-channel ref="errorHandlingChannel">
<int:header-enricher>
<int:service-activator ref="testActivator1"
method="generateException" requires-reply="true" />
</int:chain>
<!-- Does not redirect exceptions to error-channel -->
<int:chain input-channel="lastChannel" output-
channel="nullChannel">
<int:header-enricher>
<int:error-channel ref="errorHandlingChannel">
<int:header-enricher>
<int:service-activator ref="testActivator2"
method="generateException" requires-reply="true" />
<int:filter ref="myFilter" discard-channel="nullChannel" />
<int:service-activator ref="testActivator3"
method="generateException" requires-reply="true" />
<int:filter ref="myFilter" discard-channel="nullChannel" />
</int:chain>
<int:service-activator input-channel="errorHandlingChannel"
ref="errorHandler" method="handle" />
The errorHandler's handle method accepts MessagingException. When I throw exception from myRequestChannel, it is received by the errorHandler but when the same exception is thrown from testActivator2 or testActivator3, it doesn't reach the errorHandler at all.
both channels are defined as async channels:
<int:channel>
<int:queue capacity="10">
</int:channel>
On debugging, I found that all methods' exceptions are being thrown and follow a chain through spring's classes when an internal class ultimately throws a MessagingException, but the exceptions from the last two activators do not reach the handler.
What could be the reason?

What kind of exceptions do you throw?
If it is MessagingException it must be supplied with the failedMessage, otherwise the error is sent to the default errorChannel on the <poller>.

Related

How to handle Exceptions with a Spring Integration Flow

I am trying to do the following using spring integration.
I would like to read from an input channel an incoming Message that goes throws a series of Steps and produces a final message.Each step can throw an Exception. This could be either due to a DB call or ExternalCall etc. Each step is considered as a service-activator. If all is well a successful a final Message is generated.I need to handle each Exception thrown by each Step as a separate case and then produce the appropriate final Message.
Using the below xml I have written a test code that passes when sucessfull but when an error occurs although I see a Final message generated in the postSend (sent=true) on channel 'bean 'finalChannel' log but the process does not return to the final queue.
Why does this happen?
<?xml version="1.0" encoding="UTF-8"?>
<context:component-scan base-package="com.demo.">
</context:component-scan>
<header-enricher input-channel="inputChannel"
output-channel="enrichedChannel">
<header name="initialMessage" expression="getPayload()" />
</header-enricher>
<!-- first-step -->
<service-activator input-channel="enrichedChannel"
output-channel="firstStep" ref="validateOne" />
<gateway id="validateOne" default-request-channel="ch1"
error-channel="errors1" />
<chain input-channel="ch1">
<service-activator id="mockServiceOneActivator"
ref="mockServiceOne" method="register" />
</chain>
<!-- enrich-header with payload to be available down the line -->
<header-enricher input-channel="firstStep"
output-channel="secondStep">
<header name="initialInfo" expression="getPayload()" />
</header-enricher>
<!-- second-step -->
<service-activator input-channel="secondStep"
output-channel="enrichWithTwoChannel" ref="validateTwo" />
<gateway id="validateTwo" default-request-channel="ch2"
error-channel="errors2" />
<chain input-channel="ch2">
<service-activator id="mockServiceTwoActivator"
ref="mockServiceTwo" method="callExternal" />
</chain>
<!-- enrich-header with payload to be available down the line -->
<header-enricher input-channel="enrichWithTwoChannel"
output-channel="eligibilityCheck">
<header name="serviceTwoInfo" expression="getPayload()" />
</header-enricher>
<!-- final-step -->
<service-activator input-channel="eligibilityCheck"
output-channel="finalChannel" id="mockServiceFinalActivator"
ref="mockServiceFinal" method="submit" />
<!-- error handling -->
<service-activator input-channel="errors1"
output-channel="finalChannel" ref="traceErrorHandler"
method="handleFailedTrace" />
<service-activator input-channel="errors2"
output-channel="finalChannel" ref="traceErrorHandler2"
method="handleFailedTrace" />
<channel id="finalChannel">
<queue />
</channel>
<service-activator input-channel="errorChannel"
ref="globalExceptionHandler" method="handleError" />
My handler code looks like this ..
#Component("traceErrorHandler")public class TraceErrorHandler {
public Message<FinalMessage> handleFailedTrace(Message<?> errorMessage) {
MessagingException payload = (MessagingException) errorMessage.getPayload();
InitialMessage im = (InitialMessage) payload.getFailedMessage().getHeaders().get("initialMessage");
ServiceOneException error = (ServiceOneException) payload.getCause();
FinalMessage v = new FinalMessage(im.getFrom(), im.getTo(), error.getErrorTemplate());
Message<FinalMessage> finalMessage = MessageBuilder.withPayload(v).copyHeaders(payload.getFailedMessage().getHeaders()).build();
return finalMessage;
}}
I am not sure if this is the correct approach in regards to error-handling. The initial chains are simple activators but further down the line there will be more logic.
Should we I always use chains to handle separate error channels
Even if its a single activator service called.
Only when there are Multiple service-activator/transforms that encapsulate a single point of error?
EDIT 1
I added a request-handler-advice-chain reference a bean.
<int:service-activator
id="serviceOneActivator" input-channel="enrichedChannel"
ref="serviceOne" method="register" output-channel="firstStep">
<int:request-handler-advice-chain>
<ref bean="myclass" />
</int:request-handler-advice-chain></int:service-activator>
The reference bean is at first a default definition in xml
<bean id="myclass"
class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<property name="failureChannel" ref="finalChannel" />
<property name="onFailureExpression" value="#payload"/>
<property name="returnFailureExpressionResult" value="true" />
</bean>
if I add the <property name="onFailureExpression" value="#payload"/> property it fails with:
Cannot convert value of type 'java.lang.String' to required type 'org.springframework.expression.Expression' for property 'onFailureExpression'
What i would like to do is convert the exception message to a final message object? but all expression i have added seem to fail on load.
A chain with one component makes no sense.
A chain is syntactic sugar only, each component still stands alone at runtime.
If you want to handle errors for individual endpoints, consider using ExpressionEvaluatingRequestHandlerAdvices instead.
See https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/messaging-endpoints.html#message-handler-advice-chain

Calling one service activator and another service activator

I have a doubt in Spring Integration Flow. Can we call one int:service-activator after another . Consider below example.
<int:channel id="getPresciption" />
<int:channel id="respPrescription" />
<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="medicineServiceActivator" method="buildPrescription"
input-channel="respPrescription" output-channel="respPrescription" />
<int:service-activator
ref="medicineServiceActivator" method="storePrescription"
input-channel="respPrescription"></int:service-activator>
My Qoestion is can we do this.
or we have to use Aggregator.
You need another channel
<int:service-activator
ref="medicineServiceActivator" method="buildPrescription"
input-channel="respPrescription" output-channel="toStorePrescription" />
<int:service-activator
ref="medicineServiceActivator" method="storePrescription"
input-channel="toStorePrescription"></int:service-activator>

Spring integration gateway routing

I have a problem with spring integration basically I'am trying to create chain which will invoke 1 of several services and returns result as reply to chain gateway for further processing.
<int:chain input-channel="inputChannel" output-channel="outputChannel">
<int:header-enricher>
<int:header name="temporalPayload" expression="payload"/>
<int:header name="matcher" value="other" />
</int:header-enricher>
<int:gateway request-channel="routerChannel" reply-channel="replyChannel" error-channel="errorChannel"/>
<int:transformer expression="headers.temporalPayload"/>
</int:chain>
<int:router input-channel="routerChannel" expression="headers.matcher.matches('(test)|(test2)')?headers.matcher:'other'" >
<int:mapping value="test1" channel="channel1"/>
<int:mapping value="test2" channel="channel2"/>
<int:mapping value="other" channel="channel3" />
</int:router>
<int:service-activator input-channel="channel1" output-channel="replyChannel" ref="service1" method="handle"/>
<int:service-activator input-channel="channel2" output-channel="replyChannel" ref="service2" method="handle"/>
<int:service-activator input-channel="channel3" output-channel="replyChannel" ref="service3" method="handle"/>
Problem that gateway does not receive reply to channel "replyChannel" from service-activator. What I'am doing wrong?
You can turn on DEBUG logging level for org.springframework.integration category and observe how your message travels.
On the other hand, in most cases you don’t need that reply-Channel on the gateway and just rely on the replyChannel header populated by the gateway. In this case you even have to remove the output-Channel on the service-activators.
There might be some subscriber for your repjyChannel who steals messages for the Gateway.
You can try without reply-Channel or investigate logs as I suggested above.

Is it possible to use 2 different retry beans for one service activator depending on an Exception thrown?

I would like to do a serious retry for the service-activator only in case of a ConnectionException, for other exceptions I would like not to use a retry or a very light retry. What configuration can I use? The essence of my configuration is below:
<int:channel id="sDZCreationErrorChannel">
<int:interceptors>
<int:wire-tap channel="errorLogger"/>
</int:interceptors>
</int:channel>
<int:channel id="sDZConnectionErrorChannel">
<int:interceptors>
<int:wire-tap channel="errorLogger"/>
</int:interceptors>
</int:channel>
<int:chain input-channel="sDZCreationErrorChannel" output-channel="outboundMailChannel">
<int-mail:header-enricher>
<int-mail:to value="${integration.sdz.email.to}"/>
<int-mail:subject value="${integration.sdz.email.subject.creation}"/>
</int-mail:header-enricher>
<int:transformer ref="integrationEmailTransformer" method="transformToEmail"/>
</int:chain>
<int:chain input-channel="sDZConnectionErrorChannel" output-channel="outboundMailChannel">
<int-mail:header-enricher>
<int-mail:to value="${integration.sdz.email.to}"/>
<int-mail:subject value="${integration.sdz.email.subject.noconnection}"/>
</int-mail:header-enricher>
<int:transformer ref="integrationEmailTransformer" method="transformToEmail"/>
</int:chain>
<int:channel id="sDZCreationChannel">
<int:queue/>
</int:channel>
<!-- Processing Creation Chain -->
<int:chain input-channel="sDZCreationChannel" output-channel="debugLogger"
auto-startup="#{environment.getProperty('sd.zoo.enabled') == 'connect'}">
<int:poller fixed-delay="500" />
<int:filter ref="sDZIntegrationExistingRequestSentFilter" method="filter"/>
<int:transformer ref="sDZCreationTransformer" method="transformOrder"/>
<int:service-activator ref="sDZCreationServiceImpl" method="activateConfirmationCodes">
<int:request-handler-advice-chain>
<ref bean="retryAdvice"/>
</int:request-handler-advice-chain>
</int:service-activator>
</int:chain>
<int:exception-type-router input-channel="errorChannel" default-output-channel="integrationDeadLetterErrorChannel">
<int:mapping exception-type="com.smartdestinations.connect.integration.exception.sdz.SDZCreationResponseException" channel="sDZCreationErrorChannel"/>
<int:mapping exception-type="com.smartdestinations.connect.integration.exception.sdz.SDZConnectionException" channel="sDZConnectionErrorChannel"/>
</int:exception-type-router>
No, you can't use one retry advice alongside with other. The <request-handler-advice-chain> strategy is to wrap one advice into another with the order how they are configured within the <request-handler-advice-chain>. So, if you declare one retryAdvice and then another, the first one won't be reached until the second will finish its work.
I don't see the whole picture yet how to easy achieve your requirements, but I really sure that you should deal with custom RetryPolicy, where you can reach a target exception via:
public boolean canRetry(RetryContext context) {
Throwable t = context.getLastThrowable();
...
}
Pay attention to that useful RetryContext object.
There is also interested hook as a RetryListener abstraction with which you can set some additional attributes into that RetryContext. For example in the SI RequestHandlerRetryAdvice:
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
context.setAttribute("message", messageHolder.get());
return true;
}

Spring Integration - header lost

I'm new to Spring Integration and Spring Integration AMQP.
I have the following code:
<bean id="enricher" class="soft.Enricher"/>
<amqp:inbound-channel-adapter queue-names="QUEUE1" channel="amqpInboundChannel"/>
<int:channel id="amqpInboundChannel">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<int:header-enricher input-channel="amqpInboundChannel" output-channel="routingChannel">
<int:header name="store" value="sj" />
</int:header-enricher>
<int:channel id="routingChannel" />
<int:header-value-router input-channel="routingChannel" header-name="store">
<int:mapping value="sj" channel="channelSJ" />
<int:mapping value="jy" channel="channelJY" />
</int:header-value-router>
<amqp:outbound-channel-adapter channel="channelSJ" exchange-name="ex_store" routing-key="sj" amqp-template="rabbitTemplate"/>
<amqp:outbound-channel-adapter channel="channelJY" exchange-name="ex_store" routing-key="jy" amqp-template="rabbitTemplate"/>
<int:channel id="channelSJ" />
<int:channel id="channelJY" />
<int:logging-channel-adapter id="logger" level="ERROR" />
The setup is the following:
Everything is working fine except that headers are lost when a message is picked up by the inbound-channel-adapter.
Likewise the header being enriched called "store" is also lost when the message is being send to the exchange using the outbound-channel-adapter.
This is how a message is looking before being picked up by the inbound-channel-adapter:
This is how the same message is looking after the whole process (notice no headers)
I think your problem is described here:
"By default only standard AMQP properties (e.g. contentType) will be copied to and from Spring Integration MessageHeaders. Any user-defined headers within the AMQP MessageProperties will NOT be copied to or from an AMQP Message unless explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames' properties of this HeaderMapper. If you need to copy all user-defined headers simply use wild-card character ''.*"
So you need to define your own custom instance of DefaultAmqpHeaderMapper and configure the inbound-channel-adapter with it. See here.
It might look something like this:
<bean id="myHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper">
<property name="requestHeaderNames" value="*"/>
<property name="replyHeaderNames" value="*"/>
</bean>
<amqp:inbound-channel-adapter queue-names="QUEUE1" channel="amqpInboundChannel"
header-mapper="myHeaderMapper"/>

Categories

Resources