Spring Integration: can one call a gateway from within a header enricher? - spring

I need to make a number of calls to gateways and to then place the return payloads into headers. These gateway calls are really sending a message to a channel behind which there is a chain. It currently looks like this:
<int:channel id = "call_api_a" />
<int:chain input-channel = "call_api_a" >
...
</int:chain>
<int:channel id = "call_api_b" />
<int:chain input-channel = "call_api_b" >
...
</int:chain>
<int:gateway request-channel="call_api_a" />
<int:header-enricher>
<int:header name="a" expression="payload" />
</int:header-enricher>
<int:gateway request-channel="call_api_b" />
<int:header-enricher>
<int:header name="b" expression="payload" />
</int:header-enricher>
Is there an elegant way to embed the call to the gateway inside of the header-enricher?
Something like:
<int:header-enricher>
<int:header name="a">
<int:gateway request-channel="call_api_a" />
</int:header>
</int:header-enricher>
I know that the above doesn't work but gives the sense of what I'd like to achieve.
Thanks for any pointers and best regards.
-aljo

Not <gateway>, but like this:
<int:header-enricher>
<int:header name="a" ref="myGateway" method="gatewayMethod"/>
</int:header-enricher>
The <gateway> is bean based on some service interface.
You just point in the <header> definition to that bean and its method to be called against your payload or message.
There is some explanation in the docs: https://docs.spring.io/spring-integration/docs/current/reference/html/message-transformation.html#header-enricher

It looks like the "additional gateway definition" is the way to go on this one. Thanks, #artem-bilan, for pointing me in the right direction.
I want to call to my chain-channel directly from a header-enricher.
If my chain-channel is:
<int:channel id = "call_api_a" />
<int:chain input-channel = "call_api_a" >
...
</int:chain>
Define somewhere in the Java add your gateway interface.
package com.app.wonderful;
#FunctionalInterface
public interface MyChainChannelGatewayMethod {
Message<?> callChainChannel(Message<?> message) throws MessageException;
}
Define a gateway that points to your chain-channel:
<int:gateway default-request-channel = "call_api_a"
id = "call_api_a_GW"
service-interface. = "com.app.wonderful.MyChainChannelGatewayMethod" />
Then you can call your gateway in-line from the SpEL expression in the header-enricher:
<int:header-enricher >
<int:header name="a" expression="#call_api_a_GW(#root).payload" />
</int:header>
Does the trick for me. Thanks again.

#artem-bilan's suggestion to use <enricher> instead of <header-enricher> leads to a solution that obviates the Java interface signatures by addressing directly a channel.
<int:channel id = "call_api_a" />
<int:chain input-channel = "call_api_a" >
...
</int:chain>
We can call this channel from an <enricher>. Here, for simplicity, the <enricher> component is part of a chain, but that is not a requirement:
<int:chain ...>
<int:enricher request-channel="call_api_a"
request-payload-expression="payload" >
<int:header name="a" expression="payload" />
</int:enricher>
</int:chain>
Thanks again for the pointers.

Related

Send and receive data using Spring Integration TCP IP socket

I am creating a simple spring boot application(PoC) to send product id's (string) from client to server over socket using Spring integration TCP. If the server is hit with correct product data, the server will respond back with the product details which I need to print. Just need to establish a connection and get the response by sending proper data.
Please tell me what are the classes I am supposed to implement? outbound/inboud gateways,messsage channels, tcplisteners? Should I go with xml configuration or annotations? I am new to SI and would be of great help if you could give me an idea on how to implement it.
Here is my updated integration xml.
<int-ip:tcp-connection-factory id="client" type="client" host="XX.XX.XX.99" port="9XXX" single-use="true" so-timeout="10000" />
<int:channel id="input"/>
<int-ip:tcp-outbound-gateway id="outGateway" request-channel="input" reply-channel="clientBytes2StringChannel" connection-factory="client" request-timeout="10000" reply-timeout="10000"/>
<int:object-to-string-transformer id="clientBytes2String" input-channel="clientBytes2StringChannel"/>
<int:service-activator input-channel="clientBytes2StringChannel" ref="echoService" method="test"/>
<bean id="echoService" class="org.springframework.integration.samples.tcpclientserver.EchoService"/>
<int:channel id="toSA"/>
But this still prints the echoed result. Also, when I call getHost on abstractClientConnectionfactory from main class, its showing "localhost". How can I confirm if the connection is active?
<int:gateway id="gw"
service-interface="org.springframework.integration.samples.tcpclientserver.SimpleGateway"
default-request-channel="input"/>
<int-ip:tcp-connection-factory id="client" type="client" host="xx.xx.xx.99"
port="9xxx"
single-use="false" so-timeout="300000" using-nio="false"
so-keep-alive="true" serializer="byteArrayRawSerializer"
deserializer="byteArrayRawSerializer"/>
<bean id="byteArrayRawSerializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer" />
<int-ip:tcp-outbound-gateway id="outGateway"
request-channel="input"
reply-channel="responseBytes2StringChannel"
connection-factory="client"
request-timeout="10000"
reply-timeout="10000" />
<int:object-to-string-transformer id="clientBytes2String"
input-channel="responseBytes2StringChannel" output-channel="toSA"/>
<int:service-activator input-channel="toSA" ref="echoService" method="test"/>
<bean id="echoService" class="org.springframework.integration.samples.tcpclientserver.EchoService"/>
<int:channel id="toSA"/>
<int:transformer id="errorHandler" input-channel="errorChannel" expression="payload.failedMessage.payload + ':' + payload.cause.message"/>
<int:channel id="responseBytes2StringChannel"></int:channel>
**** Update SI xml ****
<int:gateway id="gw"
service-interface="org.springframework.integration.samples.tcpclientserver.SimpleGateway"
default-request-channel="objectIn"/>
<int:channel id="objectIn" />
<int-ip:tcp-connection-factory id="client"
type="client"
host="xx.xx.xx.99"
port="9xxx"
single-use="true"
so-timeout="50000"
using-nio="false"
so-keep-alive="true"/>
<!--
serializer="byteArrayLengthSerializer"
deserializer="byteArrayLengthSerializer"
<bean id="byteArrayLengthSerializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer " />
-->
<int:payload-serializing-transformer input-channel="objectIn" output-channel="objectOut"/>
<int-ip:tcp-outbound-gateway id="outGateway"
request-channel="objectOut"
reply-channel="bytesIn"
connection-factory="client"
request-timeout="10000"
reply-timeout="10000"
/>
<int:payload-deserializing-transformer input-channel="bytesIn" output-channel="objectOut" />
<int:object-to-string-transformer id="clientBytes2String"
input-channel="objectOut" output-channel="toSA"/>
<int:service-activator input-channel="toSA" ref="echoService" method="test"/>
<bean id="echoService" class="org.springframework.integration.samples.tcpclientserver.EchoService"/>
<int:channel id="objectOut"/>
<int:channel id="toSA"/>
<int:channel id="bytesIn"/>
I suggest you to go the Documentation route first: https://docs.spring.io/spring-integration/docs/current/reference/html/ip.html to investigate what Spring Integration provides for you in regards of TCP/IP. Then it would be great to jump into samples project to see what we suggest for configuration and usage options: https://github.com/spring-projects/spring-integration-samples

How to log request data along with response data in http:outbound-gateway

Using spring integration, I am reading from a queue and then calling a REST service using http:outbound-gateway. The code works fine. But I want to relate the logs such that for a given request key field , so and so response is received. Otherwise, it is very difficult to pinpoint for which request the response is received, when there are thousands of messages flowing through the queue.
My JSON request for the service call is as follows:
{"ID":"123","status":"A"}
my JSON Response from the service call is as follows:
{"transactionStatus":"Success"}
I want to do logging such that "ID:123" has got a reponse transactionStatus as "Success".
Please help me with the code how I can achieve this. Please let me know if you need further details.
Thanks in advance.
<int:object-to-json-transformer input-channel = "gcmRequestChannel" output-channel="RESTSrvcChannel"></int:object-to-json-transformer>
<int:header-enricher id = "restenricher" input-channel = "RESTSrvcChannel" output-channel = "RESTSrvcChannel2">
<int:header name="contentType" value="application/json"/>
<int:header name="SPApikey" value="${throttler.SPApikey}" />
</int:header-enricher>
<http:outbound-gateway id="gcmrestHttpOutboundGateway" request-channel="RESTSrvcChannel2" reply-channel="nullChannel"
extract-request-payload="true"
url="${throttler.url}"
header-mapper="headerMapper"
http-method="POST"
expected-response-type="java.lang.String"
>
</http:outbound-gateway>
<beans:bean id="headerMapper"
class="org.springframework.integration.http.support.DefaultHttpHeaderMapper">
<beans:property name="inboundHeaderNames" value="*" />
<beans:property name="outboundHeaderNames" value="HTTP_REQUEST_HEADERS,SPApikey" />
<beans:property name="userDefinedHeaderPrefix" value="" />
</beans:bean>
Since Spring Integration deals with the Message object in its components and via channels in between, there is a nice solution like supply your important information into the headers and they will be available in the replyMessage for your logging purpose:
<header-enricher>
<header name="originalPayload" expression="payload"/>
</header-enricher>
I have coded as suggested by you. Thanks for your response.
<int:header-enricher id = "gcmrestenricher" input-channel = "gcmRESTSrvcChannel" output-channel = "gcmRESTSrvcChannel2">
<int:header name="contentType" value="application/json"/>
<int:header name="SPApikey" value="${throttler.SPApikey}" />
<int:header name="JSONPayload" expression="payload"/>
</int:header-enricher>
<http:outbound-gateway id="gcmrestHttpOutboundGateway" request-channel="gcmRESTSrvcChannel2" reply-channel="gcmRESTSrvcOutputChannel"
extract-request-payload="true"
url="${throttler.gcmurl}"
header-mapper="gcmheaderMapper"
http-method="POST"
expected-response-type="java.lang.String"
>
</http:outbound-gateway>
<beans:bean id="gcmheaderMapper"
class="org.springframework.integration.http.support.DefaultHttpHeaderMapper">
<beans:property name="inboundHeaderNames" value="*" />
<beans:property name="outboundHeaderNames" value="HTTP_REQUEST_HEADERS,SPApikey,JSONPayload" />
<beans:property name="userDefinedHeaderPrefix" value="" />
</beans:bean>
<beans:bean id="logmsg" class = "com.MLAServiceActivator"></beans:bean>
<int:service-activator requires-reply="false" input-channel="gcmRESTSrvcOutputChannel" ref="logmsg" method="ResponseLogging"></int:service-activator>
#ServiceActivator
public void ResponseLogging(Message<String> message)
{
logger.info("The response message is:"+message.getPayload()+ " for request message:"+message.getHeaders().get("JSONPayload"));
}

spring integration tcp server send message to tcp client

I want to write a perfect client-server application is Spring Integration. I have a part where server receive a message and send response to client.
I would like to send to client a message with some information from time to time also. I set a header with connectionId received in TcpConnectionEvent but there is nothing happend. There is my code bellow. I stack with this problem from few days. Thanks for any halp!
<!-- CLIENT SIDE -->
<int:gateway id="gw"
service-interface="com.app.hos.service.client.Gateway"
default-request-channel="input"/>
<int-ip:tcp-connection-factory id="client"
type="client"
host="localhost"
port="14020"
single-use="false"
so-timeout="10000"
/>
<int:channel id="input" />
<int-ip:tcp-outbound-gateway id="outGateway"
request-channel="transformChannel"
reply-channel="reply"
connection-factory="client"
request-timeout="10000"
reply-timeout="10000"
/>
<int:channel id="transformChannel" />
<int:channel id="reply" datatype="java.lang.String" />
<!-- TRANSFORMERS -->
<int:transformer id="testTransformer" ref="testTransformerBean" input-channel="input"
method="transform" output-channel="transformChannel"/>
<bean id="testTransformerBean" class="com.app.hos.service.integration.Transformer" />
<!-- SERVER SIDE -->
<bean id="connectionSerializeDeserialize" class="com.app.hos.service.integration.ByteArrayToStringConverter"/>
<int-ip:tcp-connection-factory id="hosServer"
type="server"
port="14020"
serializer="connectionSerializeDeserialize"
deserializer="connectionSerializeDeserialize"
using-nio="true"/>
<int-ip:tcp-inbound-gateway id="inputHosGateway"
connection-factory="hosServer"
request-channel="toServerChannel"
error-channel="errorChannel"/>
<int:channel id="toServerChannel"/>
<int:channel id="errorChannel"/>
<int:channel id="inputChannel" />
<int:service-activator input-channel="toServerChannel"
ref="server"
method="serverTest"/>
<bean id="server"
class="com.app.hos.service.server.Server" />
<!-- TCP EVENTS -->
<int:service-activator input-channel="eventChannel"
ref="event"
method="eventTest"/>
<bean id="event"
class="com.app.hos.service.integration.Event" />
<int:channel id="eventChannel"/>
<int-event:inbound-channel-adapter channel="eventChannel"
event-types="org.springframework.integration.ip.tcp.connection.TcpConnectionEvent"/>
Transforemr where I set connectionId:
#Autowired
public Event event;
public Message<String> transform(Message<String> msg) {
Message<String> newMessage = MessageBuilder.withPayload(msg.getPayload())
.setHeader(IpHeaders.CONNECTION_ID, event.getConncetionId())
.copyHeadersIfAbsent(msg.getHeaders())
.build();
return newMessage;
}
MVC Controller where I try send a message by a gateway:
#Autowired
public TestController(Gateway gateway) {
this.gateway = gateway;
}
#RequestMapping(value = "/showTest", method=RequestMethod.GET)
public String showTestPage() {
return "test/sendMessageTest";
}
#RequestMapping(value = "/sendMessage", method=RequestMethod.GET)
public void sendMessage() {
gateway.send("Working!");
}
You can't use gateways when sending arbitrary data they are strictly for request/reply messaging.
In any case, you are sending to a completely different connection.
Instead of an inbound gateway, you need an inbound channel adapter and outbound channel adapter (sharing the server connection factory).
When you want to send arbitrary data (not part of a request/reply), send the message to the oubound channel adapter, with the connection id header set appropriately.
Thanks for help! That's the solution:
<int:channel id="input" />
<int-ip:tcp-outbound-channel-adapter id="outboundChannel"
channel="transformChannel"
connection-factory="hosServer" />
<int-ip:tcp-inbound-channel-adapter id="inboundChannel"
channel="toServerChannel"
connection-factory="hosServer"
/>
<int:channel id="transformChannel" />
<int:channel id="reply" datatype="java.lang.String" />
<!-- TRANSFORMERS -->
<int:transformer id="testTransformer" ref="testTransformerBean" input-channel="input"
method="transform" output-channel="transformChannel"/>
<bean id="testTransformerBean" class="com.app.hos.service.integration.Transformer" />
<!-- SERVER SIDE -->
<bean id="connectionSerializeDeserialize" class="com.app.hos.service.integration.ByteArrayToStringConverter"/>
<int-ip:tcp-connection-factory id="hosServer"
type="server"
port="14020"
serializer="connectionSerializeDeserialize"
deserializer="connectionSerializeDeserialize"
using-nio="true"/>
<int:channel id="toServerChannel"/>
<int:channel id="errorChannel"/>
<int:channel id="inputChannel" />
<int:service-activator input-channel="toServerChannel"
ref="server"
method="serverTest"/>
<bean id="server"
class="com.app.hos.service.server.Server" />
<!-- TCP EVENTS -->
<int:service-activator input-channel="eventChannel"
ref="event"
method="eventTest"/>
<bean id="event"
class="com.app.hos.service.integration.Event" />
<int:channel id="eventChannel"/>
<int-event:inbound-channel-adapter channel="eventChannel"
event-types="org.springframework.integration.ip.tcp.connection.TcpConnectionEvent"/>

Unable to route to Message endpoints using SpEL in spring router

I have a message-driven-channel-adapter which is listening to a jms queue 'inputqueue' to which an application is posting a json message in the below format.
{
"name": "Praveep",
"action": "Drink",
"age": "32",
"userid": "pk",
"details": [{
"state": "karnataka",
"country": "India",
}]
}
Inside my applicationContext file I have defined a router which based on the action will point the message to the appropriate queue.
<int:channel id="processEmpChannel">
<int:queue />
</int:channel>
<int:channel id="drink">
<int:queue />
</int:channel>
<int:channel id="eat">
<int:queue />
</int:channel>
<int:channel id="sleep">
<int:queue />
</int:channel>
<int:router input-channel="processEmpChannel" expression="$.action">
<int:mapping value="drink" channel="drink" />
<int:mapping value="eat" channel="eat" />
<int:mapping value="sleep" channel="sleep" />
</int:router>
<int:service-activator input-channel="drink"
ref="springExample" method="handleDrink">
<int:poller ref="poller"></int:poller>
</int:service-activator>
<int:service-activator input-channel="eat"
ref="springExample" method="handleEat">
<int:poller ref="poller"></int:poller>
</int:service-activator>
<int:service-activator input-channel="sleep"
ref="springExample" method="handleSleep">
<int:poller ref="poller"></int:poller>
</int:service-activator>
In my Class file I have defined methods for each of the actions inside a public method. When I run the java program, I am getting the below error. Can someone let me know where I am going wrong in the spel expression.
Property or field '$' cannot be found on object of type 'org.springframework.messaging.support.GenericMessage' - maybe not public?
The JSON property accessor is not configured by default, add:
<int:spel-property-accessors>
<bean id="jsonPropertyAccessor" class="org.springframework.integration.json.JsonPropertyAccessor"/>
</int:spel-property-accessors>

how to design a fallback for a spring integration service activator with circuit breaker?

when a circuit breaker advice is triggered, this takes care not to overload the failing integration service.
How do i fallback to a different service activator when the circuit is open? and when the circuit becomes closed need to fallback to primary activator
Is there a way to implement this with the framework? or some custom code has to be written
You can use an inline gateway to catch the exceptions and send them to an error channel; you can then use a router on the error flow to decide how to proceed further. Hopefully the following is reasonably easy to follow...
<int:service-activator input-channel="inbound" ref="gw" />
<int:gateway id="gw" default-request-channel="toPrimary"
error-channel="failures"
default-reply-timeout="0" />
<int:service-activator input-channel="toPrimary" expression=" 1 / 0 ">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice">
<property name="threshold" value="2" />
<property name="halfOpenAfter" value="10000" />
</bean>
</int:request-handler-advice-chain>
</int:service-activator>
<!-- Error flow -->
<int:publish-subscribe-channel id="failures" />
<int:logging-channel-adapter id="failing" channel="failures" />
<int:recipient-list-router input-channel="failures" default-output-channel="nullChannel" >
<int:recipient channel="circuitFailures" selector-expression="payload.message.contains('Expression evaluation failed: 1 / 0') || payload.cause.message.contains('Circuit Breaker is Open')" />
</int:recipient-list-router>
<int:chain input-channel="circuitFailures" output-channel="loggingChannel">
<int:transformer expression="payload.failedMessage" />
<int:service-activator expression="payload + ' failed'" />
</int:chain>
<int:logging-channel-adapter id="loggingChannel" log-full-message="false" logger-name="tapInbound"
level="INFO" />

Resources