Dynamic URL for Custom Http-Client - Spring-XD - spring

We have written a custom Http-client for spring XD and planning to use this custom processor for invoking different APIs. However, We have a situation where we need to invoke two different API based on the payload. Apart from using dynamic-router (which writes to a named channel) is there a way we can set the URL dynamically for the http-client to invoke? Here is the current configuration.
<header-filter input-channel="input" output-channel="inputX"
header-names="x-death" />
<service-activator input-channel="inputX" ref="gw" />
<gateway id="gw" default-request-channel="toHttp"
default-reply-timeout="0" error-channel="errors" />
<beans:bean id="inputfields"
class="com.batch.httpclient.HTTPInputProperties">
<beans:property name="nonRetryErrorCodes" value="${nonRetryErrorCodes}" />
</beans:bean>
<beans:bean id="responseInterceptor"
class="com.batch.httpclient.ResponseInterceptor">
<beans:property name="inputProperties" ref="inputfields" />
</beans:bean>
<chain input-channel="errors" output-channel="output">
<transformer ref="responseInterceptor" />
</chain>
<int-http:outbound-gateway id='batch-http'
header-mapper="headerMapper" request-channel='toHttp' url-expression="${url}"
http-method="${httpMethod}" expected-response-type='java.lang.String'
charset='${charset}' reply-timeout='${replyTimeout}' reply-channel='output'>
</int-http:outbound-gateway>
<beans:bean id="headerMapper"
class="org.springframework.integration.http.support.DefaultHttpHeaderMapper"
factory-method="outboundMapper">
<beans:property name="outboundHeaderNames" value="*" />
<beans:property name="userDefinedHeaderPrefix" value="" />
</beans:bean>
<channel id="output" />
<channel id="input" />
<channel id="inputX" />
<channel id="toHttp" />

If it's based on the payload type, something like this should work...
url-expression="payload.getClass().getSimpleName().equals("Bar") ? 'http://foo/bar' : 'http://foo/baz'"
If it's a property on the payload, then
url-expression="payload.foo.equals("bar") ? 'http://foo/bar' : 'http://foo/baz'"
EDIT
or
url-expression="payload.foo.equals("bar") ? '${url1}' : '${url2}'"
This would need changing the property metadata class to rename url and add url2.

Related

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

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.

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: How to get value from util:map into SI's Router

My configuration is like below:
I am using SpEL inside router to get values from map.
<util:map id="routeConfig">
<entry key="Default" value="not configured" />
<entry key="GB22XXX" value="LON" />
<entry key="AEADXXX" value="ME" />
<entry key="HBXXXX" value="ME" />
<entry key="EBHBXBAO" value="ME" />
</util:map>
<router input-channel="InputRoutingChannel" default-output-channel="testOutputChannel" expression="#routeConfig.get('payload.getMsgHeader().getSourceSystem().substring(4, 12)')">
<mapping value="LON" channel="MarshallerOutputChannel_lonme" />
<mapping value="ME" channel="MarshallerOutputChannel_me" />
</router>
Your issue is here:
expression="#routeConfig.get('payload.getMsgHeader().getSourceSystem().substring(4, 12)')
Since you get deal with runtime Message you really don't need to wrap to the literal. Hence an answer to you is:
expression="#routeConfig.get(payload.msgHeader.sourceSystem.substring(4, 12))

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