Spring Integration WebFlux inboundGateway replyChannel for error response - spring-integration

Trying to use the replyChannel for sending an error response for the WebFlux.inboundGateway, however no response is sent from ErrorFlow and the client continues to wait for response. Kindly suggest.
return IntegrationFlows.from(WebFlux.inboundGateway("/some/uri")
.requestMapping(m -> m.methods(POST))
.requestPayloadType(SomeObject.class)
.replyChannel(webReplyChannel)
.errorChannel(appErrorChannel))
#Bean
public IntegrationFlow appErrorFlow() {
return IntegrationFlows.from(appErrorChannel())
.<ErrorMessage, Message<String>> transform(errorMessage -> MessageBuilder.withPayload("Error Response")
.copyHeaders(((MessagingException) errorMessage.getPayload()).getFailedMessage().getHeaders())
.build())
.get();
Exception:
2023-01-14 13:34:10,343 [reactor-http-nio-2 ] ERROR o.s.i.h.LambdaMessageProcessor - 1283c6fd42485bf5 Could not invoke the method 'public java.lang.Object com.xxxxxxxxxxx.xxxxx..ErrorFlowConfig$$Lambda$2129/0x0000000801ac3a00.transform(java.lang.Object)' due to a class cast exception, if using a lambda in the DSL, consider using an overloaded EIP method that takes a Class<?> argument to explicitly specify the type. An example of when this often occurs is if the lambda is configured to receive a Message<?> argument.
java.lang.ClassCastException: class com.xxxxxxx.model.dto.SomeObject cannot be cast to class org.springframework.messaging.support.ErrorMessage (com.xxxxxxx.model.dto.SomeObject is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader #32f93dff; org.springframework.messaging.support.ErrorMessage is in unnamed module of loader 'app')
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.integration.handler.LambdaMessageProcessor.processMessage(LambdaMessageProcessor.java:105)
at org.springframework.integration.transformer.AbstractMessageProcessingTransformer.transform(AbstractMessageProcessingTransformer.java:115)
at o

This is known framework limitation: https://github.com/spring-projects/spring-integration/issues/3984.
So, for now you have to do like this instead:
.transform(Message.class,
errorMessage ->
MessageBuilder.withPayload("Error Response")
.copyHeaders(((MessagingException) errorMessage.getPayload()).getFailedMessage().getHeaders())
.build())
Only those getFailedMessage().getHeaders() contain respective replyChannel and errorChannel headers.

Related

Correct way to retry messages in batch in case of error in Spring Integration using ExpressionEvaluatingRequestHandlerAdvice

I am processing messages from batch.
Defined advice for MessageHandler
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setFailureChannelName("errorChannel");
Once there is an error processing one or more messages from they payload, serviceActivator is triggered
#ServiceActivator(inputChannel = "errorChannel")
public void handleFailure (Message<?> message){
ExpressionEvaluatingRequestHandlerAdvice.MessageHandlingExpressionEvaluatingAdviceException adviceException = (ExpressionEvaluatingRequestHandlerAdvice.MessageHandlingExpressionEvaluatingAdviceException) message.getPayload();
//throw CustomException
So the above gives me the error but not which item from payload caused it (can be more than one).
What is the correct way to retry the payload(one by one)?
Should I somehow 'Split' the payload ? if yes what is the way to do this.
I tried replacing
#ServiceActivator(inputChannel = "errorChannel")
public void handleFailure (Message<?> message){
with
#Splitter(inputChannel = "errorChannel", outputChannel = "outboundChannel")
public List<Message<?>> handleFailure2(Message<?> message)
But couldn't split the messages from there as message's type is ExpressionEvaluatingRequestHandlerAdvice.MessageHandlingExpressionEvaluatingAdviceException
Spring Integration deals with a Message as a unit of work. It doesn't matter for the framework if payload is a batch of data or not: the service throws an exception, the whole failed message is sent to the error channel with a single exception.
You may consider to use an AggregateMessageDeliveryException in your service to gather all the errors in a single exception and then throw it for processing in that error handler. There you can inspect such an aggregate to determine what items in batch have been failed.
If you'd like to go a splitter way, then it is going to be a general splitter for your batch and then you process in your service items one by one. Not sure if that is what you'd like to do. However splitter can be configured for an ExecutorChannel as an output to process items in parallel.

How can I pass an object `Message` to the route?

I create a flow, which consumes messages from RabbitMQ and after that distributes to the appropriate services by type using the router.
Methods in services take argument Message<?>, because I need to use headers there. But in this method I receive only message payload with type java.lang.String instead org.springframework.messaging.Message and
I get error java.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.messaging.Message.
Payload isn't suitable for me, because I need to get headers from message.
#Bean
public IntegrationFlow testFlow(String queueName,
ConnectionFactory connectionFactory,
Service1 service1,
Service2 service2) {
SimpleMessageListenerContainer consumerListener = new SimpleMessageListenerContainer(connectionFactory);
consumerListener.addQueueNames(queueName);
return IntegrationFlows.from(Amqp.inboundAdapter(consumerListener))
.transform(s -> s, ConsumerEndpointSpec::transactional)
.<Message<?>, String>route(HeadersUtil::getType, m -> m
.subFlowMapping(Type.SERVICE_1, sf -> sf.handle(service1::handleProcedure))
.subFlowMapping(Type.SERVICE_2, sf -> sf.handle(service2::handleProcedure)))
.get();
}
The signature of the method handleProcedure is as follows:
void handleProcedure(Message<?> message)
I expect to get headers of Message in the method handleProcedure, but I get exception now.
I think you didn't understand the stack trace properly.
Your void handleProcedure(Message<?> message) and its service1::handleProcedure method reference fully fits to the public B handle(MessageHandler messageHandler) { method signature in the IntegrationFlowDefinition.
Your problem is here:
.<Message<?>, String>route(HeadersUtil::getType,
Your HeadersUtil::getType expects a message, but the type for the lambda invocation is a payload which is String in your case.
This should work:
.<Message<?>, String>route(Message.class, HeadersUtil::getType,
The handle(GenericHandler<P>) version of .handle, which you are using, only gets the payload.
If you want to receive the complete message, you need to use a different overloaded .handle, such as handle("service1", "handleProcedure") or .handle(service1, "handleProcedure").

Spring Integration : SimpleAsyncTaskExecutor

I have read another issue in this website like this but I don't get how to resolve the issue.
Spring Integration: Application leaking SimpleAsyncTaskExecutor threads?
My error is similar to previous link
SimpleAsyncTaskExecutor-2327" - Thread t#2405
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <7a224c1> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel.receive(GenericMessagingTemplate.java:199)
at org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel.receive(GenericMessagingTemplate.java:192)
at org.springframework.messaging.core.GenericMessagingTemplate.doReceive(GenericMessagingTemplate.java:130)
at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:157)
at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessagingTemplate.sendAndReceive(AbstractMessagingTemplate.java:42)
at org.springframework.integration.core.MessagingTemplate.sendAndReceive(MessagingTemplate.java:97)
at org.springframework.integration.core.MessagingTemplate.sendAndReceive(MessagingTemplate.java:38)
at org.springframework.messaging.core.AbstractMessagingTemplate.convertSendAndReceive(AbstractMessagingTemplate.java:79)
at org.springframework.messaging.core.AbstractMessagingTemplate.convertSendAndReceive(AbstractMessagingTemplate.java:70)
at org.springframework.integration.gateway.MessagingGatewaySupport.doSendAndReceive(MessagingGatewaySupport.java:449)
I'm using
spring-integration-java-dsl-1.2.3.RELEASE
spring-integration-ip-4.3.17.RELEASE
spring-integration-http-4.3.17.RELEASE
My scenario is the next: I receive a message throught a Api Controller and this message is sent a un TCP socket.
I have defined a MessageGateway interface
#MessagingGateway(defaultRequestChannel = "toTcp.input")
public interface MessageTcpGateway {
#Gateway
public ListenableFuture<Void> sendTcpChannel(byte[] data,
#Header("connectionId") String connectionId );
}
After I use this interface in a service class like this:
public void sendMessageTcpGateway(final String bridgeId,final String connectionId, final byte[] message) {
LOGGER.debug("sendMessageTcpGateway connectionId:{} - message:{}", connectionId, message);
if (holder.existsConnection(connectionId)!=null) {
gatewayTcp.sendTcpChannel(message,connectionId);
} else {
LOGGER.error("Not send message connectionId:{} - message:{}", connectionId, message);
}
}
Why the thread is waiting ?. Is my process waiting for any kind of sign and I'm not considered?. I guess that if the connection is not available or whatever kind of error, spring-integration will throw a exception
How can i resolve this issue?
I wonder why don't follow recommendations from that SO thread...
The ListenableFuture<Void> is a bottleneck in your solution. As you see by stack trace you have there doSendAndReceive(), but I guess your target solution is really one-way and doesn't return anything for the replyChannel in headers.
You should consider to have just plain void return type and an ExecutorChannel downstream.
Unfortunately we can't detect such a situation from the framework side since a Future return type of the gateway method indicates that you are going to perform request-reply async manner. In your case it is just an async request, nothing more.

How to Nak a ServiceStack RabbitMQ message within the RegisterHandler?

I'd like to be able to requeue a message from within my Service Endpoint that has been wired up through the RegisterHandler method of RabbitMQ Server. e.g.
mqServer.RegisterHandler<OutboundILeadPhone>(m =>
{
var db = container.Resolve<IFrontEndRepository>();
db.SaveMessage(m as Message);
return ServiceController.ExecuteMessage(m);
}, noOfThreads: 1);
or here.
public object Post(OutboundILeadPhone request)
{
throw new OutBoundAgentNotFoundException(); // added after mythz posted his first response
}
I don't see any examples how this is accomplished, so I'm starting to believe that it may not be possible with the ServiceStack abstraction. On the other hand, this looks promising.
Thank you, Stephen
Update
Throwing an exception in the Service does nak it, but then the message is sent to the OutboundILeadPhone.dlq which is normal ServiceStack behavior. Guess what I'm looking for is a way for the message to stay in the OutboundILeadPhone.inq queue.
Throwing an exception in your Service will automatically Nak the message. This default exception handling behavior can also be overridden with RabbitMqServer's RegisterHandler API that takes an Exception callback, i.e:
void RegisterHandler<T>(
Func<IMessage<T>, object> processMessageFn,
Action<IMessage<T>, Exception> processExceptionEx);
void RegisterHandler<T>(
Func<IMessage<T>, object> processMessageFn,
Action<IMessage<T>, Exception> processExceptionEx,
int noOfThreads)

Spring Integration: exception on receiving message

Within our Spring Integration application, when a message is received, an exception is thrown and the transformer does not receive the message.
I think it could be due to the content of the message, as messages of type String are processed correctly.
The following is the signature of the transformer for this message type:
#Transformer
public String transform(Message inboundMessage){
Have also tried
#Transformer
public String transform(Byte[] inboundMessage){
but in both instances, the following exception is thrown:
2013-10-14 07:21:33,547 D|DefaultMessageListenerContainer |Received message of type [class com.solacesystems.jms.message.SolTextMessage] from consumer [Cached JMS MessageConsumer: com.solacesystems.jms.SolQueueReceiver#13b5500] of session [Cached JMS Session: com.solacesystems.jms.SolSession#1fa0f19]
2013-10-14 07:21:33,579 W|DefaultMessageListenerContainer |Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: UTF-8 format error
at com.solacesystems.common.util.UTF8Util.getStringFromUTF8(UTF8Util.java:272) ~[sol-common-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.load(SolTextMessage.java:82) ~[sol-jms-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.getText(SolTextMessage.java:69) ~[sol-jms-6.0.0.146.jar:na]
at org.springframework.jms.support.converter.SimpleMessageConverter.extractStringFromMessage(SimpleMessageConverter.java:177) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.support.converter.SimpleMessageConverter.fromMessage(SimpleMessageConverter.java:94) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.integration.jms.ChannelPublishingJmsMessageListener.onMessage(ChannelPublishingJmsMessageListener.java:266) ~[spring-integration-jms-2.2.0.RC2.jar:na]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:537) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:497) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:468) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:326) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:264) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1071) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1063) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:960) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at java.lang.Thread.run(Unknown Source) [na:1.6.0_06]
2013-10-14 07:21:33,579 D|SolMessageConsumer |Entering receive(), timeout: 1000
2013-10-14 07:21:34,391 D|SolMessageConsumer |Leaving receive()
Any help is greatly appreciated
It's not getting as far as the transformer. The problem is within the Solace library when Spring is calling getText...
java.lang.RuntimeException: UTF-8 format error
at com.solacesystems.common.util.UTF8Util.getStringFromUTF8(UTF8Util.java:272) ~[sol-common-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.load(SolTextMessage.java:82) ~[sol-jms-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.getText(SolTextMessage.java:69) ~[sol-jms-6.0.0.146.jar:na]
at org.springframework.jms.support.converter.SimpleMessageConverter.extractStringFromMessage(SimpleMessageConverter.java:177) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
...
It looks like the library thinks the data is in UTF-8 format when it's not.

Resources