How to define an exception router in chain after aggregator - spring-integration

I using Spring integration to aggregate messages into one and then send by FTP out bound adapter, I want to move the aggregated messages in a specific folder when outbound FTP server is not available(org.springframework.messaging.MessageDeliveryException), other exceptions will log by console.
Here is my Configuration
<int:chain id="transformChain" input-channel="inboundChannel">
<int:header-enricher>
<int:header name="file_name" expression="payload.name"/>
<int:header name="correlationId" expression="${header.enricher.correlationId}"/>
<int:header name="sum" expression="${header.enricher.sum}"/>
</int:header-enricher>
<int:transformer ref="fileNameToContentTransformer"/>
<int:aggregator send-partial-result-on-expiry="true"
release-strategy-expression="#this.size() == new Integer([0].headers.sum)"
group-timeout="${aggregator.group-timeout}"
message-store="messageStore"
expire-groups-upon-completion="true"
correlation-strategy-expression="headers.correlationId"/>
<int:transformer ref="xmlToJsonTransformer"/>
<ftp:outbound-channel-adapter remote-directory="${ftp.out.remote.directory}"
session-factory="ftpOutClientSessionFactory" auto-create-directory="true"
remote-filename-generator="fileNameGenerator" charset="UTF-8"
temporary-file-suffix=".writing">
</ftp:outbound-channel-adapter>
<int:exception-type-router >
<int:mapping exception-type="org.springframework.messaging.MessageDeliveryException" channel="undeliveredChannel"/>
<int:mapping exception-type="java.lang.Exception" channel="myErrorChannel"/>
</int:exception-type-router>
</int:chain>
However I met such exception when try to start.
Caused by: java.lang.IllegalArgumentException: All handlers except for the last one in the chain must implement the MessageProducer interface. Object of class [org.springframework.integration.ftp.outbound.FtpMessageHandler] must be an instance of interface org.springframework.integration.core.MessageProducer
at org.springframework.util.Assert.instanceCheckFailed(Assert.java:389)
at org.springframework.util.Assert.isInstanceOf(Assert.java:327)
at org.springframework.integration.handler.MessageHandlerChain.configureChain(MessageHandlerChain.java:119)
at org.springframework.integration.handler.MessageHandlerChain.onInit(MessageHandlerChain.java:99)
at org.springframework.integration.context.IntegrationObjectSupport.afterPropertiesSet(IntegrationObjectSupport.java:176)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
Anyone can tell me how to achieve this?
thanks in advance.

You can't add a component to the chain after the ftp:outbound-channel-adapter because it produces no result.
See the retry-and-more sample for an example of how to handle exceptions by adding an ExpressionEvaluatingRequestHandlerAdvice to the outbound adapter.

Related

AWS Async Response routing to success-channel for sqs-outbound-channel-adapter

I have registered an AsyncHandler and also added a success-channel to an SQS outbound flow. The success-channel has a int:logging-channel-adapter endpoint. However I am not able to see any logs from this adapter. The AsyncHandler is able to receive the call-backs but nothing on the success-channel.
In the SqsMessageHandler I see that we are setting an output channel in the obtainAsyncHandler method, but I did not see the success-channel set anywhere. Am I missing something?
I would prefer using the success and failure channels and not AsyncHandler call-back Impl to avoid having AWS specific code in my classes.
Also my <int-aws:sqs-outbound-channel-adapter> is inside a <int:chain> which has no output channel, since the flow ends when the message is sent.
EDIT - Added Config
This is the only way I can get it to log the callback.
<int:channel id="chainChannel" />
<int:channel id="successChannel" />
<bean class="ServiceTransformer" id="serviceTransformer" />
<int:chain input-channel="serviceChannel" id="sendToServiceSqsChain" output-channel="chainChannel">
<int:transformer ref="serviceTransformer" method="transform" />
<int:header-filter header-names="config" />
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS" queue="some-queue" async-handler="sqsPublishCallbackHandler" success-channel="successChannel"/>
</int:chain>
<int:logging-channel-adapter log-full-message="true" channel="chainChannel" />
Here I can just use the same channel in both chain (outbound channel) and sqs-outbound (success-channel)
Unable to get it to work like below:
<int:channel id="successChannel" />
<bean class="ServiceTransformer" id="serviceTransformer" />
<int:chain input-channel="serviceChannel" id="sendToServiceSqsChain" >
<int:transformer ref="serviceTransformer" method="transform" />
<int:header-filter header-names="config" />
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS" queue="some-queue" async-handler="sqsPublishCallbackHandler" success-channel="successChannel"/>
</int:chain>
<int:logging-channel-adapter log-full-message="true" channel="successChannel" />
The <int-aws:sqs-outbound-channel-adapter> component is one-way, therefore there is no outputChannel option expose. However the target class is AbstractMessageProducingHandler. To avoid code duplication we reuse an existing outputChannel internally for that AsyncHandler.
In the XML parser we just remap one to another:
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "success-channel", "outputChannel");
You probably don't see anything in logs because you need to adjust logging config respectively for the appropriate category and level.
UPDATE
According my testing this is definitely not possible to configure such a component with XML DSL within the <chain>. That <int-aws:sqs-outbound-channel-adapter> has to be presented outside of the <chain>.
Consider to more your configuration to Java DSL instead: https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/dsl.html#java-dsl.

Spring Integration - Flow process with erro handling

I'm continuing to study SI and in the same time i'm trying to build an application.
My application flow is this:
Read XML file and split each tag
Each tag have define an attribute called "interval", and i need to create a job that will be repeated, according to this value.
When the job execution is terminated, need to invoke a Web Service to store information
If WBS invokation fails, try to send info by email
Right now i'm arrived on point one ( :D ) of this flow, now i'm trying to move forward and first check the error handling (point 4 of the flow).
This is the actual configuration that i have and this works fine splitting the tag and then invoking the right service-activator:
<context:component-scan base-package="it.mypkg" />
<si:poller id="poller" default="true" fixed-delay="1000"/>
<si:channel id="rootChannel" />
<si-xml:xpath-splitter id="mySplitter" input-channel="rootChannel" output-channel="routerChannel" create-documents="true">
<si-xml:xpath-expression expression="//service" />
</si-xml:xpath-splitter>
<si-xml:xpath-router id="router" input-channel="routerChannel" evaluate-as-string="true">
<si-xml:xpath-expression expression="concat(name(./node()), 'Channel')" />
</si-xml:xpath-router>
<si:service-activator input-channel="serviceChannel" output-channel="endChannel">
<bean class="it.mypkg.Service" />
</si:service-activator>
The endChannel will need to receive all messages from the several channel (sent by the router) and then invoke the WBS. Right now i'm jkust creating classes to check if the flow will works or not.
The remaining part of my applicationContext.xml is this:
<!-- Create a poller that will be used by endChannel -->
<si:poller id="poller" default="true" fixed-delay="1000" error-channel="failedInvocationChannel" />
<!--- take messages from serviceChannel and redirect to endChannel, that is responsable to receive messages from all channels created by the router -->
<si:service-activator input-channel="serviceChannel" output-channel="endChannel">
<bean class="it.mypkg.Service" />
</si:service-activator>
<!-- end channel is a queue -->
<si:channel id="endChannel">
<si:queue capacity="10"/>
</si:channel>
<!-- Messages are taken from the queue.. -->
<si:service-activator input-channel="endChannel">
<bean class="it.mypkg.Invokator" />
</si:service-activator>
<!-- Service activator that handle the errors on the queue -->
<si:channel id="failedInvocationChannel" />
<si:service-activator input-channel="failedInvocationChannel">
<bean class="it.mypkg.Resubmitter" />
</si:service-activator>
but when i run my application i got this error:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.integration.handler.MessageHandlerChain#0': Cannot create inner bean 'org.springframework.integration.handler.MessageHandlerChain#0$child#1.handler' of type [org.springframework.integration.config.ServiceActivatorFactoryBean] while setting bean property 'handlers' with key [1]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.integration.handler.MessageHandlerChain#0$child#1.handler': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Target object of type [class org.springframework.integration.channel.QueueChannel] has no eligible methods for handling Messages.
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:313)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:122)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:382)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:157)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1481)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1226)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:838)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:197)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:172)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:158)
I've read a lot and i'm a little bit confused about all the components that can be used... maybe my error is because i'm trying to use components in the wrong way...
EDIT: Configuration updated with error-channel on poller and removed chain to handle the error
<si:service-activator ref="endChannel" method="dispatch" />
You can't use a ref to a channel in a service activator.
Also, it's better to give elements like chains an id so exceptions are easier to debug.
Also, you generally shouldn't be manipulating the errorChannel header; it's better to add an error-channel to the poller and route the error flow that way.

Spring Integration Chain Exception Handling with Header enricher inside chain

I have a common flow that will be reused multiple times. So, I defined a SI Chain for it like below.
<int:chain id="addInfo" input-channel="addInfoChannel">
<int:header-enricher>
<int:header name="outgoingService" value="Retrieve" />
</int:header-enricher>
<int:gateway request-channel="common_Retrieve_Channel" />
<int-xml:xslt-transformer xsl-templates="addInfoXslTemplate">
<int-xml:xslt-param name="param1" expression="headers.param1" />
<int-xml:xslt-param name="param2" expression="headers.param2" />
</int-xml:xslt-transformer>
<int:header-enricher>
<int:header name="outgoingService" value="Add" overwrite="true" />
</int:header-enricher>
<int:gateway request-channel="common_Add_Channel" />
</int:chain>
If the common_Retrieve_Channel channel fails with a SOAP fault, the header value (outgoingService) is lost.
If I have the header-enricher outside the chain, then header value is available on payload.failedMessage.headers.
I don't want to add this value outside chain, Since this value will be changing inside the chain to call another service.
This chain will be called multiple times by setting up the header values (param1 and param2) differently.
Please let me know if there is any better solution other than extracting the gateway into it's own chain. Thanks for your help.
You must have some compensation flow on the error-channel of your <gateway>.
Since you say that you have SOAP error and you already are familiar with the payload.failedMessage.headers you just need to write something like this there:
<int:transformer input-channel="gatewayErrorChannel" expression="payload.failedMessage"/>
And looks like you further <chain> flow should be the same. Only difference that you will get deal there with requestMessage only, but lose error information from the external SOAP request.

using gateway for starting transaction

I have been following previous answers that talk about inserting gateway / service-activator to start a new transaction mid way in SI processing. I have tried below code but for some reason it does not work, if you could point to error in this configuration please.
All the threads seems to be waiting for something to happen and the sp outbound gateway is not invoked, but i cant figure out what.
The idea here is to process each task produced by splitter in a thread pool under a new transaction.
<int:splitter...output-channel="taskChannel"/>
<int:channel id="taskChannel">
<int:dispatcher task-executor="taskExecutor"/>
</int:channel>
<int:gateway id="txGw" service-interface="com.some.test.StartTransactionalGateway"
default-request-channel="taskChannel" default-reply-channel="individualTask">
</int:gateway>
<int:service-activator ref="txGw"
input-channel="taskChannel"
output-channel="individualTask"
method="startTx"
auto-startup="true">
</int:service-activator>
<int-jdbc:stored-proc-outbound-gateway ...request-channel="individualTask" .....
interface StartTransactionalGateway {
#Transactional
Message<?> startTx(Message<?> m);
}
default-request-channel="taskChannel" the gateway is sending messages to itself.
You can't mix channels in the subflow with the main channels. You need something like...
<int:splitter...output-channel="taskChannel"/>
<int:channel id="taskChannel">
<int:dispatcher task-executor="taskExecutor"/>
</int:channel>
<int:service-activator ref="txGw"
input-channel="taskChannel"
output-channel="whereWeWantTheResultsToGo"
method="startTx"
reply-timeout="0"
auto-startup="true" />
<int:gateway id="txGw" service-interface="com.some.test.StartTransactionalGateway"
default-request-channel="toStoredProc" />
<int-jdbc:stored-proc-outbound-gateway ...request-channel="toStoredProd" .....
You don't need a default-reply-channel; simply omit the reply-channel on the stored proc gateway and the reply will automatically go back.
Well, Don't forget that you can mark something transactional not only using #Transactional annotation. There is <tx:advice> for the XML declaration.
If you need to wrap to the TX just only that <int-jdbc:stored-proc-outbound-gateway>, there is <request-handler-advice> sub-element to wrap the handleRequestMessage of the underlying component to any Advice:
<int-jdbc:request-handler-advice-chain>
<tx:advice>
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</int-jdbc:request-handler-advice-chain>
From other side your code can not work, because <int-jdbc:stored-proc-outbound-gateway> may not return result. The procedure is one-way. But the <gateway> interface is request-reply.
UPDATE
See my answer here Keep transaction within Spring Integration flow. It is another trick how to make down-stream flow transactional.
With <gateway> you should be sure that your transactional subflow returns to the replyChannel in the end. Or... Make your gateway's method as void to achieve one-way behaviour.

Spring integration:Header value router not getting invoked everytime

We are using header-value-router. Configuration:
<int:header-value-router input-channel="accountSummeryRequest"
header-name="word"
default-output-channel="accountSummeryRequest"
resolution-required="false">
<int:mapping value="xx" channel="accountSummeryRequest" />
<int:mapping value="yy" channel="newRequestChannel" />
</int:header-value-router>
<int:service-activator id="accountServiceActivator"
input-channel="accountSummeryRequest"
output-channel="accountSummeryResponse"
ref="serviceGatewayAdapter"
method="requestHandler"
send-timeout="60000"/>
<int:service-activator id="caRequestActivator"
input-channel="newRequestChannel"
output-channel="accountSummeryResponse"
ref="caServiceGatewayAdapter"
method="requestHandler"
send-timeout="60000"/>
now if i give word as yy,first time header-value-router enter code hereis getting called and exact service activator,in this case is caRequestActivator ,is called. But i try again with word=yy header-value-router is not getting called and request goes through accountServiceActivator. Alternate requests works correctly.
I don't know what's the problem here.
Your issue is around a round-robin dispatcher for accountSummeryRequest channel and its two subscribers: <int:header-value-router> and accountServiceActivator.
To fix it you should change the input-channel of that <service-activator> to some different channel. And, of course, don't fogtet to change the <header-value-router>
accordingly.

Resources