Automatically publish errors from errorChannel to the specific destination - spring-integration

This documentation says, that it is possible to automatically send error messages from global errorChannel to specific destination, the only thing needed is to set the following property:
spring.cloud.stream.bindings.error.destination
I am really confused with this part. Just adding property doesn't work (I use RabbitMQ, Exchange and Queue are configured). So I can see error handled by #ServiceActivator in my application, but it is not sent to "myErrors" exchange in RabbitMQ. Looks like this property does not define outbound binding for errorChannel.
If I create output "error" channel explicitly in my application,
/**
* Error channel name.
*/
String ERROR_ONE = "error";
#Output(MySink.ERROR_ONE)
MessageChannel errorOne();
and explicitly send error message to it from my global handler, for example by declaring outputChannel:
#ServiceActivator(inputChannel = "errorChannel", outputChannel = "error")
public ErrorMessage errorGlobal(ErrorMessage message) {
System.out.println("Handling ERROR GLOBAL SA: " + message);
return message;
}
it will work. But this can be done to binding-specific handlers also, so there is nothig errorChannel-specific here.
The question: Do I miss something? Can publish be done without defining outbound channels explicitly?

That documentation about ...binding.errors... is obsolete and needs to be fixed.
You should use auto-bind-dlq and republish-to-dlq instead and the framwork will publish the failed message to the dlq, with additional headers with information about the failure.

Related

JmsOutboundGateway how can we set alter replyTo when sending out bound message so it is not same as replyDestination?

We have a MQ request/reply pattern implementation
Here we use a IBM MQ Cluster host. Here the request/reply queues on both sides are linked to each other by the MQ cluster environment, as the queue managers of different systems within the cluster talks to each other.
Our Requestor code uses Spring JMS Integration - JmsOutboundGateway to send and receive message
The service provider is a Mainframe application which we have no control.
public class JmsOutboundGatewayConfig {
#Bean
public MessageChannel outboundRequestChannel() {
return new DirectChannel();
}
#Bean
public QueueChannel outboundResponseChannel() {
return new QueueChannel();
}
#Bean
#ServiceActivator(inputChannel = "outboundRequestChannel")
public JmsOutboundGateway jmsTestOutboundGateway(ConnectionFactory connectionFactory) {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestDestinationName("REQUEST.ALIAS.CLUSTER.QUEUE");
gateway.setReplyDestinationName("REPLY.ALIAS.CLUSTER.QUEUE");
gateway.setReplyChannel(outboundResponseChannel());
gateway.setRequiresReply(true);
return gateway;
}
}
// Requestor - sendAndReceive code
outboundRequestChannel.send(new GenericMessage<>("payload"));
Message<?> response = outboundResponseChannel.receive(10000);
Issue:
The issue we are facing when we send message, the gateway code is also passing the replyTo = queue://REPLY.ALIAS.CLUSTER.QUEUE.
Now the mainframe program that consumes this message , it is forced to reply back to the replyTo queue. It is failing on mainframe side as this replyTo queue which we send is not part of their MQ Mgr/env.
I could not find a way to remove the replyTo when sending message. As JmsOutboundGateway set this replyTo using the "ReplyDestinationName" which I had configured.
Our requestor will need to set the "ReplyDestinationName" as we are listening to this Alias-cluster reply queue for reply back.
I looked at the Channel interceptor options, I could only Intercept the message to alter it, but no option to change the replyTo.
Is there way to alter the replyTo i.e replyTo and ReplyDestination different?
Is there anyway to remove/not-set the replyTo when sending message to request queue?
Just wondering how to get this working for such MQ cluster environment where the replyTo queue will have to kept what the mainframe consumer service want, that is different to the replyDestination queue which we use.
Considering that the replyTo is used by the mainframe service to reply back. If it is not passed the mainframe service will use its own reply queue which is linked to our reply-cluster-alias queue.
Any inputs appreciated?
Thanks
Saishm
Further clarification:
The cluster mq env we have, Our spring jms outbound gateway is writing request to - "REQUEST.ALIAS.CLUSTER.QUEUE" & listening to the reply on "REPLY.ALIAS.CLUSTER.QUEUE"
So the jmsOutboundGateway sets the replyTo=REPLY.ALIAS.CLUSTER.QUEUE
Now the mainframe service on the other side is reading the message from "REQUEST.LOCAL.QUEUE". In the cluster env the "REQUEST.ALIAS.CLUSTER.QUEUE" ands its QMGR are linked to "REQUEST.LOCAL.QUEUE" and its QMGR, this is all managed within the cluster MQ env.
The mainframe service when consuming the request, sees that the incoming message had a replyTo and tries to send the response to this replyTo.
The issue is mainframe was supposed to reply to "REPLY.LOCAL.QUEUE" which is linked to REPLY.ALIAS.CLUSTER.QUEUE
If there is no replyTo it would have send the reply to "REPLY.LOCAL.QUEUE".
Now from the jmsOutBoundGateway I dont have any options to remove replyTo when sending mEssage or edit it to "REPLY.LOCAL.QUEUE" and keep listening to the response/reply of the request on "REPLY.ALIAS.CLUSTER.QUEUE"
Doesn't look like a JmsOutboundGateway will fit your IBM MQ cluster configuration and requirements. You just cannot use that replyTo feature since we cannot bypass JMS protocol over here.
Consider to use a pair of JmsSendingMessageHandler and JmsMessageDrivenEndpoint components, respectively. Your JmsSendingMessageHandler will just send a JMS message to the REQUEST.ALIAS.CLUSTER.QUEUE and forget. Only what you need is to supply a JmsHeaders.CORRELATION_ID. The JmsHeaderMapper will populate a jmsMessage.setJMSCorrelationID() property - the same way as JmsOutboundGateway does that by default. In this case your mainframe service is free to use its own reply queue and I guess it will supply our correlationId correctly.
A JmsMessageDrivenEndpoint has to be subscribed to the REPLY.ALIAS.CLUSTER.QUEUE and you do a correlation between request and reply yourself. For example a Map of correlationId and Future to fulfill when reply comes back.

Handling Error in asynchronous process using #Async annotation on custom method vs on #Gateways method

I have a process where I need to upload file to Sftp server asynchronously. So after exploring more about Async in gateways I found that I need to have error channel defined in #MessagingGateway parameter then handler to handle the Exception propagated to error channel but I felt handling this way is complicated for me, as I will have to update Pojo field and persist into DB depending on the file upload, success or failure.
So I thought of having a custom method annotated with #Async and call the gateway method. Also surround gateway method with try block and catch any exception occurred in the downstream
Code Sample:
#Async
void upload(Resource file, FileStatus fileStatus){
try{
uploadGateway.upload(file,fileStatus.getFilePath(),fileStatus.getFileName());
}catch(RuntimeException e){
fileStatus.setUploadStatus("Failed");
//save into db
}
}
Upload Gateway without error channel so that error can be sent back to caller
#MessagingGateway
public interface UploadGateway {
#Gateway(requestChannel = "input.channel")
void upload(#Payload Resource file, #Header("path") String path, #Header("name") String fileName);
}
Handler:
#Bean
public IntegrationFlow uploadDocument() {
return IntegrationFlows.from("input.channel")
.log(LoggingHandler.Level.WARN)
.handle(Sftp.outboundAdapter(sftpSessionFactory(), FileExistsMode.FAIL)
.autoCreateDirectory(true)
.remoteDirectoryExpression("headers['path']")
.fileNameExpression("headers['name']"))
.get();
}
Question:
What will be the consequences if I'm handling error this way? Is this the right way to handle any error occurred in downstream flow?
Since #MessagingGateway is like an RPC in messaging, it is fully OK to catch an exception on its method call like that. Since you make your flow fully sync, it works like typical Java exceptions sub-system.
Your concern about async error handling with errorChannel really makes sense since it is similar in complexity with standard Java async method handling and its errors processing.
On the other hand it is really commended to handle errors downstream via errorChannel if that is going to be some complex logic in some other flow. Plus you are going to return back some compensation message.
However in the end of day the choice is yours: there is no drawbacks to handle errors yourself.
See Error Handling chapter for more food to think on.

Set message custom properties when rejecting message

I have this simple application that uses node-amqp10 to read messages from some subscriptions and process/accepts or rejects messages due to some error that might happen.
My question is, is it possible for the consumer to set any additional property into the message when rejecting? (like DeadLetterError)
I've thought the receiverLink.reject would get the error parameter and set into the message automatically, but maybe this is not even possible (consumer setting properties into the message)
I am assuming you are using Azure Service Bus based on the tag in your question. If you use #azure/service-bus package instead, you have the option of updating properties of the message. Also while dead lettering you can provide deadLetterReason and deadLetterErrorDescription like below:
async function receiveMessage() {
// If receiving from a subscription you can use the createReceiver(topicName, subscriptionName) overload
const receiver = sbClient.createReceiver(queueName);
const messages = await receiver.receiveMessages(1);
if (messages.length) {
console.log(
">>>>> Deadletter the one message received from the main queue - ",
messages[0].body
);
// Deadletter the message received
await receiver.deadLetterMessage(messages[0], {
deadLetterReason: "Incorrect Recipe type",
deadLetterErrorDescription: "Recipe type does not match preferences."
});
} else {
console.log(">>>> Error: No messages were received from the main queue.");
}
await receiver.close();
}
For more details, refer Azure Service Bus client library for Javascript.
My question is, is it possible for the consumer to set any additional
property into the message when rejecting? (like DeadLetterError)
No, you can't. I know what you want to do, But the method of the basic module '#azure/service-bus' is not designed as you think.
For NODE.JS, it is deadLetterMessage(ServiceBusReceivedMessage, DeadLetterOptions & [key: string]: any).
JavaScript reference API:
https://learn.microsoft.com/en-us/javascript/api/#azure/service-bus/servicebusreceiver?view=azure-node-latest#deadLetterMessage_ServiceBusReceivedMessage__DeadLetterOptions____key__string___any_
It don't allow you to add custom properties.
But if you using C#, then it is possible:
DeadLetterAsync(String, IDictionary<String,Object>)
C# API reference:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.queueclient.deadletterasync?view=azure-dotnet
In short, for NODE.JS you can only change the description of the dead-letter message, but can't add custom properties.

How to publish reply back to reply queue specified as replyTo header with Jms.inboundGateway

I am using
MessageProducerSupport messageProducer =
Jms.messageDriverChannelAdapter(jmsConnectionFactory, TransactedMessageListenerContainer.class)
.destination(queue)
.get();
to consume messages from ActiveMQ queue.
This is first part of my IntegrationFlow and then multiple stages occur (transform, route, handle..) within transaction
It is there to handle messages from upstream
In order to get the ACK from Spring integration pipeline I used Jms.inboundGateway(jmsConnectionFactory, TransactedMessageListenerContainer.class) which doesn't break existing flow and everything works
When I set replyTo header of upstream message, I would assume Spring Integration would send the object of the last stage of IntegrationFlow which was successful back to replyTo queue
Is my approach correct?
Is it possible to achieve such use-case?
Yes, that's correct and should work by its (Messaging Gateway) premise.
The Jms.inboundGateway() is based on the ChannelPublishingJmsMessageListener with the expectReply = true and there is a code:
private Destination getReplyDestination(javax.jms.Message request, Session session) throws JMSException {
Destination replyTo = request.getJMSReplyTo();
....
return replyTo;
}
to obtain a replyTo from the request.
Everything that works well, if your last MessageHandler in the flow is a AbstractReplyProducingMessageHandler and really returns something to be produced to the replyChannel from headers.
If you aren't sure in your case, so share the end of your flow and the place where would you like to send a reply.

Spring Integration - TCP - Response Correlation

I'm new to Spring Integration. The situation is that I've to connect to Tcp server dynamically(i.e. the DNS will be dynamically generated at runtime based on some params). Because of this I'm using Service Activator to manually create Tcp Connections and send messages. I've overridden CachingClientConnectionFactory to make use of shared connections concept(with single-use='false'). I was listening to messages using TcpReceivingChannelAdaptor by overriding "onMessage" method. The problem is that the server either responds with a Success or failure(with Generic messages) with no CorrelationID. Is there any way to correlate the request with the response ?
I tried using TcpOutboundGateway, but with this approach also I get the same problem. I used TcpConnectionSupport to send messages :
//Sample Code.
final String correlationId = "" // Dynamic unique number
TcpOutboundGateway outboundGateway = new TcpOutboundGateway(){
public synchronized boolean onMessage(Message<?> message) {
ByteArrayToStringConverter converter = new ByteArrayToStringConverter();
String response = converter.convert((byte[]) message
.getPayload());
logger.info(correlationId);
return false;
}
};
DefaultCachingClientConnectionFactory connFactory = new DefaultCachingClientConnectionFactory();
TcpConnectionSupport con = connFactory.obtainConnection();
GenericMessage<String> msg = new GenericMessage<String>("Sample Message" + correlationId);
con.registerListener(outboundGateway);
con.send(msg);
// DefaultCachingClientConnectionFactory is the subclass of CachingClientConnectionFactory.
When I send multiple messages, every time I get the same correlation printed in the "onMessage" method.
I read here that Outbound Gateway will correlate messages. Please help me. Maybe I'm doing something wrong.
Thanks
Unless you include correlation data in the message you can't correlate a response to a request.
The gateway achieves this by only allowing one outstanding request on a socket at a time; hence the reply has to be for the request. This is not very useful at high volume with a shared connection; hence the caching client cf was introduced. The gateway keeps a map of outstanding requests based on the connection id.
The gateway, in conjunction with the caching client connection factory should do what you need. However, overriding onMessage is not a good idea, because that's where the reply correlation is done.

Resources