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.
Related
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.
I have a REST API which receives a POST request from a client application.
#Autowired
private EdiTranslationEvents.TransformToString transformToString;
#PostMapping("/testPost")
#ResponseStatus(HttpStatus.OK)
public String testPostLogging(#RequestBody Student student) {
log.info("In controller....");
System.out.println(this.transformToString.objectToInputGateway(student));
return "testPost logging";
}
As you can see, in the controller, I have an autowired messaging gateway and I am using it to send data to a channel.
#Configuration
#EnableIntegration
public class EdiTranslationEvents {
#Component
#MessagingGateway
public interface TransformToString {
#Gateway(requestChannel = "inputObjectChannel")
String objectToInputGateway(Student student);
}
#Bean
public IntegrationFlow inputObjectString() {
return IntegrationFlows.from(inputObjectChannel())
.transform(Transformers.objectToString())
.log(LoggingHandler.Level.DEBUG, "com.dash.Logger")
.get();
}
}
When I send data to the REST API, the API just hangs. The gateway does not return anything. I have the return type specified, and I was assuming that the gateway creates a temporary reply channel and sends the response to that channel.
However, I am not doing anything in the DSL configuration for creating or managing a reply. So, how do I send a reply back to the reply channel from the DSL flow?
Your current flow does not return a value, you are simply logging the message.
A terminating .log() ends the flow.
Delete the .log() element so the result of the transform will automatically be routed back to the gateway.
Or add a .bridge() (a bridge to nowhere) after the log and it will bridge the output to the reply channel.
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.
A POJO Message.java is to be Converted to JSON(JSON is to be sent to pubsub Topic,using Spring Integration MessageChannels.),using following:
#Bean
#Transformer(inputChannel = "pubsubOutputChannel", outputChannel = "handleOutChannel")
public ObjectToJsonTransformer transformOut() {
return new ObjectToJsonTransformer();
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(Messages msg);
}
#Bean
#ServiceActivator(inputChannel = "handleOutChannel")
public MessageHandler messageSender(PubSubOperations pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, "TestTopic");
}
When i call sendToPubsub() with an instance of Message.java with required properties set,i get an error "Null".
Is serviceActivator not able to receive the required data?
Any suggestions to fix this?.
Yes, it can't do that because you just don't tell it to do that.
Your gateway is configured for this:
#MessagingGateway(defaultRequestChannel = "handleOutChannel")
But that is not an input channel for the ObjectToJsonTransformer. So, whatever you send over that gateway is going directly to the messageSender service activator.
Try to configure your gateway like this:
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
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())