Given a gateway that handles service calls to a ws. My goal is to supply the http:outbound-gateway's reply-channel using header-enricher since I'll be adding multiple methods to gateway and I would like to make use of only 1 http:outbound-gateway
I can currently receive the response up to groovy script (2) BUT it doesn't seem to want to return the results to the actual method that calls the service
Any help would be appreciated. Thanks!
<gateway id="registryService" service-interface="RegistryService">
<method name="create" request-channel="create-request-channel"
reply-channel="create-reply-channel" />
</gateway>
<chain input-channel="create-request-channel" output-channel="create-request-fulfillment-channel">
<transformer>
// groovy script that contains the method to be called in the ws (1)
</transformer>
<object-to-json-transformer/>
<header-enricher>
<reply-channel overwrite="true" ref="create-reply-fulfillment-channel" />
</header-enricher>
</chain>
<http:outbound-gateway request-channel="create-request-fulfillment-channel"
extract-request-payload="true"
expected-response-type="java.lang.String"
url="http://localhost:4567" http-method="POST" />
<chain input-channel="create-reply-fulfillment-channel"
output-channel="create-reply-channel">
<json-to-object-transformer type="JsonRpcResponse"/>
<transformer>
//groovy script to manipulate response (2)
</transformer>
</chain>
Do the following:
Each method of your gateway should enrich message with some unique 'routing' header value:
<gateway id="registryService" service-interface="RegistryService">
<method name="create" request-channel="http-request-channel"
reply-channel="registryService-create-responseChannel">
<header name="routingHeader" value="registryService-create" />
</method>
</gateway>
And then send message straight forward to outbound gateway:
<http:outbound-gateway request-channel="http-request-channel"
response-channel="http-response-channel"
extract-request-payload="true"
expected-response-type="java.lang.String"
url="http://localhost:4567" http-method="POST" />
Http outbound gateway sends request to the remote server and then forward response to http-response-channel. To this channel is attached header value router, which basis on the value of routing header, sends (routes) message to the appropriate channel:
<header-value-router input-channel="http-response-channel" header-name="routingHeader">
<mapping value="registryService-create" channel="registryService-create-responseChannel" />
<mapping value="someOtherService-otherMethod" channel="someOtherService-otherMethod-responseChannel" />
</header-value-router>
Of course you don't need to send it back directly to the gateway - you can add some additional processing between those components, and all the time you can route message basis on the header value.
It's simpler than hacks with groovy and I use it myself - proven that works ;)
Related
I have registered an AsyncHandler and also added a success-channel to an SQS outbound flow. The success-channel has a int:logging-channel-adapter endpoint. However I am not able to see any logs from this adapter. The AsyncHandler is able to receive the call-backs but nothing on the success-channel.
In the SqsMessageHandler I see that we are setting an output channel in the obtainAsyncHandler method, but I did not see the success-channel set anywhere. Am I missing something?
I would prefer using the success and failure channels and not AsyncHandler call-back Impl to avoid having AWS specific code in my classes.
Also my <int-aws:sqs-outbound-channel-adapter> is inside a <int:chain> which has no output channel, since the flow ends when the message is sent.
EDIT - Added Config
This is the only way I can get it to log the callback.
<int:channel id="chainChannel" />
<int:channel id="successChannel" />
<bean class="ServiceTransformer" id="serviceTransformer" />
<int:chain input-channel="serviceChannel" id="sendToServiceSqsChain" output-channel="chainChannel">
<int:transformer ref="serviceTransformer" method="transform" />
<int:header-filter header-names="config" />
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS" queue="some-queue" async-handler="sqsPublishCallbackHandler" success-channel="successChannel"/>
</int:chain>
<int:logging-channel-adapter log-full-message="true" channel="chainChannel" />
Here I can just use the same channel in both chain (outbound channel) and sqs-outbound (success-channel)
Unable to get it to work like below:
<int:channel id="successChannel" />
<bean class="ServiceTransformer" id="serviceTransformer" />
<int:chain input-channel="serviceChannel" id="sendToServiceSqsChain" >
<int:transformer ref="serviceTransformer" method="transform" />
<int:header-filter header-names="config" />
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS" queue="some-queue" async-handler="sqsPublishCallbackHandler" success-channel="successChannel"/>
</int:chain>
<int:logging-channel-adapter log-full-message="true" channel="successChannel" />
The <int-aws:sqs-outbound-channel-adapter> component is one-way, therefore there is no outputChannel option expose. However the target class is AbstractMessageProducingHandler. To avoid code duplication we reuse an existing outputChannel internally for that AsyncHandler.
In the XML parser we just remap one to another:
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "success-channel", "outputChannel");
You probably don't see anything in logs because you need to adjust logging config respectively for the appropriate category and level.
UPDATE
According my testing this is definitely not possible to configure such a component with XML DSL within the <chain>. That <int-aws:sqs-outbound-channel-adapter> has to be presented outside of the <chain>.
Consider to more your configuration to Java DSL instead: https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/dsl.html#java-dsl.
I have a requirement wherein I have to FTP read XML files from a remote server and dump them in a local directory. After that I have to fire a SQL query against the database using values from the incoming payload and then - based on the outcome of the query - decide if I want to continue the flow or not.
<int-ftp:inbound-channel-adapter id="ftpInbound1"
channel="inboundFTPFileChannel" session-factory="ftpSessionFactory"
local-filter="compositeFilter" filename-pattern="*.xml"
preserve-timestamp="true" charset="UTF-8" remote-directory="${remote.request.cdr.circle.dir}"
remote-file-separator="/" local-directory="${local.request.dir}">
<int:poller fixed-rate="${ftp.file.poller.interval}"
time-unit="SECONDS" max-messages-per-poll="-1" />
</int-ftp:inbound-channel-adapter>
<int:filter input-channel="inboundFTPFileChannel"
output-channel="jdbcChannel" discard-channel="discardChannel"
expression="payload.isFile()" />
<int-jdbc:outbound-channel-adapter
channel="jdbcChannel" query="SELECT COUNT(1) FROM some_table WHERE some_col = some_value"
data-source="myDataSource" />
At this stage I want to continue the flow if the query returns a non-zero output. Else the flow for this message should end. Also, if the flow should continue, the output to next channel should be the same Spring Integration Message which came on 'jdbcChannel'. Please advice.
What I can definitely do is write a <int:filter> which references a bean returning true or false. But I am just trying to avoid having to write this Java code!
Save off the original payload in a header and restore it later...
<int:header-enricher ...>
<int:header name="savedPayload" expression="payload" />
</int:header-enricher>
<int:jdbc-outbound-gateway... />
<int:filter ... expression="payload > 0" />
<int:transformer ... expression="headers['savedPayload']" />
...
Notice you need to use a gateway, not a channel adapter (JDBC).
You can put all this in a <chain/> if you wish.
I have a common flow that will be reused multiple times. So, I defined a SI Chain for it like below.
<int:chain id="addInfo" input-channel="addInfoChannel">
<int:header-enricher>
<int:header name="outgoingService" value="Retrieve" />
</int:header-enricher>
<int:gateway request-channel="common_Retrieve_Channel" />
<int-xml:xslt-transformer xsl-templates="addInfoXslTemplate">
<int-xml:xslt-param name="param1" expression="headers.param1" />
<int-xml:xslt-param name="param2" expression="headers.param2" />
</int-xml:xslt-transformer>
<int:header-enricher>
<int:header name="outgoingService" value="Add" overwrite="true" />
</int:header-enricher>
<int:gateway request-channel="common_Add_Channel" />
</int:chain>
If the common_Retrieve_Channel channel fails with a SOAP fault, the header value (outgoingService) is lost.
If I have the header-enricher outside the chain, then header value is available on payload.failedMessage.headers.
I don't want to add this value outside chain, Since this value will be changing inside the chain to call another service.
This chain will be called multiple times by setting up the header values (param1 and param2) differently.
Please let me know if there is any better solution other than extracting the gateway into it's own chain. Thanks for your help.
You must have some compensation flow on the error-channel of your <gateway>.
Since you say that you have SOAP error and you already are familiar with the payload.failedMessage.headers you just need to write something like this there:
<int:transformer input-channel="gatewayErrorChannel" expression="payload.failedMessage"/>
And looks like you further <chain> flow should be the same. Only difference that you will get deal there with requestMessage only, but lose error information from the external SOAP request.
I am new to SI. I am using the code from the SI TCP Multiplexing example as a starting point for an app server I am writing. The caller of the service already exists and will be sending the payload prefixed by a byte length header. I am having a bit of trouble with the correlation of the response. As you can see below, I changed the Multiplexing example to first add a correlation id header to the incoming request before pushing on to the publish-subscribe-channel. The rest of the code is pretty much the same as the example.
So, the problem. The correlation id header is not available on the call to MessageController from the TcpSendingMessageHandler which serializes the message payload and sends it. Should I enrich the payload to include the correlation id (no correlation header) or is there a simpler way of doing all of this? Any guidance would be greatly appreciated.
<gateway id="gw"
service-interface="is.point.tokens.server.MessageGateway"
default-request-channel="input">
</gateway>
<ip:tcp-connection-factory id="client"
type="client"
host="${tcpClientServer.address}"
port="${tcpClientServer.port}"
single-use="false"
serializer="bigEndianFormatSerializer"
deserializer="bigEndianFormatSerializer"
so-timeout="10000"/>
<channel id="input" datatype="java.lang.String"/>
<header-enricher input-channel="input" output-channel="enriched.input">
<correlation-id expression="headers['id']"/>
</header-enricher>
<publish-subscribe-channel id="enriched.input"/>
<ip:tcp-outbound-channel-adapter id="outAdapter.client"
order="2"
channel="enriched.input"
connection-factory="client"/>
<!-- Collaborator -->
<!-- Also send a copy to the custom aggregator for correlation and
so this message's replyChannel will be transferred to the
aggregated message. The order ensures this gets to the aggregator first -->
<bridge input-channel="enriched.input" output-channel="toAggregator.client" order="1"/>
<!-- Asynchronously receive reply. -->
<ip:tcp-inbound-channel-adapter id="inAdapter.client"
channel="toAggregator.client"
connection-factory="client"/>
<!-- Collaborator -->
<channel id="toAggregator.client" datatype="java.lang.String"/>
<aggregator input-channel="toAggregator.client"
output-channel="toTransformer.client"
correlation-strategy-expression="headers.get('correlationId')"
release-strategy-expression="size() == 2">
</aggregator>
<!-- The response is always second -->
<transformer input-channel="toTransformer.client" expression="payload.get(1)"/>
<!-- Server side -->
<ip:tcp-connection-factory id="server"
type="server"
port="${tcpClientServer.port}"
using-nio="true"
serializer="bigEndianFormatSerializer"
deserializer="bigEndianFormatSerializer"/>
<ip:tcp-inbound-channel-adapter id="inAdapter.server"
channel="toSA"
connection-factory="server" />
<channel id="toSA" datatype="java.lang.String"/>
<service-activator input-channel="toSA"
output-channel="toObAdapter"
ref="messageController"
method="handleMessage"/>
<beans:bean id="messageController"
class="example.server.MessageController"/>
<channel id="toObAdapter"/>
<ip:tcp-outbound-channel-adapter id="outAdapter.server"
channel="toObAdapter"
connection-factory="server"/>
Yes, you need something in the data so the reply can be correlated.
But I am confused. If you are the server side for an existing application, then you can simply use an inbound gateway.
If you really are providing the client and server side, we did add a mechanism in 3.0 to selectively add headers (to the tcp message), for example, using JSON.
EDIT:
From your comments, you only need the server side. All you need is this...
<int-ip:tcp-connection-factory id="server"
type="server"
serializer="serializer"
deserializer="serializer"
port="${port}"/>
<int-ip:tcp-inbound-gateway id="gateway"
connection-factory="crLfServer"
request-channel="toSA"
error-channel="errorChannel"/>
<int:channel id="toSA" />
<int:service-activator input-channel="toSA"
ref="messageController"
method="handleMessage"/>
<bean id="serializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer" />
The fact that the service-activator has no output-channel means the framework will send the reply to the gateway automatically.
This assumes the service can handle a byte[]; if you need a String, you'll need a transformer like in the sample. Either way, the payload will not include the length header; it is stripped off (inbound) and added (outbound).
This also assumes the length header is 4 bytes (default); the serializer takes a size in a constructor...
<bean id="serializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer">
<constructor-arg value="2" />
</bean>
In my application, several jms:message-driven-channel-adapter instances all output to the same internal channel. I want to associate a different header-enricher with each channel-adapter. Is there a way to do this?
The following code shows how to associate a header-enricher with the internal channel to which the jms:message-driven-channel-adapter outputs to:
<jms:message-driven-channel-adapter id="J1" destination="Q1" channel="in1"/>
<int:header-enricher input-channel="in1" output-channel="out1">
<int:header name="fromAdapter" value="1"/>
</int:header-enricher>
However, this will not work in my scenario, in which all channel-adapter's use the same internal channel . It would be nice to be able to define the header-enricher inside of the channel-adapter. Is there another way to link the adapter and enricher together?
Thanks
Different header-enrichers should have different input-channels anyway. You can provide some routing logic and use <gateway> for request-reply flow for your header-enrichers:
<chain input-channel="in1" output-channel="out1">
<gateway request-channel="enrichement"/>
</chain>
<router input-channel="enrichement">
<mapping channel="he1" value=""/>
...
</router>
<header-enricher input-channel="he1">
...
</header-enricher>
<header-enricher input-channel="he2">
...
</header-enricher>
....
<header-enricher input-channel="heN">
...
</header-enricher>
HTH