I have a problem with spring integration basically I'am trying to create chain which will invoke 1 of several services and returns result as reply to chain gateway for further processing.
<int:chain input-channel="inputChannel" output-channel="outputChannel">
<int:header-enricher>
<int:header name="temporalPayload" expression="payload"/>
<int:header name="matcher" value="other" />
</int:header-enricher>
<int:gateway request-channel="routerChannel" reply-channel="replyChannel" error-channel="errorChannel"/>
<int:transformer expression="headers.temporalPayload"/>
</int:chain>
<int:router input-channel="routerChannel" expression="headers.matcher.matches('(test)|(test2)')?headers.matcher:'other'" >
<int:mapping value="test1" channel="channel1"/>
<int:mapping value="test2" channel="channel2"/>
<int:mapping value="other" channel="channel3" />
</int:router>
<int:service-activator input-channel="channel1" output-channel="replyChannel" ref="service1" method="handle"/>
<int:service-activator input-channel="channel2" output-channel="replyChannel" ref="service2" method="handle"/>
<int:service-activator input-channel="channel3" output-channel="replyChannel" ref="service3" method="handle"/>
Problem that gateway does not receive reply to channel "replyChannel" from service-activator. What I'am doing wrong?
You can turn on DEBUG logging level for org.springframework.integration category and observe how your message travels.
On the other hand, in most cases you don’t need that reply-Channel on the gateway and just rely on the replyChannel header populated by the gateway. In this case you even have to remove the output-Channel on the service-activators.
There might be some subscriber for your repjyChannel who steals messages for the Gateway.
You can try without reply-Channel or investigate logs as I suggested above.
Related
I have a doubt in Spring Integration Flow. Can we call one int:service-activator after another . Consider below example.
<int:channel id="getPresciption" />
<int:channel id="respPrescription" />
<int-http:inbound-gateway
request-channel="getPresciption" reply-channel="respPrescription"
supported-methods="GET" path="/getAllPresciption">
<int-http:request-mapping
consumes="application/json" produces="application/json" />
</int-http:inbound-gateway>
<int:service-activator
ref="medicineServiceActivator" method="buildPrescription"
input-channel="respPrescription" output-channel="respPrescription" />
<int:service-activator
ref="medicineServiceActivator" method="storePrescription"
input-channel="respPrescription"></int:service-activator>
My Qoestion is can we do this.
or we have to use Aggregator.
You need another channel
<int:service-activator
ref="medicineServiceActivator" method="buildPrescription"
input-channel="respPrescription" output-channel="toStorePrescription" />
<int:service-activator
ref="medicineServiceActivator" method="storePrescription"
input-channel="toStorePrescription"></int:service-activator>
I am using spring integration's error channel to capture exceptions in the framework. But of the two channels of the same type, one's thrown exceptions are received by the error handler while other's are not.
<int:gateway id="myGateway" service-interface="com.si.MyGateway" request-
channel="myRequestChannel" error-channel="errorHandlingChannel">
<!-- Redirects exceptions to error-channel -->
<int:chain input-channel="myRequestChannel" output-channel="lastChannel">
<int:header-enricher>
<int:error-channel ref="errorHandlingChannel">
<int:header-enricher>
<int:service-activator ref="testActivator1"
method="generateException" requires-reply="true" />
</int:chain>
<!-- Does not redirect exceptions to error-channel -->
<int:chain input-channel="lastChannel" output-
channel="nullChannel">
<int:header-enricher>
<int:error-channel ref="errorHandlingChannel">
<int:header-enricher>
<int:service-activator ref="testActivator2"
method="generateException" requires-reply="true" />
<int:filter ref="myFilter" discard-channel="nullChannel" />
<int:service-activator ref="testActivator3"
method="generateException" requires-reply="true" />
<int:filter ref="myFilter" discard-channel="nullChannel" />
</int:chain>
<int:service-activator input-channel="errorHandlingChannel"
ref="errorHandler" method="handle" />
The errorHandler's handle method accepts MessagingException. When I throw exception from myRequestChannel, it is received by the errorHandler but when the same exception is thrown from testActivator2 or testActivator3, it doesn't reach the errorHandler at all.
both channels are defined as async channels:
<int:channel>
<int:queue capacity="10">
</int:channel>
On debugging, I found that all methods' exceptions are being thrown and follow a chain through spring's classes when an internal class ultimately throws a MessagingException, but the exceptions from the last two activators do not reach the handler.
What could be the reason?
What kind of exceptions do you throw?
If it is MessagingException it must be supplied with the failedMessage, otherwise the error is sent to the default errorChannel on the <poller>.
I'm using Spring AMQP to listen to messages (configuration has listener-container, service-activator, chain, bridge & aggregators). At application startup AMQP starts reading messages which we don't want. I tried auto-startup=false but it isn't working. Am I missing anything?
Also, if it does work then how do I programmatically start them again? I tried listenerContainer.start();. What about aggregators & others?
EDIT
Following is my config:
<rabbit:queue name="my_queue1" declared-by="consumerAdmin"/>
<rabbit:queue name="my_queue2" declared-by="consumerAdmin"/>
<rabbit:queue name="my_batch1" declared-by="consumerAdmin"/>
<int-amqp:channel id="myPollableChannel" message-driven="false" connection-factory="consumerConnFactory" queue-name="my_queue2"/>
<int-event:inbound-channel-adapter channel="myPollableChannel" auto-startup="false"/>
<int-amqp:channel id="myAggregateChannel" connection-factory="consumerConnFactory"/>
<int-event:inbound-channel-adapter channel="myAggregateChannel" auto-startup="false"/>
<int-amqp:channel id="myChannel" connection-factory="consumerConnFactory"/>
<int-event:inbound-channel-adapter channel="myChannel" auto-startup="false"/>
<int-amqp:channel id="myFailedChannel" connection-factory="consumerConnFactory"/>
<int-event:inbound-channel-adapter channel="myFailedChannel" auto-startup="false"/>
<rabbit:template id="genericTopicTemplateWithRetry" connection-factory="connectionFactory" exchange="my_exchange" retry-template="retryTemplate"/>
<rabbit:topic-exchange name="my_exchange" declared-by="consumerAdmin">
<rabbit:bindings>
<rabbit:binding queue="my_queue1" pattern="pattern1"/>
<rabbit:binding queue="my_queue2" pattern="pattern1"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<int:handler-retry-advice id="retryAdvice" max-attempts="5" recovery-channel="myFailedChannel">
<int:exponential-back-off initial="3000" multiplier="5.0" maximum="300000"/>
</int:handler-retry-advice>
<int:bridge input-channel="myPollableChannel" output-channel="myAggregateChannel">
<int:poller max-messages-per-poll="100" fixed-rate="5000"/>
</int:bridge>
<int:aggregator id="myBatchAggregator"
ref="myAggregator"
correlation-strategy="myCorrelationStrategy"
release-strategy="myReleaseStrategy"
input-channel="myAggregateChannel"
output-channel="myChannel"
expire-groups-upon-completion="true"
send-partial-result-on-expiry="true"
group-timeout="1000" />
<int:chain input-channel="myFailedChannel">
<int:transformer expression="'Failed to publish messages to my channel:' + payload.failedMessage.payload" />
<int-stream:stderr-channel-adapter append-newline="true"/>
</int:chain>
<int:service-activator input-channel="myChannel" output-channel="nullChannel" ref="myWorker" method="myMethod">
<int:request-handler-advice-chain><ref bean="retryAdvice" /></int:request-handler-advice-chain>
</int:service-activator>
<rabbit:listener-container connection-factory="consumerConnFactory" requeue-rejected="false" concurrency="1">
<rabbit:listener ref="myListener" method="listen" queue-names="queues1" admin="consumerAdmin" />
</rabbit:listener-container>
OK. Thank you for the configuration!
Not sure why you need AMQP-bascked channels, but the main issue for you is exactly there.
But pay attention, the <int-amqp:channel> has auto-startup="false" option as well.
And you will be ready to receive data from the AMQP, you will need just start() those channels by their id.
I'm new to Spring Integration and Spring Integration AMQP.
I have the following code:
<bean id="enricher" class="soft.Enricher"/>
<amqp:inbound-channel-adapter queue-names="QUEUE1" channel="amqpInboundChannel"/>
<int:channel id="amqpInboundChannel">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<int:header-enricher input-channel="amqpInboundChannel" output-channel="routingChannel">
<int:header name="store" value="sj" />
</int:header-enricher>
<int:channel id="routingChannel" />
<int:header-value-router input-channel="routingChannel" header-name="store">
<int:mapping value="sj" channel="channelSJ" />
<int:mapping value="jy" channel="channelJY" />
</int:header-value-router>
<amqp:outbound-channel-adapter channel="channelSJ" exchange-name="ex_store" routing-key="sj" amqp-template="rabbitTemplate"/>
<amqp:outbound-channel-adapter channel="channelJY" exchange-name="ex_store" routing-key="jy" amqp-template="rabbitTemplate"/>
<int:channel id="channelSJ" />
<int:channel id="channelJY" />
<int:logging-channel-adapter id="logger" level="ERROR" />
The setup is the following:
Everything is working fine except that headers are lost when a message is picked up by the inbound-channel-adapter.
Likewise the header being enriched called "store" is also lost when the message is being send to the exchange using the outbound-channel-adapter.
This is how a message is looking before being picked up by the inbound-channel-adapter:
This is how the same message is looking after the whole process (notice no headers)
I think your problem is described here:
"By default only standard AMQP properties (e.g. contentType) will be copied to and from Spring Integration MessageHeaders. Any user-defined headers within the AMQP MessageProperties will NOT be copied to or from an AMQP Message unless explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames' properties of this HeaderMapper. If you need to copy all user-defined headers simply use wild-card character ''.*"
So you need to define your own custom instance of DefaultAmqpHeaderMapper and configure the inbound-channel-adapter with it. See here.
It might look something like this:
<bean id="myHeaderMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper">
<property name="requestHeaderNames" value="*"/>
<property name="replyHeaderNames" value="*"/>
</bean>
<amqp:inbound-channel-adapter queue-names="QUEUE1" channel="amqpInboundChannel"
header-mapper="myHeaderMapper"/>
I am trying to use spring integration to do a Gateway --> Splitter-->ServiceActivator --> Aggregator Pattern in an event driven fashion backed by JMS . I Expect the service activator to be multi-threaded and any of the end points can be executed on a cluster and not necessarily the originating server . I could get this working in a single JVM without using JMS ( Using SI Channels ) but I understand that SI Channels will not help me scale horizontally i.e multiple VMs .
Here's the configuration I have so far
<int:gateway id="transactionGateway" default-reply-channel="transaction-reply"
default-request-channel="transaction-request" default-reply-timeout="10000"
service-interface="com.test.abc.integration.service.ProcessGateway">
</int:gateway>
<int-jms:outbound-gateway id="transactionJMSGateway"
correlation-key="JMSCorrelationID" request-channel="transaction-request"
request-destination="transactionInputQueue" reply-channel="transaction-reply"
reply-destination="transactionOutputQueue" extract-reply-payload="true"
extract-request-payload="true">
<int-jms:reply-listener
max-concurrent-consumers="20" receive-timeout="5000"
max-messages-per-task="1" />
</int-jms:outbound-gateway>
<!-- Inbound Gateway for Splitter -->
<int-jms:inbound-gateway id="splitterGateWay"
request-destination="transactionInputQueue" request-channel="splitter-input"
reply-channel="splitter-output" concurrent-consumers="1"
default-reply-destination="processInputQueue"
max-concurrent-consumers="1" extract-reply-payload="true"
correlation-key="JMSCorrelationID" extract-request-payload="true" />
<!-- Inbound Gateway Invokes Service Activator and Sends response back to
the channel -->
<int-jms:inbound-gateway id="seriveActivatorGateway"
request-destination="processInputQueue" request-channel="process-input"
reply-channel="process-output" concurrent-consumers="1"
default-reply-destination="processOutputQueue"
max-concurrent-consumers="1" extract-reply-payload="true"
correlation-key="JMSCorrelationID" extract-request-payload="true"
max-messages-per-task="1" />
<int-jms:inbound-gateway id="aggregatorGateway"
request-destination="processOutputQueue" request-channel="aggregator-input"
reply-channel="aggregator-output" concurrent-consumers="1"
default-reply-destination="transactionOutputQueue"
max-concurrent-consumers="1" extract-reply-payload="true"
extract-request-payload="true" max-messages-per-task="1"
correlation-key="JMSCorrelationID" />
<int:splitter id="transactionSplitter" input-channel="splitter-input"
ref="processSplitter" output-channel="splitter-output">
</int:splitter>
<int:service-activator id="jbpmServiceActivator"
input-channel="process-input" ref="jbpmService" requires-reply="true"
output-channel="process-output">
</int:service-activator>
<int:aggregator id="transactionAggregator"
input-channel="aggregator-input" method="aggregate" ref="processAggregator"
output-channel="aggregator-output" message-store="processResultMessageStore"
send-partial-result-on-expiry="false">
</int:aggregator>
Before using gateway I tried using JMS backed Channels and that approach was't successful either . The problem I am facing now is that the Splitter now reply back to the transactionOutputQueue . I tried playing around with jms:header-enricher without much success . I feel that my approach to the problem /SI might have fundamental flaw . Any help /guidance is highly appreciated .
Also , in the code snippet I have provided above use a simple in memory aggregator , I understand that If I need to get this working across the cluster I might need a JDBC backed Aggregator but for the for now , I am trying to get this pattern working on a single VM
Here's the updated working configuration based on Gary's Comment
<bean id="processOutputQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="test.com.abc.process.output" />
</bean>
<bean id="transactionOutputQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="test.com.abc.transaction.result" />
</bean>
<bean id="transactionInputQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="test.com.abc.transaction.input" />
</bean>
<int:gateway id="transactionGateway"
default-request-channel="transaction-request" default-reply-timeout="10000"
default-reply-channel="aggregator-output"
service-interface="com.test.abc.integration.service.ProcessGateway">
</int:gateway>
<int:splitter id="transactionSplitter" input-channel="transaction-request"
ref="processSplitter" output-channel="splitter-output">
</int:splitter>
<int-jms:outbound-gateway id="splitterJMSGateway"
correlation-key="JMSCorrelationID" request-channel="splitter-output"
request-destination="processInputQueue" reply-channel="aggregator-input"
reply-destination="processOutputQueue" extract-request-payload="true"
extract-reply-payload="true">
<int-jms:reply-listener
max-concurrent-consumers="20" receive-timeout="5000" />
</int-jms:outbound-gateway>
<!-- Inbound Gateway Invokes Service Activator and Sends response back to
the channel -->
<int-jms:inbound-gateway id="seriveActivatorGateway"
request-destination="processInputQueue" request-channel="process-input"
reply-channel="process-output" default-reply-destination="processOutputQueue"
concurrent-consumers="5" max-concurrent-consumers="10"
extract-reply-payload="true" correlation-key="JMSCorrelationID"
extract-request-payload="true" max-messages-per-task="1" />
<int:service-activator id="jbpmServiceActivator"
input-channel="process-input" ref="jbpmService" requires-reply="true"
output-channel="process-output">
</int:service-activator>
<int:aggregator id="transactionAggregator"
input-channel="aggregator-input" ref="processAggregator"
output-channel="aggregator-output" message-store="processResultMessageStore"
send-partial-result-on-expiry="false">
</int:aggregator>
<bean id="processResultMessageStore"
class="org.springframework.integration.store.SimpleMessageStore" />
<bean id="processResultMessageStoreReaper"
class="org.springframework.integration.store.MessageGroupStoreReaper">
<property name="messageGroupStore" ref="processResultMessageStore" />
<property name="timeout" value="5000" />
</bean>
<task:scheduled-tasks>
<task:scheduled ref="processResultMessageStoreReaper"
method="run" fixed-rate="1000" />
</task:scheduled-tasks>
<int:logging-channel-adapter id="logger"
level="DEBUG" log-full-message="true" />
<int-stream:stdout-channel-adapter
id="stdoutAdapter" channel="logger" />
I limited the JMS pipeline only to the Service Activator , which is what I originally wanted .
The only question I have based on the above approach is that do I need to have my Aggregator backed by a database even if I use this across multiple VMS ( Since the JMS gateway in front of it make sure that it receives only the messages that have valid correlation ID ?)
Regards ,
You probably don't need to use JMS between every component. However we have lots of test cases for chained gateways like this, and all works fine.
Something must be wired up incorrectly. Since you didn't show your full configuration, it's hard to speculate.
Be sure to use the latest version (2.2.4) and turn on DEBUG logging and follow a message through the flow; as long as your message payload is identifiable across JMS boundaries, it should be easy to figure out where things go awry.