How to intercept the reply message of an jms inbound gateway - jms

I have a jms-inbound-gateway that reads requests from a WebsphereMQ broker, passes them though my integration system and then replies with a response message.
I need to log the messages with the jms_messageId and jms_correlationId headers set, so I can match request/reply messages in the log file (and show it to my client when he says that my response does not have the correct jms_correlationId)
Is there a way to intercept the method producer.sendReply(...) after the correlationId header is set?

There is no need to intercept there; the headers are available in the Spring Integration messsage in the gateway reply message before it gets to the gateway.
Simply make the reply-channel a publish-subscribe-channel and add a <logging-channel-adapter/> that has it as its input channel.
The reply message will be sent to both the gateway and the logger.
If you are using the default mechanism to route the reply (no output-channel on your last integration component), simply add an output-channel and route to the reply channel.

This is the gist of my solution after Gary's input:
<jms:inbound-gateway
id="inboundDestination"
connection-factory="connectionFactory"
request-destination="nmRequestsQueue"
request-channel="request-begin"
reply-channel="request-end"
error-channel="normaErrorChannel"
concurrent-consumers="1"
acknowledge="transacted" />
<int:logging-channel-adapter id="request-response-logger"
log-full-message="true"
level="DEBUG"
logger-name="com.audaxys.si.messages" />
<int:channel id="request-begin">
<int:interceptors>
<int:wire-tap channel="request-response-logger" />
</int:interceptors>
</int:channel>
<int:chain input-channel="request-begin" output-channel="request-end">
... Do Stuff ...
</int:chain>
<int:publish-subscribe-channel id="request-end">
<int:interceptors>
<int:wire-tap channel="request-response-logger" />
</int:interceptors>
</int:publish-subscribe-channel>

Related

Spring integration http:inbound-channel-adapter error-channel

I recently update from Spring Integration 4.1.3 to 4.2.6 and noticed that I started getting a 500 response from an http:inbound-gateway.
Upon investigation this was due to an empty reply and the gateway interpreting this as a timeout in MessagingGatewaySupport
if (reply == null && this.errorOnTimeout) {
This makes sense so I changed this to an http:inbound-channel-adapter and it solves this problem, but then the error handling doesn't behave as expected.
I previously had and error-channel on the gateway with
<int-http:inbound-gateway id="inboundGateway" request-channel="httpInChannel" reply-channel="httpResponseChannel" path="/**/status*" supported-methods="POST" error-channel="httpErrorChannel"/>
<int:chain input-channel="httpInChannel" output-channel="serviceChannel">
...
</int:chain>
<int:chain input-channel="httpErrorChannel">
<int:header-enricher>
<int:header name="http_statusCode" value="500" />
</int:header-enricher>
<int:transformer expression="payload.localizedMessage" />
</int:chain>
<int:service-activator input-channel="serviceChannel" ref="someController" method="someVoidMethod"/>
I suspected it may not work but I modified this slightly to
<int-http:inbound-channel-adapter id="inboundAdapter" channel="httpInChannel" path="/**/status*" supported-methods="POST" error-channel="httpErrorChannel"/>
<int:chain input-channel="httpInChannel" output-channel="serviceChannel">
...
</int:chain>
<int:chain input-channel="httpErrorChannel">
<int:header-enricher>
<int:header name="http_statusCode" value="500" />
</int:header-enricher>
<int:transformer expression="payload.localizedMessage" />
</int:chain>
<int:service-activator input-channel="serviceChannel" ref="someController" method="someVoidMethod"/>
Now, it works fine for a normal valid POST request, but if I send an invalid message that errors I get a 500 response with full error stack in response (have tried changing the status code in the header enricher too). The error is
org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
which makes sense as the error-channel does not have an output (although it doesn't in the docs either http://docs.spring.io/spring-integration/docs/4.3.0.RELEASE/reference/htmlsingle/#http-response-statuscode).
Is there a way to change the error response for the inbound adapter in a similar way to that of the gateway?
I think it's a bug, please open a JIRA Issue.
As a work around, in the main flow, just after the adapter, add a mid-flow gateway...
<int:service-activator ref="gw" input-channel="httpInChannel" >
<int:gateway id="gw" error-channel="httpErrorChannel"
default-request-channel="nextChannelInMainFlow" default-reply-timeout="0" />
The gateway is like a try/catch block around the flow.
The reply timeout is important since you do not expect a reply.

jms dynamic destination from original jmsReplyTo

There is one request queue and the reply queues are created by the client server instances and pinned to each instance rather than using temporary queues.
The use case needs to get an inbound jms message and then send that message to an asynchronous process. Once the async reply messsage is received from the service I need to take those results and reply back to the original message's jmsReplyTo. The Jms gateway would not work in this instance AFAIK>
I am using a jms message driven channel adapter for the message in with a series of channels and service activators to handle the out of process calls and async replies. I am trying to use the DynamicDestinationResolver to no avail. Additionally I have tried to set the outbound destination address programatically but could not figure out a good way to do this.
This seems like a common pattern but I could not find a good example for a completely disconnected async request response. Disconnected meaning that the usual async jms request reply did not seem to fit the need.
Context Config:
<!-- Input from Amq -->
<amq:queue id="requestQueue" physicalName="${request.queue}" />
<int-jms:message-driven-channel-adapter id="jmsIn"
connection-factory="jmsConnectionFactory"
destination="requestQueue"
channel="queueRequestChannel" concurrent-consumers="5" />
<int:channel id="queueRequestChannel" />
<int:service-activator input-channel="queueRequestChannel" ref="switchMessageHandler" method="processSwitchMessage"
output-channel="cardNetworkOutChannel"/>
<!-- Output to Card Network-->
<int:channel id="cardNetworkOutChannel" />
<!--<int:service-activator input-channel="cardNetworkOutChannel" ref="cardNetworkHandler" method="send8583Message" />-->
<!-- Simply used to mock the card network by transforming a SwithMessage to a SwitchMessageResponse * Not needed for target solution -->
<int:transformer id="requestResponseTransformer" ref="nettyCardNetworkClientMock" input-channel="cardNetworkOutChannel"
method="process" output-channel="cardNetworkInChannel"/>
<!-- Input from Card Network -->
<int:channel id="cardNetworkInChannel" />
<int:service-activator input-channel="cardNetworkInChannel" ref="switchMessageHandler" method="sendSwitchMessage"
output-channel="queueReplyChannel"/>
<int:channel id="queueReplyChannel"/>
<int-jms:outbound-channel-adapter
destination-resolver="simpleDestinationResolver" connection-factory="jmsConnectionFactory"
channel="queueReplyChannel" destination-expression="headers.jms_replyTo" />
I just updated the jms sample app to make the server side use independent adapters instead of the inbound gateway and it works just fine...
<!-- <jms:inbound-gateway id="jmsin" -->
<!-- request-destination="requestQueue" -->
<!-- request-channel="demoChannel"/> -->
<channel id="demoChannel"/>
<jms:message-driven-channel-adapter destination="requestQueue" channel="demoChannel" />
<service-activator input-channel="demoChannel" ref="demoBean" output-channel="reply" />
<channel id="reply" />
<jms:outbound-channel-adapter channel="reply" destination-expression="headers['jms_replyTo']" />
Turn on DEBUG logging - we put out lots of useful stuff.
EDIT
I just made it async by...
<channel id="reply">
<queue/>
</channel>
<jms:outbound-channel-adapter channel="reply" destination-expression="headers['jms_replyTo']">
<poller fixed-delay="3000"/>
</jms:outbound-channel-adapter>
EDIT2
Depending on what you are using on the client side, many clients require the inbound message id to be used as the correlation id. (This is true for the outbound gateway by default, with named reply queue, unless you provide a correlation-key).
So, to set up the correlationId, you can use a header enricher; I just tested this...
<chain input-channel="reply">
<header-enricher>
<header name="jms_correlationId" expression="headers['jms_messageId']" />
</header-enricher>
<jms:outbound-channel-adapter destination-expression="headers['jms_replyTo']"/>
<poller fixed-delay="1000" />
</chain>
This is not the issue if the client side is setting the correlation id header itself.

How to create channel interceptor programmatically in spring

I want to create programmatically the following XML config on demand:
<int-mqtt:message-driven-channel-adapter id="inboundAdapter"
client-id="${mqtt.client.id}"
url="${mqtt.broker.url}"
topics="${mqtt.subscribed.topics}"
client-factory="clientFactory"
channel="input-channel-1" converter="customConverter" />
<int:channel id="input-channel-1">
<int:queue/>
<int:interceptors>
<int:wire-tap channel="logger"/>
<int:ref bean="messageListener"/>
</int:interceptors>
</int:channel>
<int:channel id="logger" />
<int:logging-channel-adapter channel="logger"
auto-startup="true" level="INFO" id="loggerAdapter" log-full-message="true" />
What i can do is the following
CustomMqttPahoMessageDrivenChannelAdapter adapter = new CustomMqttPahoMessageDrivenChannelAdapter(url, clientId, topic);
adapter.setOutputChannel(outputChannel);
adapter.setConverter(ctx.getBean("customConverter", MyPahoMessageConverter.class));
Now I need to add interceptor bean through which each client will get notified when message arrives according to their subscribed topics respectively.
What I am trying to achieve is:
1) Create mqtt adapter when a client connects to server.(each client will subscribe to different topics as per configuration)
2) Dispose mqtt adapter when client disconnects.
Can anyone help me on this?
It's not clear what you are trying to do; what do you have downstream of the input-channel-1 in the XML configuration.
What does messageListener do?
It is an anti-pattern to put business logic in a channel; unless it's something really lightweight, consider invoking it using a <service-activator/> instead - possibly by making input-channel-1 a pub-sub channel.
To answer your simple question, to add the interceptor, you can use outputChannel.addInterceptor(ctx.getBean("messageListener", ChannelInterceptor.class));.

Bi-directional messaging with spring integration

I'm relatively new to spring integration but I have been tasked with implementing a tcp gateway that needs to:
Listen for messages on a socket
Receive a message
Process the message and write some data to a queue
Return a response message to the original socket.
In my experience with spring integration, the message flow has not been bi-directional. I've only configured routers to listen, handle messages, and output to queue/topic. In this case, though, I need to accept messages and return a response while also forwarding on some message to a queue. Suggestions?
This is my integration xml so far.
<int:chain input-channel="tcpChannel">
<int:service-activator ref="tcpHandler" method="handleInput" />
</int:chain>
<int-ip:tcp-inbound-gateway
id="tcpGateway"
connection-factory="tcpServer"
request-channel="tcpChannel" />
How can I have the output of handleInput be forwarded to a queue but also have some response sent back from the gateway?
Edit: After the conversation below with Gary, this seems to be the pattern we want to follow:
<int-ip:tcp-inbound-gateway id="tcpGateway"
connection-factory="tcpServer"
request-channel="tcpChannel"
reply-channel="tcpReplyChannel"/>
<int:publish-subscribe-channel id="tcpChannel" />
<int:chain input-channel="tcpChannel">
<!-- int:json-to-object-transformer type="com.heb.revo.events.RxPosCredit" /-->
<int:service-activator ref="tcpHandler" method="handleInputToQueue" />
<jms:outbound-channel-adapter destination-name="${queue.response}" />
</int:chain>
<int:service-activator id="tcpResponseHandler"
ref="tcpHandler" method="replyToSocket"
input-channel="tcpChannel"
output-channel="tcpReplyChannel" />
<int:publish-subscribe-channel id="tcpReplyChannel" />
Since your chain has no output-channel the framework will route the return value of the handleInput method back to the gateway automatically.
If you want to capture the result and send it somewhere else (as well as a reply), create a <int:publish-subscribe-channel id="foo"/>, set the output-channel of the chain to foo, set the reply-channel on the gateway to foo, and subscribe another endpoint to foo (as an input-channel.

Logging in Spring-integration, statistics for the queue

Working with Spring-integration, I am using this code for logging (and I have configured the log4j).
<int:message-history/>
<int:logging-channel-adapter id="logger" auto-startup="true" log-full-message="true" level="INFO" logger-name="logger"/>
<int:channel id="messages" >
<int:queue />
<int:interceptors>
<int:wire-tap channel="logger" />
</int:interceptors>
</int:channel>
Here I am logging the full-message, but I would like to use the SpEL in order to log the ChannelName, and the message.
I need also to log the number of Messages in the Queue at each insertion or Poll but I didn't found how to do it and what to declare in the log4j.xml.
There is nothing to do for log4j: you just should configure specific category and level on the matter.
WireTap doesn't do anything with channel for which it is configured. It just sends a message to the provided channel. And it does it only onSend. It doesn't do anything onReceive.
To get deal with channel you have to implement your own ChannelInterceptor and do logging just there. I mean you don't need <wire-tap> and <logging-channel-adapter> anymore.
To get a number of Messages in the Queue you should configure:
<context:mbean-export />
<context:mbean-server />
After that all Integration components will be wrapped with *Metrics proxy.
For example the QueueChannel becomes QueueChannelMetrics.
And this info you can get from your new ChannelInterceptor before logging it together with the Message and channelName.

Resources