Spring AMQP - How to confirm that a message is delivered and Routed successfully? - spring-boot

I am looking for a way to delivery a message, and once the message is delivered (and routed) successfully, i need to perform some operations.
I have enabled publisher confirms and returns by:
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true
I have configured return and confirm callback on the rabbit template:
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
System.out.println("Message returned");
});
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
System.out.println("confirm"); //correlationData.returnedMessage has the original message
});
Here is my publish code:
CorrelationData crd = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("X-ORDERS", "ORDER_PLACED", request, crd);
crd.getFuture().addCallback(new ListenableFutureCallback<Confirm>() {
#Override
public void onFailure(Throwable throwable) {
log.info("Failure received");
}
#Override
public void onSuccess(Confirm confirm) {
if(confirm.isAck()){
log.info("Success received");
doSomethingAfterSuccess();
}}
});
Now, when i publish a message that is unable to route the message :-
rabbitTemplate's returnCallBack AND confirmCallBack are also being
called
the onSuccess(..) of the correlationData is still called with
isAck() = true
So, how can I check if the message is delivered successfully and routed?
EDIT: Found solution. The publish code :
CorrelationData crd = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("X-ORDERS", "ORDER_PLACED", request, crd);
crd.getFuture().addCallback(new ListenableFutureCallback<Confirm>() {
#Override
public void onFailure(Throwable throwable) {
log.info("Failure received");
}
#Override
public void onSuccess(Confirm confirm) {
if(confirm.isAck() && crd.getReturnedMessage == null){
log.info("Success received");
doSomethingAfterSuccess();
}}
});
basically changed the condition in onSuccess to "confirm.isAck() && crd.getReturnedMessage == null"

That is per the RabbitMQ documentation - you still get a positive ack, but it is guaranteed to be delievered after the return.
So simply check that the future.returnedMessage is not null in onSuccess().
See the documentation.
In addition, when both confirms and returns are enabled, the CorrelationData is populated with the returned message. It is guaranteed that this occurs before the future is set with the ack.

Related

PublishSubscribeChannel having multiple subscribers and return value

I would like to understand how returning values work for PublishSubscribeChannel having multiple subscribers.
#Bean
public PublishSubscribeChannel channel(){
return new PublishSubscribeChannel();
}
#Bean
#ServiceActivator(inputChannel = "channel")
public MessageHandler handler1() {
//...
return handler1;
}
#Bean
#ServiceActivator(inputChannel = "channel")
public MessageHandler handler2() {
//...
return handler2;
}
#Bean
#ServiceActivator(inputChannel = "channel")
public MessageHandler handler3() {
//...
return handler3;
}
#MessagingGateway
public interface TestGateway{
#Gateway(requestChannel = "channel")
String method(String payload);
}
method expects some String as a return type. If a message is sent to all three handlers via channel, the value coming from which handler would be returned? From what I understand, messages are sent to each subscriber one by one, so would it be the value returned by the last handler?
Also, would it be possible to have handlers returning type different than the method return type, also if it wouldn't necessarily expect String?
When it comes to a scenario where any Exception occurs, I believe if setIgnoreFailures = false, the processing would stop on it and not process to the next handler. Otherwise, the last exception would be thrown.
Thanks in advance
I'm sure there is a specific business task behind your question.
But i you really are about an academic knowledge to see how Spring Integration works internally, then here is some answer for you.
Since your PublishSubscribeChannel is not configure with an Executor, then all your subscribers are called one by one, and only when the previous has done its job. And the part of that job is really a reply producing. So, if your first MessageHandler produced some reply, then exactly this one fulfills CountDownLatch in the TempraryReplyChannel for a gateway request-reply functionality.
The replies from the rest of handlers are going to be ignored and they may throw a late reply error.
Yes, you can return any type as long as it can be converted to the expected return type. See more info about ConversionService: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints.html#payload-type-conversion
About ignoreFailures I'd suggest to look into a PublishSubscribeChannel source code and how it is propagated down to BroadcastingDispatcher:
private boolean invokeHandler(MessageHandler handler, Message<?> message) {
try {
handler.handleMessage(message);
return true;
}
catch (RuntimeException e) {
if (!this.ignoreFailures) {
if (e instanceof MessagingException && ((MessagingException) e).getFailedMessage() == null) { // NOSONAR
throw new MessagingException(message, "Failed to handle Message", e);
}
throw e;
}
else if (this.logger.isWarnEnabled()) {
logger.warn("Suppressing Exception since 'ignoreFailures' is set to TRUE.", e);
}
return false;
}
}
And no: otherwise none exception will be thrown. See that code again.

Custom Event Notifier for apache camel doesn't work for exchange events

I have a spring-boot application that implements a camel routing service. I want to know if the consumers queues are alive or not (because those queues are not in my system). I implemented a Event Notifier to know if the exchange sent event it's triggered or not. But my custom implementation of the Event notifier is not working. I can see in the logs when camel context event is triggered but this is all. No other event is captured by the event notifier.
Thanks.
This is may event notifier class:
#Component
public class MyLoggingSentEventNotifer extends EventNotifierSupport {
private static final Logger logger = LoggerFactory.getLogger(MyLoggingSentEventNotifer.class);
#Override
public void notify(final EventObject event) throws Exception {
if (event instanceof CamelContextStartedEvent) {
}
if (event instanceof ExchangeSentEvent) {
final ExchangeSentEvent sent = (ExchangeSentEvent) event;
log.info("Took {} millis to send to: {}", sent.getTimeTaken(), sent.getEndpoint());
}
if (event instanceof ExchangeCreatedEvent) {
final ExchangeSendingEvent sending = (ExchangeSendingEvent) event;
log.info("Sending to to: {}", sending.getEndpoint());
}
}
#Override
public boolean isEnabled(final EventObject event) {
if (event instanceof CamelContextStartedEvent) {
return true;
}
return false;
}
}
The problem is your isEnabled method where you should filter which events you want to accept. And in your code, you only accept the camel context started event, and therefore you only get that. Instead either just return true for all events, or filter the ones you only want.

rabbitmq spring ReturnCallback and confimcallback

Below is my code:
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new ReturnCallback() {
#Override
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange, String routingKey) {
System.out.println("Received returnedMessage with result {}"
+ routingKey);
}
});
rabbitTemplate.setConfirmCallback(new ConfirmCallback() {
#Override
public void confirm(CorrelationData correlationData, boolean ack,
String cause) {
System.out
.println("*************************************************************************************"
+ ack);
//log.info("Received confirm with result {}", ack);
System.out.println("Message received by broker");
}
});
It not printing the message "Message received by broker".
Help me any one for same. Thanks in Advance!
You also need to configure the connection factory to support these features see the documentation.
For returned messages, the template’s mandatory property must be set to true, or the mandatory-expression must evaluate to true for a particular message. This feature requires a CachingConnectionFactory that has its publisherReturns property set to true
...
For Publisher Confirms (aka Publisher Acknowledgements), the template requires a CachingConnectionFactory that has its publisherConfirms property set to true.
...

Tryus websocket client - onMessage does not get called although connection is succesful

I am successfully connecting to a local websocket server with tyrus, but the onMessage method does not get called. I setup Fiddler as proxy in between and I see that the server responds with two messages, however, they are not printed out in my code. I more or less adapted the sampe code:
The onOpen Message is printed out
public static void createAndConnect(String channel) {
CountDownLatch messageLatch;
try {
messageLatch = new CountDownLatch(1);
final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build();
ClientManager client = ClientManager.createClient();
client.connectToServer(new Endpoint() {
#Override
public void onOpen(Session session, EndpointConfig config) {
System.out.println("On Open and is Open " + session.isOpen());
session.addMessageHandler((Whole<String>) message -> {
System.out.println("Received message: " + message);
messageLatch.countDown();
});
}
}, cec, new URI("ws://192.168.1.248/socket.io/1/websocket/" + channel));
messageLatch.await(5, TimeUnit.SECONDS); //I also tried increasing timeout to 30sec, doesn't help
} catch (Exception e) {
e.printStackTrace();
}
}
That's a known issue - it will work if you rewrite lambda to anonymous class or use Session#addMessageHandler(Class, MessageHandler) (you can use lambdas here).

Google Web Toolkit (GWT) EventBus event firing/handling

Background Story:
I am developing a GWT application, using the standard MVP design pattern, and also using RPC to get data from my custom data handling servlet (does a lot behind the scenes). Anyway, my goal is to create a very simple custom caching mechanism, that stores the data returned from the RPC callback in a static cache POJO. (The callback also sends a custom event using the SimpleEventBus to all registered handlers.) Then when I request the data again, I'll check the cache before doing the RPC server call again. (And also send a custom event using the EventBus).
The Problem:
When I send the event from the RPC callback, everything works fine. The problem is when I send the event outside the RPC callback when I just send the cached object. For some reason this event doesn't make it to my registered handler. Here is some code:
public void callServer(final Object source)
{
if(cachedResponse != null)
{
System.err.println("Getting Response from Cache for: "+ source.getClass().getName());
//Does this actually fire the event?
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
else
{
System.err.println("Getting Response from Server for: "+ source.getClass().getName());
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
}
Now I have two Activities, HelloActivity and GoodbyeActivity (taken from: GWT MVP code)
They also print out messages when the handler is called. Anyway, this is the output I get from the logs: (Not correct)
Getting Response from Cache for: com.hellomvp.client.activity.HelloActivity
Response in GoodbyeActivity from: com.hellomvp.client.activity.HelloActivity
Getting Response from Cache for: com.hellomvp.client.activity.GoodbyeActivity
Response in HelloActivity from: com.hellomvp.client.activity.GoodbyeActivity
What I expect to get is this:
Getting Response from Cache for: com.hellomvp.client.activity.HelloActivity
Response in HelloActivity from: com.hellomvp.client.activity.HelloActivity
Getting Response from Cache for: com.hellomvp.client.activity.GoodbyeActivity
Response in GoodbyeActivity from: com.hellomvp.client.activity.GoodbyeActivity
And I will get this expected output if I change the above code to the following: (This is the entire file this time...)
package com.hellomvp.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.hellomvp.events.ResponseEvent;
public class RequestManager {
private EventBus eventBus;
private String cachedResponse;
private HelloServiceAsync service = GWT.create(HelloService.class);
public RequestManager(EventBus eventBus)
{
this.eventBus = eventBus;
}
public void callServer(final Object source)
{
if(cachedResponse != null)
{
System.err.println("Getting Response from Cache for: "+ source.getClass().getName());
service.doNothing(new AsyncCallback<Void>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(Void result) {
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
else
{
System.err.println("Getting Response from Server for: "+ source.getClass().getName());
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
System.err.println("RPC Call Failed.");
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
eventBus.fireEventFromSource(new ResponseEvent(cachedResponse),source);
}
});
}
}
}
So the point it out, the only change is that I created a new RPC call that does nothing, and send the event in its callback, with the cached data instead, and it causes the application to work as expected.
So the Question:
What am I doing wrong? I don't understand why 'eventBus.fireEvent(...)' Needs to be in an RPC Callback to work properly. I'm thinking this is a threading issue, but I have searched Google in vain for anything that would help.
I have an entire Eclipse project that showcases this issue that I'm having, it can be found at: Eclipse Problem Project Example
Edit: Please note that using eventBus.fireEventFromSource(...) is only being used for debugging purposes, since in my actual GWT Application I have more than one registered Handler for the events. So how do you use EventBus properly?
If I understand your problem correctly you are expecting calls to SimpleEventBus#fireEventFromSource to be routed only to the source object. This is not the case - the event bus will always fire events to all registered handlers. In general the goal of using an EventBus is to decouple the sources of events from their handlers - basing functionality on the source of an event runs counter to this goal.
To get the behavior you want pass an AsyncCallback to your caching RPC client instead of trying to use the EventBus concept in a way other than intended. This has the added benefit of alerting the Activity in question when the RPC call fails:
public class RequestManager {
private String cachedResponse = null;
private HelloServiceAsync service = GWT.create(HelloService.class);
public void callServer(final AsyncCallback<String> callback) {
if (cachedResponse != null) {
callback.onSuccess(cachedResponse);
} else {
service.callServer(new AsyncCallback<String>(){
#Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
#Override
public void onSuccess(String result) {
cachedResponse = result;
callback.onSuccess(cachedResponse);
}
});
}
}
}
And in the Activity:
clientFactory.getRequestManager().callServer(new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
// Handle failure.
}
#Override
public void onSuccess(String result) {
helloView.showResponse(result);
}
});

Resources