Spring RabbitMQ - xml configuration - manual ack - java

I have defined configuration for rabbit in spring:
<rabbit:connection-factory id="amqpConnectionFactory" addresses="${amqp.host}:${amqp.port}"
thread-factory="rabbitThreadFactory"
cache-mode="CHANNEL"
channel-cache-size="25"
username="${amqp.user}"
password="${amqp.pass}"
virtual-host="${amqp.vhost}"/>
<rabbit:admin connection-factory="amqpConnectionFactory" id="rabbitAdmin"/>
<rabbit:topic-exchange id="motoTopicExchange" name="moto.ex.topic" >
<rabbit:bindings>
<rabbit:binding pattern="moto.*.speed" queue="motoQueue8"/>
<rabbit:binding pattern="moto.*.tour" queue="motoQueue9"/>
<rabbit:binding pattern="moto.*.naked" queue="motoQueue10"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<rabbit:queue id="motoQueue8" name="moto.queue.8"/>
<rabbit:queue id="motoQueue9" name="moto.queue.9"/>
<rabbit:queue id="motoQueue10" name="moto.queue.10"/>
<rabbit:template id="rabbitTemplate"
connection-factory="amqpConnectionFactory"
retry-template="retryTemplate"
message-converter="rabbitJsonConverter"/>
<bean id="rabbitJsonConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
<rabbit:listener-container connection-factory="amqpConnectionFactory" message-converter="rabbitJsonConverter"
max-concurrency="10" acknowledge="auto">
<rabbit:listener ref="amqpService8" method="handleSimple" queues="motoQueue8"/>
<rabbit:listener ref="amqpService9" method="handleSimple" queues="motoQueue9"/>
<rabbit:listener ref="amqpService10" method="handleSimple" queues="motoQueue10"/>
</rabbit:listener-container>
Where handleSimple method in listeners consumes object for example Motorcycle (there is also json conversion when sending thought amqp).
How could I manually ack massage which was passed into listeners ?
Is it possibleto get MessageHeader alongside object (Motorcycle) ?
I don't want to configure listeners thought annotation.
Thanks

What is the desire for manual acks? It's quite unusual to need them; the container will take care of acking for you.
To use manual acks, you need a ChannelAwareMessageListener implementation.
You would also have to invoke the message converter yourself.

Related

Sending messages with Spring Messaging (Websockets) from a RabbitMQ Listener class

Is it possible to send messages with SimpMessageSendingOperations from a RabbitMQ listener bean?
I have the following listener class:
public class MyJobListener {
#Autowired
public SimpMessageSendingOperations messagingTemplate;
public void handleJob(JobMessage jobMessage) {
doWork(jobMessage);
messagingTemplate.convertAndSend("/topic/greetings", "TEST");
}
}
My Rabbit config file is:
<!-- RabbitMQ configuration -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.connection.host}" port="${rabbitmq.connection.port}" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" />
<!-- Queues -->
<rabbit:queue id="myQueue" name="myQueue" />
<!-- Listeners -->
<bean id="myListener01" class="com.xxx.MyJobListener" />
<bean id="myListener02" class="com.xxx.MyJobListener" />
<bean id="myListener03" class="com.xxx.MyJobListener" />
<bean id="myListener04" class="com.xxx.MyJobListener" />
<rabbit:listener-container connection-factory="connectionFactory" >
<rabbit:listener ref="myListener01" method="handleJob" queue-names="myQueue" />
<rabbit:listener ref="myListener02" method="handleJob" queue-names="myQueue" />
<rabbit:listener ref="myListener03" method="handleJob" queue-names="myQueue" />
<rabbit:listener ref="myListener04" method="handleJob" queue-names="myQueue" />
</rabbit:listener-container>
<!-- Bindings -->
<rabbit:direct-exchange name="directexchange" >
<rabbit:bindings>
<rabbit:binding queue="myQueue"/>
</rabbit:bindings>
</rabbit:direct-exchange>
When message is expected to be sent (messagingTemplate.convertAndSend("/topic/greetings", "TEST")) nothing happens, but if I do the same thing but in a #Controller everything works fine (message is sent through websocket to the browser)
I need to do this to send a notification to the user when the job is finished.
After many tests I changed my rabbit configuration file, leaving only one listener:
<!-- Listeners -->
<bean id="myListener01" class="com.xxx.MyJobListener" />
<rabbit:listener-container connection-factory="connectionFactory" error-handler="queueErrorHandler" >
<rabbit:listener ref="myListener01" method="handleJob" queue-names="myQueue" />
</rabbit:listener-container>
and now it works almost randomly. It's strange, but each 2 calls it works. I mean, two times yes, two times not, two times yes, two times not... and so... It's very strange. I think there is something with the rabbit config...
Definitely is Spring Security configuration. If I disable Spring Security everything works fine. I will find out what it is, and then I'll post the answer here.
I was able to solve it.
The problem was not Spring Security, the problem was I was declarating twice the websocket message broker:
<websocket:message-broker application-destination-prefix="/app" >
<websocket:stomp-endpoint path="/websocket" >
<websocket:sockjs />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic,/user" />
</websocket:message-broker>
These lines resides in my websocket.xml, and this file was imported more than one time because of an "ugly" import sentences distributions along my .xml spring files.
After ordering these imports and ensuring the bean is only created once everything works fine.
May this helps!

AMQP Prevent automatic message reads

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.

How to pipeline amqp queues with spring integration?

I'm used to camel, where it is somewhat simple to pipeline output from one element to the input of another.
I want to send all application events to an AMQP queue, the fire hose, and then route events to different queues depending on the event type. For example, if the event type is session.created I'd like to take it out from the fire hose and send it to the session.created queue.
I have defined the following raabitmq configuration
<rabbit:connection-factory id="connectionFactory" host="localhost"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:queue name="q.firehose"/>
<rabbit:queue name="q.session.created"/>
<rabbit:direct-exchange name="e.firehose">
<rabbit:bindings>
<rabbit:binding key="firehose" queue="q.firehose"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:headers-exchange name="e.router">
<rabbit:bindings>
<rabbit:binding queue="q.session.created">
<rabbit:binding-arguments>
<entry key="x-match" value="all"/>
<entry key="event_type" value="session.created"/>
</rabbit:binding-arguments>
</rabbit:binding>
</rabbit:bindings>
</rabbit:headers-exchange>
And I want to try something like this spring integration configuration:
<int:channel id="fromFirehose"/>
<int:channel id="toRouter"/>
<int-amqp:inbound-channel-adapter channel="fromFirehose" queue-names="q.firehose" connection-factory="connectionFactory"/>
<!-- Some config element here to move all input from the firehose out and put it into e.router--/>
<int-amqp:outbound-channel-adapter channel="toRouter" exchange-name="e.router" amqp-template="routerTemplate" />
Which component is best suited to move input from the fire hose into the e.router exchange? Is this a good approach?
Looks like a transformer can move from messages from one channel to the other but you are obliged to apply a transformation. If there is no other way, is it there a DoNothingTransformer available?
Thanks in advance!
Just link both inbound and outbound adapters to the same channel.
For example:
<int:channel id="fromFirehose"/>
<int-amqp:inbound-channel-adapter channel="fromFirehose" queue-names="q.firehose" connection-factory="connectionFactory"/>
<int-amqp:outbound-channel-adapter channel="fromFirehose" exchange-name="e.router" amqp-template="routerTemplate" />

Adding multiple listeners which will listen to different RabbitMQ queue not working

I hava following spring xml configuration
<bean id="connectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="xxxxxxxx"/>
<property name="username" value="xxxxx"/>
<property name="password" value="xxxxx"/>
<property name="channelCacheSize" value="25"/>
<property name="virtualHost" value="/"/>
<property name="port" value="3453"/>
</bean>
<rabbit:template id="tutorialTemplate" connection-factory="connectionFactory"/>
<!-- 1st queue -->
<rabbit:queue id="veliteQueue" name="ES_queue" durable="true" auto-delete="false" exclusive="false"/>
<rabbit:direct-exchange id="myExchange" durable="true" name="ES_exchange">
<rabbit:bindings>
<rabbit:binding queue="veliteQueue" key="logstash"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 2nd Queue -->
<rabbit:queue id="veliteQueue1" name="ES_queue_Temp" durable="true" auto-delete="false" exclusive="false"/>
<rabbit:direct-exchange id="myExchange1" durable="true" name="ES_exchange_temp">
<rabbit:bindings>
<rabbit:binding queue="ES_queue_Temp" key="logstash_temp"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 2 Listeners for 2 queue's mentioned above -->
<bean id="aListener" class="com.vzw.es.cosumer.SpringMessageListener" autowire="byName"/>
<bean id="aListener1" class="com.vzw.es.cosumer.SpringMessageListener1" autowire="byName"/>
<rabbit:listener-container id="myListenerContainer" connection-factory="connectionFactory" acknowledge="auto" prefetch="750" concurrency="1">
<rabbit:listener ref="aListener" queues="veliteQueue"/>
<rabbit:listener ref="aListener1" queues="veliteQueue1"/>
</rabbit:listener-container>
Now in my Java code I have 2 Listener classes: com.vzw.es.cosumer.SpringMessageListener and com.vzw.es.cosumer.SpringMessageListener1. Now When I am running my main class only 1 listener's onMessage method is getting invoked i.e. SpringMessageListener1, I did check from RabbitMQ prespective and bothe queues have enough messages to consume.
Also when I comment out the 2nd queue and its listener from xml SpringMessageListener works perfectly.
It's a bug in the container parser, each listener gets its own container (the namespace is just a convenient way to specify common attributes). If you remove the id="myListenerContainer", it will work - because each container gets a (different) generated name. With the id specified, both beans get the same name, and the last definition replaces the first.
Alternatively, declare two separate container elements, with different IDs, and each having just one listener.
Thanks for finding this.
Please open a JIRA issue
EDIT: This issue was resolved in version 1.2.1.

Spring Integration - Gateway - Splitter - Aggregator with JMS

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.

Categories

Resources