My <int-jms:outbound-gateway> is not sending messages to destination queue because of which i end up getting MessageTimeoutException.
I tried which worked perfectly and sent the messages which makes sure that i am getting correct messages on request-channel.
Also i see in logs that my outbound gatway is receiving the messages on requestCh1.
org.springframework.integration.jms.JmsOutboundGateway#0 received message: [Payload...]
I am really stuck and dont able to figure out why jms outbound gateway is not sending messages to destination queue.
Any help anybody ?
Config:
<int:publish-subscribe-channel id="requestCh1" />
<int:service-activator input-channel="requestCh1" ref="processor" method="processMsg"/>
<bean id="processor" class="com.processor.ProcessorImpl" />
<int-jms:outbound-gateway
id="eventPublisherGateway"
connection-factory="myConnectionFactory"
request-channel="requestCh1"
request-destination="qequestQueue"
reply-channel="responseCh1"
reply-destination="responseQueue"
receive-timeout="20000">
</int-jms:outbound-gateway>
Stacktrace:
07-15-2014 13:18:49 [threaPool.Thread-1] DEBUG AbstractMessageChannel$ChannelInterceptorList.preSend(334) | preSend on channel 'errorChannel', message: [Payload MessageTimeoutException content=org.springframework.integration.MessageTimeoutException: failed to receive JMS response within timeout of: 20000ms][Headers={id=7c81f473-e60e-30fa-b031-8106b3a2fff0, timestamp=1405444729344}]
07-15-2014 13:18:49 [threaPool.Thread-1] DEBUG AbstractMessageHandler.handleMessage(72) | (inner bean)#3dd4a538 received message: [Payload MessageTimeoutException content=org.springframework.integration.MessageTimeoutException: failed to receive JMS response within timeout of: 20000ms][Headers={id=7c81f473-e60e-30fa-b031-8106b3a2fff0, timestamp=1405444729344}]
07-15-2014 13:18:49 [threaPool.Thread-1] ERROR LoggingHandler.handleMessageInternal(145) | org.springframework.integration.MessageTimeoutException: failed to receive JMS response within timeout of: 20000ms
at org.springframework.integration.jms.JmsOutboundGateway.handleRequestMessage(JmsOutboundGateway.java:667)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:170)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.BroadcastingDispatcher.invokeHandler(BroadcastingDispatcher.java:160)
at org.springframework.integration.dispatcher.BroadcastingDispatcher.dispatch(BroadcastingDispatcher.java:142)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:255)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:223)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:109)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:44)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:94)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.sendMessage(AbstractReplyProducingMessageHandler.java:260)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.sendReplyMessage(AbstractReplyProducingMessageHandler.java:241)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.produceReply(AbstractReplyProducingMessageHandler.java:205)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleResult(AbstractReplyProducingMessageHandler.java:199)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:177)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.endpoint.PollingConsumer.handleMessage(PollingConsumer.java:74)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:205)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.access$000(AbstractPollingEndpoint.java:55)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:149)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:146)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller$1.run(AbstractPollingEndpoint.java:284)
at org.springframework.integration.util.ErrorHandlingTaskExecutor$1.run(ErrorHandlingTaskExecutor.java:52)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Adding JMS Message this gateways is sending with correlation id.
JMS Message class: jms_text
JMSType: null
JMSDeliveryMode: 2
JMSExpiration: 0
JMSPriority: 4
JMSMessageID: ID:414d5120414344534430513720202020537502dd201b6c02
JMSTimestamp: 1405462485258
JMSCorrelationID:bf566441-8ccb-4620-afac-941cc4842a61
JMSDestination: queue://QMGR01/REQUESTQ
JMSReplyTo: queue://QMGR01/RESPONSEQ
JMSRedelivered: false
JMSXDeliveryCount:0
JMSXAppID:Websphere MQ Client for Java
JMS_IBM_PutApplType:28
timestamp:1405462384810
sequenceNumber:3
JMSXUserID:mqcls1
sequenceSize:4
JMS_IBM_PutTime:22574883
JMS_IBM_PutDate:20140715
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Employee><empId>567</empId></<Employee>
Since you say that you get MessageTimeoutException and there is only one place in the JmsOutboundGateway where it is:
if (jmsReply == null) {
if (this.requiresReply) {
throw new MessageTimeoutException(message,
"failed to receive JMS response within timeout of: " + this.receiveTimeout + "ms");
}
else {
return null;
}
}
Hence your issue is around the reply not request.
I mean you send the message to the destination properly, but the other side doesn't send a reply to you to the responseQueue destination.
If you aren't interest in request/reply scenario and it's just enough to send messages, you can switch to the <int-jms:outbound-channel-adapter>.
If it isn't truth, show, please, the StackTrace to see where your MessageTimeoutException is caused.
UPDATE
Can this be because of JmsCorrelationId ?
Yes it can. The another part has to support the JMSCorrelationID property with a value from the request.
JmsOutboundGateway does the correlation by selector:
messageSelector = "JMSCorrelationID = '" + correlationId + "'";
...
messageConsumer = session.createConsumer(replyTo, messageSelector);
Related
I'm trying to send messages from a Spring Boot application using ActiveMQ Artemis and Spring Integration. In the management console I can see the address and the queue with 7 routed message count, but message count is 0 and consumers are not getting the messages:
In my producer I'm using JMS outbound adapter to send messages:
#Bean
public IntegrationFlow outboundFlow(ActiveMQConnectionFactory connectionFactory) {
return IntegrationFlows
.from(requests())
.transform(messageConverterQueue())
.log()
.handle(Jms.outboundAdapter(connectionFactory).destination(new ActiveMQQueue("requests")))
.get();
}
In the logs I can see the GenericMessage with payload and header with no errors from the broker.
For the consumer:
#Bean
public IntegrationFlow inboundFlowW(ActiveMQConnectionFactory connectionFactory) throws Exception {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
.transform(jsonToChunkRequestTransformer)
.handle(chunkProcessorChunkHandler())
.channel(replies())
.get();
}
Previously I forgot to launch the consumer that's why consumer count is 0 but when it's running it turned 1.
Here is the trace:
DEBUG 19268 --- [erContainer#0-1] o.a.a.a.c.p.core.impl.ChannelImpl : RemotingConnectionID=48a825d5 Sending packet nonblocking PACKET(SessionForceConsumerDelivery)[type=78, channelID=11, responseAsync=false, requiresResponse=false, correlationID=-1, packetObject=SessionForceConsumerDelivery, consumerID=0, sequence=14] on channelID=11
DEBUG 19268 --- [erContainer#0-1] o.a.a.a.c.p.core.impl.ChannelImpl : RemotingConnectionID=48a825d5 Writing buffer for channelID=11
INFO 19268 --- [global-threads)] o.s.integration.handler.LoggingHandler : GenericMessage [payload={"jobId":348,...}, headers={id=c69469bd-8c5a-131f-ef61-cf6d86b308ca, json_resolvableType=org.springframework.batch.integration.chunk.ChunkRequest<?>, json__TypeId__=class org.springframework.batch.integration.chunk.ChunkRequest, contentType=application/json, timestamp=1635790793335}]
DEBUG 19268 --- [global-threads)] o.a.a.a.c.p.core.impl.ChannelImpl : RemotingConnectionID=1362aceb Sending blocking PACKET(SessionBindingQueryMessage)[type=49, channelID=11, responseAsync=false, requiresResponse=false, correlationID=-1, packetObject=SessionBindingQueryMessage, address=requests]
DEBUG 19268 --- [-netty-threads)] o.a.a.a.c.p.c.i.RemotingConnectionImpl : RemotingConnectionID=48a825d5 handling packet PACKET(SessionReceiveMessage)[type=75, channelID=11, responseAsync=false, requiresResponse=false, correlationID=-1, packetObject=SessionReceiveMessage, message=ClientMessageImpl[messageID=253545, durable=false, address=replies,userID=null,properties=TypedProperties[_hornetq.forced.delivery.seq=14]], consumerID=0, deliveryCount=0]
DEBUG 19268 --- [erContainer#0-1] o.a.a.a.c.c.impl.ClientConsumerImpl : org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl#5d2e71f5{consumerContext=ActiveMQConsumerContext{id=0}, queueName=replies}::There was nothing on the queue, leaving it now:: returning null
DEBUG 19268 --- [erContainer#0-1] o.a.a.a.c.c.impl.ClientConsumerImpl : org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl#5d2e71f5{consumerContext=ActiveMQConsumerContext{id=0}, queueName=replies}:: returning null
TRACE 19268 --- [erContainer#0-1] o.s.j.l.DefaultMessageListenerContainer : Consumer [ActiveMQMessageConsumer[org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl#5d2e71f5{consumerContext=ActiveMQConsumerContext{id=0}, queueName=replies}]] of session [ActiveMQSession->ClientSessionImpl [name=41d6e210-3b40-11ec-84ef-00ff1cbc4657, username=artemis, closed=false, factory = org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl#2d41bb5a, metaData=(jms-session=,)]#97a3bf2] did not receive a message
DEBUG 19268 --- [erContainer#0-1] o.a.a.a.c.client.impl.ClientSessionImpl : Sending commit
Can anyone tell me what happened to the messages? Why they're missing in the queue?
I have an app on Quarkus that is receiving AmqpMessages, and send them to another topic.
I keep get an error from smallrye saying that the message was rejected.
Here are the properties
mp.messaging.incoming.data.address=incoming
mp.messaging.incoming.data.connector=smallrye-amqp
mp.messaging.incoming.data.host=localhost
mp.messaging.incoming.data.port=5672
mp.messaging.incoming.data.broadcast=true
mp.messaging.incoming.data.durable=false
mp.messaging.outgoing.position.address=outgoing
mp.messaging.outgoing.position.connector=smallrye-amqp
mp.messaging.outgoing.position.host=localhost
mp.messaging.outgoing.position.port=5672
mp.messaging.outgoing.position.durable=false
The class itself
#Incoming("data")
#Outgoing("position")
public CompletionStage handleMessage(final String topic, final MessagingMessage messageToProcess) {
final String message = messageToProcess.getMessageString();
final String tenant = messageToProcess.getTenant();
final String Id = messageToProcess.Id();
final Message message = _gson.fromJson(message, Message.class);
return _service.getStuff(tenant, id)
.thenApply(stuff -> calculate(message, thing))
.thenApply(Data -> buildAmqpMessage(tenant, id, message, Data))
.exceptionally(ex -> {
_logger.errorv("Error handling message: {0} ", ex);
return null;
});
}
public AmqpMessage buildAmqpMessage(final String tenant, final String id,
final Message message, final Data data) {
final OpenMessage messageToSend = buildMessage(message, openClosePercentageData);
return OutgoingAmqpMessage.builder()
.withSubject(_gson.toJson(messageToSend))
.build();
}
The logs output:
2020-05-10 20:35:36,376 DEBUG [io.sma.rea.mes.amq.AmqpConnector] (vert.x-eventloop-thread-1) Sending AMQP message to address `outgoing`
2020-05-10 20:35:36,377 FINEST [io.ver.pro.imp.ProtonTransport] (vert.x-eventloop-thread-1) New Proton Event: LINK_FLOW
2020-05-10 20:35:36,523 FINE [pro.trace] (vert.x-eventloop-thread-1) IN: CH[0] : Flow{nextIncomingId=2, incomingWindow=2147483647, nextOutgoingId=0, outgoingWindow=2147483647, handle=0, deliveryCount=1, linkCredit=250, available=null, drain=false, echo=false, properties=null}
2020-05-10 20:35:36,523 FINE [pro.trace] (vert.x-eventloop-thread-1) IN: CH[0] : Disposition{role=RECEIVER, first=0, last=null, settled=true, state=Rejected{error=Error{condition=amqp:not-found, description='Deliveries cannot be sent to an unavailable address', info=null}}, batchable=false}
2020-05-10 20:35:36,523 FINEST [io.ver.pro.imp.ProtonTransport] (vert.x-eventloop-thread-1) New Proton Event: LINK_FLOW
2020-05-10 20:35:36,523 FINEST [io.ver.pro.imp.ProtonTransport] (vert.x-eventloop-thread-1) New Proton Event: DELIVERY
2020-05-10 20:35:36,524 ERROR [io.sma.rea.mes.amq.AmqpConnector] (vert.x-eventloop-thread-1) Unable to send the AMQP message: java.util.concurrent.CompletionException: io.vertx.core.impl.NoStackTraceThrowable: message rejected (REJECTED
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:331)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:346)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1137)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2159)
at io.vertx.axle.AsyncResultCompletionStage.lambda$toCompletionStage$0(AsyncResultCompletionStage.java:20)
at io.vertx.amqp.impl.AmqpSenderImpl.lambda$doSend$5(AmqpSenderImpl.java:157)
at io.vertx.proton.impl.ProtonDeliveryImpl.fireUpdate(ProtonDeliveryImpl.java:158)
at io.vertx.proton.impl.ProtonTransport.handleSocketBuffer(ProtonTransport.java:160)
at io.vertx.core.net.impl.NetSocketImpl$DataMessageHandler.handle(NetSocketImpl.java:386)
at io.vertx.core.net.impl.NetSocketImpl.lambda$new$2(NetSocketImpl.java:101)
at io.vertx.core.streams.impl.InboundBuffer.handleEvent(InboundBuffer.java:237)
at io.vertx.core.streams.impl.InboundBuffer.write(InboundBuffer.java:127)
at io.vertx.core.net.impl.NetSocketImpl.handleMessage(NetSocketImpl.java:364)
at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:369)
at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:232)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:173)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:830)
As you guys can see, there is an message rejected and no more output, saying why this is happening. Prior to that i can also detect an: description='Deliveries cannot be sent to an unavailable address
Any idea why this is happening. Prior to this we had an JMS implementation with the same topics and that was working fine
Your AMQP broker may not "auto-create" the addresses, and so reject the messages.
Did you try to pre-configure your broker to create these addresses and their type (unicast / multicast) ?
I have a web service that ingests objects, sends a notification over AMQP, and returns a JSON response to the requester. Each request is performed on a single thread and I am trying to implement publisher confirms and I am struggling on how I should set it up. I have it working but I don't like the way I am doing it.
The way I am doing it is:
Put some headers on the message
Have a publish-subscribe-channel with 2 subscribers
Subscriber 1) creates a blocking queue so it is ready
and sends the message over amqp
Subscriber 2) begins pulling for 5 seconds on that queue until it gets its confirm
The amqp:outbound-channel-adapter sends its publisher confirms to a service activator
The publisherConfirmReceiver receives the confirm and puts it in the blocking queue causing the subscriber 2's pulling to complete and return the result of the confirm.
This technique does work properly but I don't like making the assumption that the chain is going to receive the message before the waitForPublisherConfirm Service Activator from the publish subscribe channel. In this case order matters regarding which component receives the message first.
If the waitForPublisherConfirms service activator receives the message first it will just block the thread for 5 seconds, then allow the chain to send the message via the amqp:outbound-channel-adapter.
I tried putting the waitForPublisherConfirms after the amqp:outbound-channel-adapter but since the outbound-channel-adapter doesn't "return" anything so the service activator never gets called after it in the chain.
I feel like there should be a better way of doing this. My goal is to wait for publisher confirms (or a timeout which I cannot find support for in spring's publisher confirms) before sending a response to the requester.
Could you help me shape the solution a little better or let me know if it is OK to rely on the fact that the first subscriber to the publish-subscribe-channel will always receive a message first.
Sorry this one is so long.
Some configuration
<int:header-enricher input-channel="addHeaders" output-channel="metadataIngestNotifications">
<int:header name="routingKey" ref="routingKeyResolver" method="resolveReoutingKey"/>
<int:header name="notificationId" expression="payload.id" />
</int:header-enricher>
<int:chain input-channel="metadataIngestNotifications" output-channel="nullChannel" >
<int:service-activator id="addPublisherConfirmQueue"
requires-reply="false"
ref="publisherConfirmService"
method="addPublisherConfirmQueue" />
<int:object-to-json-transformer id="transformObjectToJson" />
<int-amqp:outbound-channel-adapter id="amqpOutboundChannelAdapter"
amqp-template="rabbitTemplate"
exchange-name="${productNotificationExchange}"
confirm-ack-channel="publisherConfirms"
confirm-nack-channel="publisherConfirms"
mapped-request-headers="*"
routing-key-expression="headers.routingKey"
confirm-correlation-expression="headers.notificationId" />
</int:chain>
<int:service-activator id="waitForPublisherConfirm"
input-channel="metadataIngestNotifications"
output-channel="publisherConfirmed"
requires-reply="true"
ref="publisherConfirmService"
method="waitForPublisherConfirm" />
<int:service-activator id="publisherConfirmReceiver"
ref="publisherConfirmService"
method="receivePublisherConfirm"
input-channel="publisherConfirms"
output-channel="nullChannel" />
Class
public class PublisherConfirmService {
private final Map<String, BlockingQueue<Boolean>> suspenders = new HashMap<>();
public Message addPublisherConfirmQueue(#Header("notificationId") String id, Message m){
LogManager.getLogger(this.getClass()).info("Adding publisher confirm queue.");
BlockingQueue<Boolean> bq = new LinkedBlockingQueue<>();
suspenders.put(id, bq);
return m;
}
public boolean waitForPublisherConfirm(#Header("notificationId") String id) {
LogManager.getLogger(this.getClass()).info("Waiting for publisher confirms for Notification: " + id);
BlockingQueue<Boolean> bq = suspenders.get(id);
try {
Boolean result = bq.poll(5, TimeUnit.SECONDS);
if(result == null){
LogManager.getLogger(this.getClass()).error("The broker took too long to return a publisher confirm. NotificationId: " + id);
return false;
}else if(!result){
LogManager.getLogger(this.getClass()).error("The publisher confirm indicated that the message was not confirmed. NotificationId: " + id);
return false;
}
} catch (InterruptedException ex) {
LogManager.getLogger(this.getClass()).error("Something went wrong polling for the publisher confirm for notificationId: " + id, ex);
return false;
}finally{
suspenders.remove(id);
}
return true;
}
public void receivePublisherConfirm(String id, #Header(AmqpHeaders.PUBLISH_CONFIRM) boolean confirmed){
LogManager.getLogger(this.getClass()).info("Received publisher confirm for Notification: " + id);
if (suspenders.containsKey(id)){
BlockingQueue<Boolean> bq = suspenders.get(id);
bq.add(confirmed);
}
}
}
How about to take a look to the Aggregator solution for the same purpose?
The <recipient-list-router> to send message to the aggregator's input-channel and the second channel for the <int-amqp:outbound-channel-adapter>.
The confirm-ack-channel must be something which brings the message to the same aggregator after some transformation, e.g. a proper extraction for the correlationKey and so on.
We have an inbound channel adapter that receives notifications of an event. The complexity of the consumer's criteria restrict our ability to use a simple routing key to distribute the messages, so the application uses a splitter to send that message to interested subscriber's queues via a direct exchange.
We want to use publisher confirms on our outbound channel adapter the ensure delivery to the client queues. We want to wait for the publisher confirm to ack the original message, and if a publisher confirm fails to be received or if the ack==false we want to nack the original message that came from the inbound channel adapter.
I assume this will be done in the confirm-callback from the Rabbit Template but I am not sure how to accomplish this. (Or if it is even possible)
<rabbit:connection-factory id="rabbitConnectionFactory"
host="${amqpHost}"
username="${amqpUsername}"
password="${amqpPassword}"
virtual-host="${amqpVirtualHost}"
publisher-confirms="true" />
<rabbit:template id="rabbitTemplate"
connection-factory="rabbitConnectionFactory"
confirm-callback="PublisherConfirms" />
<int-amqp:inbound-channel-adapter channel="notificationsFromRabbit"
queue-names="#{'${productNotificationQueue}' + '${queueSuffix}'}"
connection-factory="rabbitConnectionFactory"
mapped-request-headers="*"
message-converter="productNotificationMessageConverter" />
<int:chain input-channel="notificationsFromRabbit" output-channel="notificationsToClients">
<int:service-activator ref="NotificationRouter"
method="addRecipientsHeaders" />
<int:splitter ref="NotificationRouter"
method="groupMessages" />
<int:object-to-json-transformer />
</int:chain>
<int-amqp:outbound-channel-adapter channel="notificationsToClients"
amqp-template="rabbitTemplate"
exchange-name="${servicesClientsExchange}"
routing-key=""
mapped-request-headers="*" />
At the moment we are acking the messages in the groupMessages method by passing the Channel and Delivery tag as paramters. But, if the broker never sends a return or returns with ack=false then it is too late to nack the message from the inbound channel adapter.
Am I going to need a bean that keeps a Map<Channel, Long> of the channel and delivery tags to access in the confirm-callback or is there some other way?
Is the channel from the inbound channel adapter going to be closed by the time I receive a publisher confirm?
As long as you suspend the consumer thread until all the acks/nacks have been received, you can do what you want.
If you make notificationsFromRabbit a publish-subscribe channel you can add another subscriber (service-activator) where you suspend the thread; wait for all the acks/nacks and take the action you desire.
EDIT:
You can also use Spring Integration to manage the acks for you and it will emit them as messages from the outbound adapter (rather than using a callback yourself).
EDIT2:
You could then use the splitter's sequence size/sequence number headers in your correlation data, enabling the release of the consumer when all the acks are received.
EDIT3:
Something like this should work...
On the outbound adapter, set confirm-correlation-expression="#this" (the whole outbound message).
Class with two methods
private final Map<String, BlockingQueue<Boolean> suspenders;
public void suspend(Message<?> original) {
BlockingQueue<Boolean> bq = new LinkedBlockingQueue();
String key = someKeyFromOriginal(original);
suspenders.put(key, bq);
Boolean result = bq.poll(// some timeout);
try {
if (result == null) {
// timed out
}
else if (!result) {
// throw some exception to nack the message
}
}
finally {
suspenders.remove(key);
}
}
public void ackNack(Message<Message<?>> ackNak) {
Message<?> theOutbound = ackNak.payload;
BlockingQueue<Boolean> bq = suspenders.get(someKeyFromOriginal(theOutbound));
if (bq == null) // late ack/nack; ignore
else {
// check the ack/nack header
// if nack, bq.put(false)
// else, use another map field, to
// keep track of ack count Vs sequenceSize header in
// theOutbound; when all acks received, bq.put(true);
}
}
Suspend the consumer thread in the first method; route the acks/nacks from the outbound adapter to the second method.
Caveat: This is not tested, just off the top of my head; but it should be pretty close.
Within our Spring Integration application, when a message is received, an exception is thrown and the transformer does not receive the message.
I think it could be due to the content of the message, as messages of type String are processed correctly.
The following is the signature of the transformer for this message type:
#Transformer
public String transform(Message inboundMessage){
Have also tried
#Transformer
public String transform(Byte[] inboundMessage){
but in both instances, the following exception is thrown:
2013-10-14 07:21:33,547 D|DefaultMessageListenerContainer |Received message of type [class com.solacesystems.jms.message.SolTextMessage] from consumer [Cached JMS MessageConsumer: com.solacesystems.jms.SolQueueReceiver#13b5500] of session [Cached JMS Session: com.solacesystems.jms.SolSession#1fa0f19]
2013-10-14 07:21:33,579 W|DefaultMessageListenerContainer |Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: UTF-8 format error
at com.solacesystems.common.util.UTF8Util.getStringFromUTF8(UTF8Util.java:272) ~[sol-common-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.load(SolTextMessage.java:82) ~[sol-jms-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.getText(SolTextMessage.java:69) ~[sol-jms-6.0.0.146.jar:na]
at org.springframework.jms.support.converter.SimpleMessageConverter.extractStringFromMessage(SimpleMessageConverter.java:177) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.support.converter.SimpleMessageConverter.fromMessage(SimpleMessageConverter.java:94) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.integration.jms.ChannelPublishingJmsMessageListener.onMessage(ChannelPublishingJmsMessageListener.java:266) ~[spring-integration-jms-2.2.0.RC2.jar:na]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:537) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:497) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:468) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:326) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:264) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1071) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1063) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:960) [spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at java.lang.Thread.run(Unknown Source) [na:1.6.0_06]
2013-10-14 07:21:33,579 D|SolMessageConsumer |Entering receive(), timeout: 1000
2013-10-14 07:21:34,391 D|SolMessageConsumer |Leaving receive()
Any help is greatly appreciated
It's not getting as far as the transformer. The problem is within the Solace library when Spring is calling getText...
java.lang.RuntimeException: UTF-8 format error
at com.solacesystems.common.util.UTF8Util.getStringFromUTF8(UTF8Util.java:272) ~[sol-common-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.load(SolTextMessage.java:82) ~[sol-jms-6.0.0.146.jar:na]
at com.solacesystems.jms.message.SolTextMessage.getText(SolTextMessage.java:69) ~[sol-jms-6.0.0.146.jar:na]
at org.springframework.jms.support.converter.SimpleMessageConverter.extractStringFromMessage(SimpleMessageConverter.java:177) ~[spring-jms-3.1.2.RELEASE.jar:3.1.2.RELEASE]
...
It looks like the library thinks the data is in UTF-8 format when it's not.