Spring integration org.springframework.integration.MessageTimeoutException handling - spring-integration

I have following spring integration flow:
#Bean
public IntegrationFlow innerInFlow(#Value("${jms.concurrency:10}") String concurrency) {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(
Jms.container(connectionFactory, innerQueue)
.concurrency(concurrency)
.taskExecutor(taskExecutor()).get())
.extractPayload(true))
.transform(Transformers.deserializer())
.route(eventRouter())
.get();
}
And after routing
#Bean
public IntegrationFlow findPersonClienFlow(FindClientHandler findClientHandler) {
return IntegrationFlows.from(findPersonClienChannel())
.transform(findClientHandler, "queryToFindClientRequest")
.handle(Jms.outboundGateway(connectionFactory).requestDestination(cifRequestQueue)
.replyDestination(cifResponseQueue).get())
.get();
}
}
In the Jms.outboundGateway I have org.springframework.integration.MessageTimeoutException and I cant understand how I can handle this error?
Thank you.

I believe the MessageTimeoutException is there because the other side doesn't send you a reply into the cifResponseQueue.
You can configure there a receiveTimeout(), but it is 5 secs by default anyway.
Also you can configure a RequestHandlerRetryAdvice or ExpressionEvaluatingRequestHandlerAdvice on this Jms.outboundGateway() to really handle this exception some specific way, using an advice(...) of the ConsumerEndpointSpec.
See Docs on the matter: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints-chapter.html#message-handler-advice-chain
UPDATE
Also as Gary pointed you can catch an exception from the Jms.messageDrivenChannelAdapter() level using its errorChannel() and some flow subscribed to this channel.

Related

How to use custom error channel and WebFlux.inboundGateway to reply with an error response?

Following the two links below I've created an IntegrationFlow which upon an error, calls into the custom errorFlow. However the behaviour I witness is that the application never replies to the client, it just hangs. How can I reply back to the request from the errorFlow? For reference I've hosted my sample on github.
How to use custom error channel to produce custom error response?
https://github.com/spring-projects/spring-integration/issues/3276
#Bean
public IntegrationFlow mainFlow() {
return IntegrationFlows.from(WebFlux.inboundGateway(URI)
.errorChannel(customErrorChannel()))
.channel(MessageChannels.flux()) //Work around: https://github.com/spring-projects/spring-integration/issues/3276
.transform(p -> {
throw new RuntimeException("Error!");
//return "Ok Response"; //If we comment the throw and uncomment this, then the the code replies to the request ok.
})
.get();
}
#Bean
public PublishSubscribeChannel customErrorChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public IntegrationFlow errorFlow() {
return IntegrationFlows.from("customErrorChannel")
.transform(p -> {
return "Error Response";
})
.get();
}
Request...
GET http://localhost:8080/foo
Turns out there is still a problem with that logic.
We need to investigate it more and figure out the fix.
Meanwhile you could use a workaround for your failing transformer with the ExpressionEvaluatingRequestHandlerAdvice and handle error similar way in the errorFlow(). See returnFailureExpressionResult as true. And your onFailureExpression should be kinda a gateway call to that customErrorChannel. Or you can use a MessagingTemplate.sendAndReceive() API from that expression instead of gateway.
See more in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#message-handler-advice-chain

Retry handler in Spring Java DSL

currently, I have Spring Integration Flow where reading payload from JMS queue, transforming to XML format, then send the XML payload to the core app. at the RecordSenderHandler, there is logic to make call rest API to my core app and store the response to Redis according to the response I received. If my core app is not accessible or something wrong with my backend, I flag as error HTTP 500. But I do want to retry the execution for certain times and limit maximum error I got. below is my code. any suggestions?
#Bean
public IntegrationFlow jmsMessageDrivenFlowWithContainer() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(
Jms.container(this.jmsConnectionFactory, recordDestinationQueue)
.concurrentConsumers(xmlConcurrentConsumers)
.maxConcurrentConsumers(xmlMaxConcurrentConsumers))
.errorChannel("errorChannel"))
.handle(payloadSender(), e->e.advice(circuitBreakerAdvice()))
.get();
}
#Bean
#ServiceActivator(inputChannel = "handleChannel")
public PayloadSender payloadSender() {
return new PayloadSender ();
}
#Bean
public RequestHandlerCircuitBreakerAdvice circuitBreakerAdvice() {
RequestHandlerCircuitBreakerAdvice requestHandlerCircuitBreakerAdvice = new RequestHandlerCircuitBreakerAdvice();
requestHandlerCircuitBreakerAdvice.setThreshold(3);
requestHandlerCircuitBreakerAdvice.setHalfOpenAfter(15000);
return requestHandlerCircuitBreakerAdvice;
}
See Adding Behavior to Endpoints and in particular the RequestHandlerRetryAdvice.
.handle(..., e -> e.advice(retryAdvice()))
...
#Bean
public RequestHandlerRetryAdvice retryAdvice() {
...
}

create sftp reply-channel to reply with error or messages that was unsuccessfully sent

I am using java dsl to configure sfp outbound flow.
Gateway:
#MessagingGateway
public interface SftpGateway {
#Gateway(requestChannel = "sftp-channel")
void sendFiles(List<Message> messages);
}
Config:
#Bean
public IntegrationFlow sftpFlow(DefaultSftpSessionFactory sftpSessionFactory) {
return IntegrationFlows
.from("sftp-channel")
.split()
.handle(Sftp.outboundAdapter(sftpSessionFactory, FileExistsMode.REPLACE)
.useTemporaryFileName(false)
.remoteDirectory(REMOTE_DIR_TO_CREATE).autoCreateDirectory(true)).get();
}
#Bean
public DefaultSftpSessionFactory sftpSessionFactory() {
...
}
How can i configure flow to make my gateway reply with Messages that were failed?
In other words i want my gateway to be able to return list of messages which were failed, not void.
I marked gateway with
#MessagingGateway(errorChannel = "errorChannel")
and wrote error channel
#Bean
public IntegrationFlow errorFlow() {
return IntegrationFlows.from("errorChannel").handle(new GenericHandler<MessagingException>() {
public Message handle(MessagingException payload, Map headers) {
System.out.println(payload.getFailedMessage().getHeaders());
return payload.getFailedMessage();
}
})
.get();
}
#Bean
public MessageChannel errorChannel() {
return MessageChannels.direct().get();
}
and in case of some errors(i.e. no connection to SFTP) i get only one error (payload of first message in list).
Where should i put Advice to aggregate all messages?
This is not the question to Spring Integration Java DSL.
This is mostly a design and architecture task.
Currently you don't have any choice because you use Sftp.outboundAdapter() which is one-way, therefore without any reply. And your SftpGateway is ready for that behavior with the void return type.
If you have a downstream errorr, you can only throw them or catch and send to some error-channel.
According to your request of:
i want my gateway to be able to return list of messages which were failed, not void.
I'd say it depends. Actually it is just return from your gateway. So, if you return an empty list into gateway that may mean that there is no errors.
Since Java doesn't provide multi-return capabilities we don't have choice unless do something in our stream which builds that single message to return. As we decided list of failed messages.
Since you have there .split(), you should look into .aggregate() to build a single reply.
Aggregator correlates with the Splitter enough easy, via default applySequence = true.
To send to aggregator I'd suggest to take a look into ExpressionEvaluatingRequestHandlerAdvice on the Sftp.outboundAdapter() endpoint (second param of the .handle()). With that you should send both good and bad messages to the same .aggregate() flow. Than you can iterate a result list to clean up it from the good result. The result after that can be send to the SftpGateway using replyChannel header.
I understand that it sounds a bit complicated, but what you want doesn't exist out-of-the-box. Need to think and play yourself to figure out what can be reached.

How can I specify a Poller for a queue channel using java-dsl?

I want to consume messages from a Queue channel using java-dsl, but Integrations.from doesn't have a signature allowing me to specify a Poller.
How can I achieve this?
Ex.:
#Bean
IntegrationFlow flow() {
return IntegrationFlows.from(this.channel())
.handle(...)
.get();
}
#Bean
MessageChannel channel() {
return MessageChannels.queue().get();
}
Well, actually it is an endpoint responsibility to provide poller properties.
If you are familiar with an XML configuration you should remember that to poll from <queue> we should configure <poller> sub-element for <service-activator> etc.
The same approach is applied in Java DSL as well. The next endpoint definition should be with desired poller:
IntegrationFlows.from(this.channel())
.handle(..., e -> e.poller(Pollers...))
.get();
I've had trouble for some reason setting a poller on the endpoint definition as Artem described - for some reason it gets ignored. You can always set a default poller. This has worked for me:
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(500).get();
}

Spring integration Java DSL : creating jms Message Driver Channel Adapter

I'm having issues with the following Message Driver Channel Adapter
#Bean
public IntegrationFlow jmsInboundFlow() {
return IntegrationFlows.from(Jms.messageDriverChannelAdapter(this.jmsConnectionFactory)
.outputChannel(MessageChannels.queue("inbound").get())
.destination("test"))
.get();
}
#Bean
public IntegrationFlow channelFlow() {
return IntegrationFlows.from("inbound")
.transform("hello "::concat)
.handle(System.out::println)
.get();
}
I'm getting an error about "Dispatcher has no subscribers for channel". What's the preferred configuration for sending the message payload to another integration flow?
With that Java DSL channel auto-creation you should be careful. For example that .outputChannel(MessageChannels.queue("inbound").get()) doesn't populate a MessageChannel bean to the bean factory. But from other side IntegrationFlows.from("inbound") does that.
To fix your issue I suggest to extract #Bean for your inbound channel, or just rely on the DSL:
return IntegrationFlows.from(Jms.messageDriverChannelAdapter(this.jmsConnectionFactory)
.destination("test"))
.channel(MessageChannels.queue("inbound").get())
.get();
Feel free to raise a GH-issue to fix JavaDocs on that .outputChannel() or remove it alltogether, since it is confused.

Resources