How to acknowledge message through program using Spring AMQP/Spring integration - spring-integration

1) Server sends a message to client.
2) Inbound channel adapter is configured to wait for "MANUAL" acknowledge mode operation from consumer
3) "TaskBundlereceiver" bean is implementing "ChannelAwareMessageListener" and in the implementation method, I am performing message acknowledgement.
I don't see "TaskBundlereceiver" getting executed. Am I missing something ?
Below is the configuration details of the steps that I have explained.
Appreciate your inputs.
#Override
public void onMessage(org.springframework.amqp.core.Message message, Channel channel) throws Exception
{
logger.debug("In onMessage method of the channel aware listener. message =["+message.getBody().toString()+"]");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}
XML Configuration :
<!-- Channel that receives the task bundle from the server for execution -->
<int:channel id="fromKServerChannel"/>
<int-amqp:inbound-channel-adapter id="taskBundleReceiverAdapter"
channel="fromKServerChannel"
error-channel="taskBundleErrorChannel"
acknowledge-mode="MANUAL"
expose-listener-channel="true"
queue-names="kanga_task_queue"
connection-factory="connectionFactory"
concurrent-consumers="20"/>
<int:chain input-channel="fromKServerChannel" output-channel="nullChannel">
<int:service-activator ref="taskBundleReceiver" method="onMessage"/>
<int:service-activator ref="taskBundleExecutor" method="executeBundle"/>
</int:chain>

It doesn't work that way; the listener is the adapter, not the service invoked via the service-activator. The adapter currently does not support passing the channel to the client for manual acks. The expose-listener-channel attribute is for use when using transactions, so a down-stack rabbit template can participate in the transaction.
Why do you want MANUAL acks? AUTO (default) means the ack will be done automatically by the container when the thread returns normally; if your service throws an exception, the message will be nacked.
So, that's how to control the ack.
If you really want to use MANUAL acks, you'll have to use a <rabbit:listener-container/> to invoke your taskBundleReceiver directly. It could then send a message to the executor using a messaging gateway.

Related

exception scenario handling for input http headers in a REST api using spring integration

I need to do some validations on the input http headers, trying to write GET http rest api with some http headers, need to validate if one of the headers has specific value, if not throw an exception. I have used http inbound gateway, created error-channel and using service activator to notify error handler, I am getting below error,
No reply received from error channel within timeout
My code looks like,
<int-http:inbound-gateway
request-channel="sampleRequestChannel"
reply-channel="sampleResponseChannel"
error-channel="apiErrorChannel"
reply-timeout="15000"
supported-methods="GET"
path="/test/{testId}"
mapped-request-headers="*"
payload-expression="#pathVariables.testId">
<int-http:header name="source" expression="#requestParams[source]"/>
</int-http:inbound-gateway>
<int:service-activator input-channel="sampleRequestChannel" ref="testAdapterController" method="getDetails" output-channel="testSourceRouter"/>
<int:service-activator input-channel="apiErrorChannel" ref="testErrorHandler" method="handleFailedRequest" output-channel="sampleResponseChannel"/>
<bean id="loadErrorHandler" class="com.test.adapter.controller.TestErrorHandler"/>
<int:router input-channel="testSourceRouter" expression="headers.source">
<int:mapping value="ABCD" channel="callABCDChannel"/>
<!--<int:mapping value="std" channel="intermediateStdChannel"/>-->
</int:router>
I am throwing exception in the code but its not reaching to the output channel even though I tried using output channel as reply channel.
I can see exception being thrown in my console logs but api doesn't throw it as a part of api response. I am pretty sure my understanding of working with spring integration is very limited, just started to work with it.
could someone please help?
To re-throw an exception to MVC there is just enough to not have that error-channel="apiErrorChannel" configured.
If you'd like to swallow it and return something else, then your testErrorHandler must return that object.
This is going to work as is if all your channels in the flow are DirectChannel which is a default type. If it is not, then you must look into this section of the doc and ensure an errorChannel downstream as a header: https://docs.spring.io/spring-integration/docs/current/reference/html/error-handling.html#error-handling

How can I configure a QueueChannel with Poller using Java DSL?

we used to configure the poller in int:chain like below, the inboundChannel is configured with queue
<int:chain id="messageProcessChain" input-channel="inboundChannel" >
<int:poller fixed-delay="1" max-messages-per-poll="1"/>
This time, we'd like to programmatically initialize int flows, so using DSL. my test code as below:
Jms.messageDrivenChannelAdapter(inConnFactory).destination(flowProperties.getInputQueue()))
.channel(MessageChannels.queue(mongoDbChannelMessageStore,"groupId"))
// .enrichHeaders(testHeaders)
.handle(m -> {
System.out.println("test handler");
logger.info("test dsl flow:"+m.getPayload().toString());
},c -> c.poller(Pollers.fixedRate(flowProperties.getInboudFixedRate()).maxMessagesPerPoll(10)))
.get()
it cannot work with the ".enrichHeaders(testHeaders)", due to'No poller has been defined for endpoint', but neither can I configure a poller for the header richer because it has an implicit SubscribableChannel?
In such case, can only use a bridge to connect the two channel? Is there some other approaches?
Use .enrichHeaders(headers, e -> e.poller(...)).
However, you should NOT use a queue channel with a message-driven channel adapter, you will likely lose messages in the event of a failure.
To achieve concurrency, increase concurrency on the adapter.

Spring Integration Control Bus message to change selector of a JMS Inbound Channel Adapter

I'm currently implementing a flow on a Spring Integration-based (ver. 3.0.1.RELEASE) application that requires to store messages on a JMS queue to be picked up later.
For that, I've been trying to use a Spring Integration JMS Inbound Channel Adapter with a custom selector, and then picking up the message from the queue by changing the JMS selector of the JMSDestinationPollingSource to some matching ID included as a header property.
One of the requirements for this is that I cannot add a new service or a JAVA method, so I've been trying to sort it out using a Control Bus, but keep receiving the same error when I send the message to set the messageSelector to something different.
Inbound Channel Adapter definition:
<int-jms:inbound-channel-adapter id="inboundAdapter"
channel="inboundChannel"
destinationName="bufferQueue"
connection-factory="connectionFactory"
selector="matchingID = 'NO VALUE'">
<int:poller fixed-delay="1000"/>
</int-jms:inbound-channel-adapter>
Message:
#'inboundAdapter.source'.setMessageSelector("matchingID = 'VALUE'")
Error:
EvaluationException: The method 'public void org.springframework.integration.jms.JmsDestinationPollingSource.setMessageSelector(java.lang.String)' is not supported by this command processor. If usign the Control Bus, consider adding #ManagedOperation or #ManagedAttribute.
Which, AFAIK, means that the JmsDestinationPollingSource class is not Control Bus manageable, as it's not passing the ControlBusMethodFilter.
Is this approach nonviable, or is there something I'm missing? Is there any way to set the selector dynamically using SI XML configuration files only?
First of all it is strange to use Java tool and don't allow to write code on Java...
But that is your choice, or as you said requirements.
Change the employer! ;-)
That's correct: Control Bus allows only #ManagedOperation and #ManagedAttribute method. Since JmsDestinationPollingSource.setMessageSelector. We can make it like that. But does it make so much sense if we can reach it a bit different approach?
<int:outbound-channel-adapter id="changeSelectorChannel"
ref"inboundAdapter.source method="setMessageSelector"/>
where a new selector expression should be as a payload of the Message to this channel.

No reply channel error, even if it is defined in the gateway

I have created a spring dsl gateway with request channel and reply channel. This gateway produces output.
#MessaginGateway
public interface Gateway{
#Gateway(requestChannel="reqChannel", replyChannel="replyChannel")
String sayHello(String name);
}
I am trying to test the output in unit testing. So I have created a bridge in my unit test context. And when i try to receive it from bridge channel, it is giving me "no output-channel or reply-channel header available" error.
I have created bridge like below.
#Bean
#BridgeFrom("replyChannel")
public QueueChannel bridgeOutput(){
return MessageChannels.queue.get();
}
In my test, I am sending message to the request channel reqChannel.send(MessageBuilder.withPayload("Name").build()); and I have tried to receive the reply by bridgeOutput.receive(0). It is giving me the above error.
If i call sayHello() method directly, it is working fine. I am just trying to test the gateway, by directly putting message into the channel.
What I am missing?
UPDATE:
<int-enricher request-channel="gatewayRequestChannel" >
<int-property name="name" expression="payload" />
</int-enricher>
In the above, i am putting message into the requestChannel and setting the property. Instead of 'gatewayRequestChannel', can I call a java method there and set the return value?
You cannot do that; the reply channel is inserted as a header by the gateway.
Your bridge is creating a second consumer on the reply channel.
If you want to simulate what the gateway is doing in a unit test, remove that bridge and use:
QueueChannel replyChannel = new QueueChannel();
reqChannel.send(MessageBuilder.withPayload("Name")
.setReplyChannel(replyChannel)
.build());
Message<?> reply = replyChannel.receive(10000);
Inside the gateway, the reply-channel is bridged to the message header; that bridge is one consumer, your bridge is another.
EDIT
You are bypassing the enricher - you seem to have misunderstood the configuration of an enricher. The enricher is a special kind of gateway itself.
Use:
<int-enricher input-channel="enricherChannel"
request-channel="gatewayRequestChannel" >
<int-property name="name" expression="payload" />
</int-enricher>
and send your test message to enricherChannel. The enricher acts as a gateway to the flow on gatewayRequestChannel and enriches the results from the results of that flow.

Message is not coming into channel even my endpoint is declared as Bridge

In my application i have service activator which is taking message in input-channel, doing some process in activator method and putting back the processed message into output-channel to saving in db using mongo-adapter.
I have declare output-channel like <int-channel id="outputchannel"/>
In my junit test to test the message in outputchannel i used as below.
<bridge input-channel="outputchannel" output-channel="testInputChannel">
The testInputChannel is declared as QueueChannel
Still the message is going mongo-adapter input-channel="outputchannel".
Do i need to definitely declare outputchannel as pub-sub channel. Otherwise it won't come to testInputChannel
With two subscribers, the messages will be sent to the mongo adapter and bridge in round-robin fashion.
To test, you could have your test replace the channel with a pub/sub.
Or stop() the mongo adapter; which is generally better for test cases.
You can use auto-startup="${should.start}" and use a property to not start the adapter in test cases, but start it for production.
The mongo adapter won't be subscribed to the channel until it is start() ed.
Or, if your test only sends one message, make sure the bridge is the first subscriber to the channel.

Resources