Continuation of Error handling - no output-channel or replyChannel header available,
I am returning ResponseEntity from the transformer of ExpressionEvaluatingRequestHandlerAdvice failureChannel. when I debug I can see
ResponseEntity<Object> response = <409 CONFLICT Conflict,com.practice.integration.commons.error.AdapterErrorResponse#4d5a370b,[]> and its body(AdapterErrorResponse POJO) has HttpStatus status, List<AdapterError> errors which has populated correct value that I want and as per Artem Bilan's suggestion for preserving request message headers I am sending that response as MessageBuilder.withPayload(response).copyHeaders(message.getPayload().getFailedMessage().getHeaders()).build()
and I have also configured output channel on the transformer but it still does not show the above response as a part of http response payload, output channel I have is same as reply channel of the inbound gateway. could you please help here?
and I have one more external call following the above, there also I have used different transformer to handle exception and I am sending similar ResponseEntity from there , it works fine there and send response to the reply channel of the inbound gateway. Only difference is I am not using ExpressionEvaluatingRequestHandlerAdvice for the second outbound gateway.
Do you think I should do something extra with handling response using ExpressionEvaluatingRequestHandlerAdvice or am I missing anything on the first outbound gateway?
You probably didn't do this: ExpressionEvaluatingRequestHandlerAdvice.setTrapException(true);
Here is a working test, it is not HTTP based, but approach is exactly the same for any inbound request-reply gateway:
#SpringJUnitConfig
public class So74658669Tests {
#Autowired
InputGateway inputGateway;
#Test
void errorHandlerResultPropagatedBackToGateway() {
assertThat(this.inputGateway.sendAndReceive("test"))
.isEqualTo("Request failed for: test");
}
#Configuration
#EnableIntegration
#Import(InputGateway.class)
public static class TestConfiguration {
#Bean
MessageChannel outputChannel() {
return new DirectChannel();
}
#ServiceActivator(inputChannel = "inputChannel", outputChannel = "outputChannel", adviceChain = "requestHandlerAdvice")
String requestAndReply(String payload) {
throw new RuntimeException("failure");
}
#Bean
ExpressionEvaluatingRequestHandlerAdvice requestHandlerAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setFailureChannelName("errorHandlerChannel");
advice.setTrapException(true);
return advice;
}
#Transformer(inputChannel = "errorHandlerChannel", outputChannel = "outputChannel")
Message<String> errorHandler(Message<MessagingException> errorMessage) {
return MessageBuilder.withPayload("Request failed for: " + errorMessage.getPayload().getFailedMessage().getPayload())
.copyHeaders(errorMessage.getPayload().getFailedMessage().getHeaders())
.build();
}
}
#MessagingGateway
interface InputGateway {
#Gateway(requestChannel = "inputChannel", replyChannel = "outputChannel")
String sendAndReceive(String payload);
}
}
By the way there is no need in that outputChannel at all if you don't do any extra work on reply. The framework just find a replyChannel header and sends reply message directly to the input gateway.
Related
After a messsage is sent, it gets published to Kafka topic but the Message from KafkaSuccessTransformer does not return back to the REST controller. I am trying to return the message as-is if sent successfully but nothing after Kafka handler seems to be invoked.
#MessagingGateway
public interface MyGateway<String, Message<?>> {
#Gateway(requestChannel = "enrollChannel")
Message<?> sendMsg(#Payload String payload);
}
------------------------
#RestController
public class Controller {
MyGateway<String, Message<?>> myGateway;
#PostMapping
public Message<?> send(#RequestBody String request) throws Exception {
Message<?> resp = myGateway.sendMsg(request);
log.info("I am back"); // control doesn't come to this point
return resp;
}
}
--------------------------
#Component
public class MyIntegrationFlow {
KafkaSuccessTransformer stransformer;
#Bean
public MessageChannel enrollChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel kafkaSuccessChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow enrollIntegrationFlow() {
return IntegrationFlows.from("enrollChannel")
//another transformer which turns the string to Message<?>
.handle(Kafka.outboundChannelAdapter(kafkaTemplate) //kafkaTemplate has the necesssary config
.topic("topic1")
.messageKey(messageKeyFunction -> messageKeyFunction.getHeaders()
.get("key1")
.sendSuccessChannel("kafkaSuccessChannel"));
}
#Bean
public IntegrationFlow successfulKafkaSends() {
return f -> IntegrationFlows.from("kafkaSuccessChannel").transform(stransformer);
}
}
--------------
#Component
public class KafkaSuccessTransformer {
#Transformer
public Message<?> transform(Message<?> message) {
log.info("Message is sent to Kafka");
return message; //control comes here but does not return to REST controller
}
}
Channel adapters are for one-way traffic; there is no result.
Add a publishSubscribe channel with two subflows; the second one can be just a bridge to nowhere - .bridge() ends the flow. It will then return the outbound message to the gateway.
See https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-subflows
Per Artem:
Something is off in the configuration or code. The logic is like this: processSendResult(message, producerRecord, sendFuture, getSendSuccessChannel());. Then: getMessageBuilderFactory().fromMessage(message). So, the replyChannel header is present in this "success" message. Therefore that transform(stransformer) should really produce its return to the replyChannel for a gateway in the beginning. Only the problem could be in the KafkaSuccessTransformer code where it does not copy request message headers for reply message. Please, share its whole code.
I'm new with Spring Integration. I need to implement a Messaging Gateway with a returning value. In order to continue some processing asynchronously after executing some synchronous steps. So I made 2 activators
#Slf4j
#MessageEndpoint
public class Activator1 {
#ServiceActivator(inputChannel = "asyncChannel")
public void async(){
log.info("Just async message");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
log.error("I don't want to sleep now");
}
}
}
and
#Slf4j
#MessageEndpoint
public class Activator2 {
#ServiceActivator(inputChannel = "syncChannel")
public ResponseEntity sync(){
try {
Thread.sleep(500);
return ResponseEntity.ok("Return Http Message");
} catch (InterruptedException e) {
log.error("I don't want to sleep");
}
return ResponseEntity.badRequest().build();
}
}
The pipeline
#Configuration
public class Pipeline {
#Bean
MessageChannel asyncChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel syncChannel() {
return MessageChannels.direct().get();
}
}
the gateway
#MessagingGateway
public interface ReturningGateway {
#Gateway(requestChannel = "asyncChannel", replyChannel = "syncChannel")
public ResponseEntity getSyncHttpResponse();
}
And Controller
#Slf4j
#RestController
#RequestMapping("/sync")
public class ResponseController {
#Autowired
ReturningGateway returningGateway;
#PostMapping("/http-response")
public ResponseEntity post() {
return returningGateway.getSyncHttpResponse();
}
}
So I'm not sure if thats the correct way to do what I want to do
Can you give me hand?
Let me try to explain some things first of all!
#Gateway(requestChannel = "asyncChannel", replyChannel = "syncChannel")
The requestChannel is where a gateway sends a message. But since you don't have any arguments in the gateway method and there is no a payloadExpression, the behavior is to "receive" from that channel. See docs for more info: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway-calling-no-argument-methods.
The replyChannel is where to wait for a reply, not send. In most cases a gateway relies on the replyChannel header for correlation. The request-reply pattern in messaging. We need an explicit replyChannel if it is a PublishSubscribeChannel to track a reply somehow or when we deal with the flow which we can't modify to rely on the replyChannel header. See the same gateway chapter in the docs.
Your use-case is not clear for me: you say async continuation, but at the same time the return from your gateway contract looks like a result of that sync() method. From here, please make yourself familiar with the gateway contract and then come back to us with refreshed vision for your solution.
I'm stuck on a seemingly simple task, but am out of ideas. I have a TcpInboundGateway attached to a TcpNetServerConnectionFactory that passes requests to a Service Activator. The Service Activator simply puts the message back on the Gateway's reply channel. I want the Gateway to return that message over the connection.
When I run the test, the message makes it to the Service Activator successfully. I know this because the Service Activator prints the message payload before returning it. I also know the Service Activator is putting the message on the right channel because I have an interceptor on that channel which also prints the message.
The problem seems to be that the Gateway isn't reading off of that channel, even though I set it in setReplyChannel(). I can also see this in the logs:
Adding {bridge:null} as a subscriber to the 'testResponseChannel' channel
which makes me suspect that the message is just getting sent to the null channel instead of being picked up by my Gateway.
Here's the configuration:
#Bean
public TcpNetServerConnectionFactory testServerFactory() {
TcpNetServerConnectionFactory testServerFactory = new TcpNetServerConnectionFactory(0);
testServerFactory.setSerializer(TcpCodecs.lengthHeader2());
testServerFactory.setDeserializer(TcpCodecs.lengthHeader2());
return testServerFactory;
}
#Bean
public DirectChannel testRequestChannel() {
return new DirectChannel();
}
#Bean
public DirectChannel testResponseChannel() {
DirectChannel testResponseChannel = new DirectChannel();
testResponseChannel.addInterceptor(channelInterceptor());
return testResponseChannel;
}
#Bean
public TcpInboundGateway gateway() {
TcpInboundGateway gateway = new TcpInboundGateway();
gateway.setConnectionFactory(testServerFactory());
gateway.setRequestChannel(testRequestChannel());
gateway.setReplyChannel(testResponseChannel());
return gateway;
}
#Bean
#ServiceActivator(inputChannel = "testRequestChannel", outputChannel = "testResponseChannel")
public EchoHandler echoHandler() {
return new EchoHandler();
}
Here's my POJO Service Activator:
public class EchoHandler {
public Message<String> echoMessage(Message<String> request) {
System.out.println(request.getPayload());
return request;
}
}
And here's the error, which happens right after the message passes through the interceptor:
Unexpected message - no endpoint registered with connection interceptor: localhost:6060:59848:3c6c3cff-c697-4fc9-b4e3-9ea14508cec7 - GenericMessage [payload=byte[3], headers={ip_tcp_remotePort=6060, ip_connectionId=localhost:6060:59848:3c6c3cff-c697-4fc9-b4e3-9ea14508cec7, ip_localInetAddress=/127.0.0.1, ip_address=127.0.0.1, id=93c75664-54db-c93e-ab3a-3e06b1e4b626, ip_hostname=localhost, timestamp=1556828832645}]
To react properly for the reply from the server, your client must be a request-response capable. For this purpose Spring Integration IP modules suggests a TcpOutboundGateway. This way a TcpListener is going to be registered on the TcpConnection and ready to parse and handle replies messages on the socket.
I am trying to use spring integration for send mqtt messages to a broker and I am trying to use the gateway interface.
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
//set the factory details
return factory:
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("randomString", mqttClientFactory());
//set handler details
messageHandler.setDefaultTopic(topic);
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
private interface MyGateway {
void sendToMqtt(String data);
}
My question is: If I want to use the gateway handler to send messages to different topics how would I do that without having to create an adapter for each topic ?
Thanks.
Hope I formulated my question clearly and the code is properly formatted.
You need to set the target topic in a message header.
Here is one way to do that...
void sendToMqtt(String data, #Header(MqttHeaders.TOPIC) String topic);
The gateway proxy will assemble the message with the header, which is then used by the outbound adapter.
I am trying to write a simple message flow using Spring Integration v4's DSL APIs which would look like this:
-> in.ch -> Processing -> JmsGatewayOut -> JMS_OUT_QUEUE
Gateway
<- out.ch <- Processing <- JmsGatewayIn <- JMS_IN_QUEUE
With the request/response being asynchronous, when I inject a message via the initial Gateway, the message goes all the way to JMS_OUT_QUEUE. Beyond this message flow, a reply message is put back into JMS_IN_QUEUE which it is then picked up by JmsGatewayIn. At this point, the message is Processed and placed into out.ch (I know the response gets to out.ch because I have a logger interceptor there which logs the message being placed there) but, the Gateway never receives the response.
Instead of a response, the system outside of this message flow which picked up the message from JMS_OUT_QUEUE and placed the response in JMS_IN_QUEUE, receives a javax.jms.MessageFormatException: MQJMS1061: Unable to deserialize object on its own JmsOutboundgateway (I think it is failing to deserialize a jms reply object from looking at the logs).
I have clearly not got something configured correctly but I don't know exactly what. Does anyone know what I am missing?
Working with spring-integration-core-4.0.3.RELEASE, spring-integration-jms-4.0.3.RELEASE, spring-integration-java-dsl-1.0.0.M2, spring-jms-4.0.6.RELEASE.
My Gateway is configured as follows:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyChannel = "out.ch",
replyTimeout = 45000)
AResponse process(ARequest request);
}
My Integration flow is configured as follows:
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Bean(name = "out.ch")
public DirectChannel outCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.correlationKey("JMSCorrelationID")
.get();
}
#Bean
public IntegrationFlow responseFlow() {
return IntegrationFlows.from(Jms.inboundGateway(mqConnectionFactory)
.destination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.channel("out.ch")
.get();
}
}
Thanks for any help on this,
PM.
First of all your configuration is bad:
Since you start the flow from WsGateway#process you really should wait reply there.
The gateway's request/reply capability is based on TemporaryReplyChannel, which is placed to the headers as non-serializable value.
As long as you wait rely on that gateway, actually there is no reason to provide the replyChannel, if you aren't going to do some publish-subscribe logic on the reply.
As you send message to the JMS queue, you should understand that consumer part might be a separete remote application. And the last one might know nothing about your out.ch.
The JMS request/reply capability is really based on JMSCorrelationID, but it isn't enough. The one more thing here is a ReplyTo JMS header. Hence, if you are going to send reply from the consumer you should really just rely on the JmsGatewayIn stuff.
So I'd change your code to this:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyTimeout = 45000)
AResponse process(ARequest request);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.replyDestination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.get();
}
}
Let me know, if it is appropriate for you or try to explian why you use two-way gateways for one one-way cases. Maybe Jms.outboundAdapter() and Jms.inboundAdapter() are more good for you?
UPDATE
How to use <header-channels-to-string> from Java DSL:
.enrichHeaders(e -> e.headerChannelsToString())