Spring integration service-interface gateway reply channel as shared Pub/Sub - spring

This is similar to Intermittent BridgeHandler & PublishSubscribeChannel call when gateways' reply channel is pub/sub but the scenario is different in that the reply-channel is not getting "lost". The question is what is the best resolution for my scenario.
I am using Spring integration to launch Spring batch jobs. I have a number of input routes e.g. file polling and http requests. These all route to a batch-int Job Launching Gateway. The referenced Job Launcher has a task executor so job launches are asynchronous. This gateway replies on a specified channel.
<int:gateway service-interface="c.c.c.etl.gateway.JobSubmissionService" id="jobSubmissionService" default-request-channel="jobLauchInputChannel" default-reply-channel="jobLaunchReplyChannel">
</int:gateway>
<int:bridge id="filePollerBridge" input-channel="filePollerOutputChannel" output-channel="jobLauchInputChannel" />
<batch-int:job-launching-gateway request-channel="jobLauchInputChannel" reply-channel="jobLaunchReplyChannel" job-launcher="jobLauncher">
</batch-int:job-launching-gateway>
<int:publish-subscribe-channel id="jobLaunchReplyChannel" />
<int:bridge id="jobLaunchReplyChannelBridge" input-channel="jobLaunchReplyChannel" output-channel="loggingChannel">
</int:bridge>
This specified channel 'jobLaunchReplyChannel' is pub/sub and has a logger listening to it. This channel is also used as the reply channel for a service-interface gateway.
The issue I am having is that when jobs are requested via sources that are not the gateway (e.g. the poller) the Bridge that is added by gateway throws an exception because no reply channel is set on replies.
I have resolved this by adding a header to messages sent via the gateway and filtering out messages only with this header to a new 'gatewayReplyChannel'.
<int:gateway service-interface="c.c.c.etl.gateway.JobSubmissionService" id="jobSubmissionService" default-request-channel="httpJobRequestInputChannel" default-reply-channel="jobSubmissionServiceReplyChannel">
<int:default-header name="isJobSubmissionServiceMessage" value="true" />
</int:gateway>
<int:channel id="jobSubmissionServiceReplyChannel"></int:channel>
<int:filter id="jobSubmissionServiceReplyChannelFilter" input-channel="jobLaunchReplyChannel" expression="headers.get('isJobSubmissionServiceMessage') == null ? false : headers.get('isJobSubmissionServiceMessage')" output-channel="jobSubmissionServiceReplyChannel"
throw-exception-on-rejection="false" />
Is there a better way to do this?

Eh.. That's an interesting issue. The main cause is because MessagingGatewaySupport creates replyMessageCorrelator endpoint for an internal BridgeHandler.
And we really have that strange behaviour when we send message to the reply-channel directly. That BridgeHandler tries to send message to the replyChannel from headers.
And we really can't do anything to prevent that logic. And can't protect that explicit reply-channel from direct messages.
I think your solution is correct. Another way to overcome that: add replyChannel header from the start of another flow (file polling in your case) just using something like this:
<header-enricher>
<reply-channel ref="nullChannel"/>
</header-enricher>
Feel free to raise a JIRA issue on the matter and we'll take a look what we can do. At least we can document these specifics.

Related

Add Poller to SFTP Outbound Gateway

I'm not sure but I can't add a poller to my sftp outbound gateway. I tried doing it but I have no luck, it does not poll during start up so I have created a service interface and then just call the method via scheduler. Is there a better way to do it? Here is snippet of what I did :
<int:channel channel="requestChannel"
<int:queue />
</int:channel>
<int-sftp:outbound-gateway id="ls"
auto-startup="true"
expression="payload"
request-channel="requestChannel"
remote-directory="${remote-directory}"
command-options="-1"
session-factory="sftpSessionFactory"
command="ls"
reply-channel="replyChannel">
<int:poller fixed-delay="10000" max-messages-per-poll="1"/>
</int-sftp:outbound-gateway>
Any Outbound Gateway is an event-driven endpoint. It is not a beginning of the flow like it would be, for example, with the <int-sftp:inbound-channel-adapter>.
If really would like to trigger such a gateway in a periodic manner, you would simulate it via "void" inbound channel adapter:
<int:inbound-channel-adapter channel="requestChannel" expression="''">
<int:poller fixed-delay="10000" max-messages-per-poll="1"/>
</int:inbound-channel-adapter>
The requestChannel must not be a queue channel any more. The message with empty payload is going to be sent to this channel on each poll and your gateway is going to perform its logic. However I see that you use expression="payload" for the remote directory to perform LS command. So, or you need to configure that <int:inbound-channel-adapter > to produce a message with that remote dir payload or just have a static expression for it on the gateway, e.g. expression="'/myRemoteDir'".
Either way, but only with a polling channel adapter in the beginning of the flow. Otherwise a gateway is not going to be triggered periodically.

Gateway default-reply-timeout has no effect and still waits for reply (Spring Integration)

I have been trying the reply timeout of Gateway in Spring Integration but it doesn't work in the configuration I've been using below:
<int:gateway id="TrailerGateway" service-interface="com.12lmdk.gateways.TrailerGateway"
default-request-channel="trailerChannel" default-reply-timeout="5000"/>
<int:channel id="trailerChannel" />
<int:service-activator input-channel="trailerChannel"
ref="trailerService" method="getTrailer"/>
I have read in a stackoverflow question that I should provide a reply channel on the gateway and output channel in the service activator and that channel should be pollable so I tried that as well
<int:gateway id="TrailerGateway" service-interface="com.12lmdk.gateways.TrailerGateway" default-reply-channel="trailerOutputChannel" default-reply-timeout="5000"/>
<int:channel id="trailerChannel" />
<int:channel id="trailerOutputChannel" >
<int:queue/>
</int:channel>
<int:service-activator input-channel="trailerChannel" output-channel="trailerOutputChannel" ref="trailerService" method="getTrailer"/>
This still won't work and the reply-timeout has no effect. (I've tested it by putting a Thread.sleep of 10 seconds in one of the method accessed by the service activator)
The gateway still waits for the reply of the service activator which is not what I am expecting.
How can I produce an exception or even a null response from the gateway due to timeout???
The timer doesn't start until the thread returns to the gateway. It is intended for use when the calling thread hands off the work to another thread, for example with a QueueChannel or ExecutorChannel. When the calling thread returns to the gateway, the timer starts.

Inserting messages in a JMS queue and then sending back the acknowledgement using Spring Integration

I have a service which receives xml messages via an http inbound adapter and then transforms them into text that becomes the content of an email that gets sent out.
I now need to first insert these messages into a JMS queue and send the acknowledgement back as a 200 ok after the message is inserted into the Q and then carry-on with the rest of the processing.
<int-http:inbound-channel-adapter channel="inputChannel"
id="httpInbound"
auto-startup="true"
request-payload-type="java.lang.String"
path="/message"
supported-methods="POST"
error-channel="logger" >
<int-http:request-mapping consumes="application/xml" />
</int-http:inbound-channel-adapter>
<int:chain id="chain" input-channel="inputChannel" >
<int:service-activator ref="mailTransformerBean" method="transform" />
</int:chain>
The service-activator takes care of the processing to convert the xml into an email.
Before that I need to incorporate a JMS Queue into which the received messsages will be inserted and then the acknowledgement is sent back. This is so as to retain the messages and retry in case of a failure of the service.
I would like to set this up as a Transaction with the JMS queue as a endpoint.
How do i approach this?
If you are seeking something like a in-process persistence storage, take a look, please, into the SubscribableJmsChannel :
The channel in the above example will behave much like a normal <channel/> element from the main Spring Integration namespace. It can be referenced by both "input-channel" and "output-channel" attributes of any endpoint. The difference is that this channel is backed by a JMS Queue instance named "exampleQueue".

Generating a reply when using an outbound channel adapter

I have the following simplified spring integration flow:
int-ws:inbound-gateway ----> int:transformer ----> int-kafka:outbound-channel-adapter
Basically:
A web service endpoint is exposed using the int-ws:inbound-gateway
Messages from this endpoint are put onto the input channel (the first --->)
A custom transformer translates the payload JSON format and adds the MESSAGE_KEY header (required for kafka)
The message is placed onto the inputToKafka channel (the second --->)
The int-kafka:outbound-channel-adapter pushes the message to a kafka topic
The web service operation has a request and a response payload.
The request payload is what I'm transforming into the JSON message.
I would like to return a response payload (which will be marshalled etc) once the message is placed on the kafka topic by the int-kafka:outbound-channel-adapter
How do I do this?
At the moment, when I invoke the web service everything works as expected, but I have to set a reply-timeout on the int-ws:inbound-gateway so that it doesn't hang. When I do this, I simply get an empty response back on SOAPUI.
I understand the concepts in the section Gateway behavior when no response arrives - but in my case I do want to generate a response.
This is my integration context (without the kafka broker config etc):
<int-ws:inbound-gateway id="ws-inbound-gateway" request-channel="input"
marshaller="marshaller" unmarshaller="marshaller" reply-timeout="100"/>
<int:channel id="input"/>
<int:transformer input-channel="input" output-channel="inputToKafka" method="transform">
<bean class="com.test.InputToJSONTransformer"/>
</int:transformer>
<int:channel id="inputToKafka"/>
<int-kafka:outbound-channel-adapter kafka-producer-context-ref="kafkaProducerContext"
auto-startup="true"
channel="inputToKafka"
order="1">
</int-kafka:outbound-channel-adapter>
Change
<int:channel id="inputToKafka"/>
to
<int:publish-subscribe-channel id="inputToKafka"/>
Add a second subscriber to the channel.
<service-activator input-channel="inputToKafka" ... order="2" />
Where the service generates the response; it will be invoked after a successful send to kafka.
Do not include an output-channel; the framework will take care of routing the service output back to the ws gateway.
What kind of a response are you expecting from Kafka outbound channel adapter, remember it is Adapter not a Gateway
You could however introduce another component, ServiceActivator With an input channel sendToInputToKafka now your transformer will output to this channel.
Your 'ServiceActivator' should have an #Autowire MessageChannel inputToKafka and should manually send a message to this channel programmatically.
After sending that message, you will construct your desired response as a return type of the ServiceActivator and a response to your ws gateway

Spring Integration/RabbitMQ/AMQP: How do I create outbound-channel-adapters for dynamic input channels?

I'm working on abstracting out any sort of messaging framework for some code I'm working on. Basically, I'm using a combination of Spring AOP and Spring Integration to generate messages without the Java code knowing anything about RabbitMQ, JMS, or even Spring Integration. That said, what I'm using to generate the messages is contained in its own .jar, and it re-used by several other areas of the application. I currently have the messaging system set up such that the channels on which messages are sent are specified by the code that calls the system (i.e., channels are generated automatically based on the external method invocation) by specifying the channel name in the message header and using a header-value router to create the channels if they don't exist. My issue comes in on the endpoint of these channels - the intention of the current structure is to allow Spring to change to any messaging structure as requirements specify or change. I know how to take a static channel and use outbound channel converters/gateways to send it to a pre-specified RabbitMQ/JMS queue and process from there; what I'm struggling with is how to tell Spring that I need every channel created by the router to have a RabbitMQ (or whatever other messaging system gets implemented) outbound channel adapter that's dynamically generated based on the channel name since we don't know channel names beforehand.
Is this possible? And if not, would you mind providing input as to what could perhaps be a better way?
Thanks ahead of time!
Here's a basic template of what my config file looks like - I have an initial channel ("messageChannel") which gets sent to a publish-subscribe-channel and queuing channel depending on one of the message headers and is routed from there.
<!--Header value based channel configurations-->
<int:channel id="messageChannel" />
<int:channel id="queue" />
<int:publish-subscribe-channel id="topic" />
<!--Header-based router to route to queue or topic channels-->
<int:header-value-router input-channel="messageChannel"
header-name="#{ T(some.class.with.StringConstants).CHANNEL_TYPE}" />
<!--Re-routes messages according to their destination and messaging type-->
<int:header-value-router input-channel="queue"
header-name="#{ T(some.class.with.StringConstants).MESSAGE_DESTINATION}" />
<int:header-value-router input-channel="topic"
header-name="#{ T(some.class.with.StringConstants).MESSAGE_DESTINATION}" />
<!--AOP configuration - picks up on any invocation of some.class.which.generates.Messages.generateMessage()
from a Spring-managed context.-->
<aop:config>
<aop:pointcut id="eventPointcut"
expression="execution(* some.class.which.generates.Messages.generateMessage(..))" />
<aop:advisor advice-ref="interceptor" pointcut-ref="eventPointcut"/>
</aop:config>
<int:publishing-interceptor id="interceptor" default-channel="messageChannel">
<int:method pattern="generateMessage" payload="#return" channel="messageChannel" />
</int:publishing-interceptor>
See the dynamic-ftp sample; it uses a dynamic router that creates new outbound endpoints/channels on demand.

Resources