Notifying the Websocket when no message received in PUBSUB - java

Below is my Message Listener which listens to Redis PUBSUB. It is working fine. I have to implement a feature in such a way that if no messages are received after threshold/specified time, then I have to send a proper message to websocket(subscription URL same as PUBSUB channel). I don't see any way to that as there will be many clients which subscribes to different channels at different times.
Any help or suggestion would be appreciable.
public class PubSubListener implements MessageListener {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate ;
#Override
public void onMessage(Message message, byte[] pattern) {
logger.info(" MESSAGE RECEIVED FROM PUBSUB ");
simpMessagingTemplate.convertAndSend(new String(pattern), message.toString());
}
}

Related

How to rollback in saga pattern when a service fails

I am starting with Saga pattern using Spring cloud and rabbit mq. Following is the problem statement:
I call /service1 (producer) which publishes a message in rabbit mq and that message is consumed by the consumer service.
Now occurs tow cases:
Case 1: Consumer service does its part successfully.
Case 2: Consumer service fails to do its part, thus /service1 has to rollback its changes.
How does /service1 know if consumer is successful or not, so that it can send a success/failure response. Following is the project structure:
Producer:
#RestController
public class ProducerController {
private MessageChannel greet;
public ProducerController(HelloBinding binding) {
greet = binding.greeting();
}
#GetMapping("/greet/{name}")
public void publish(#PathVariable String name) {
String greeting = "Hello, "+name+"!";
Message<String> msg = MessageBuilder.withPayload(greeting)
.build();
this.greet.send(msg);
System.out.println("Message sent to the queue");
AMQP.Basic.Ack;
}
Consumer:
#EnableBinding(HelloBinding.class)
public class HelloListener {
#StreamListener(target=HelloBinding.GREETING)
public void processHelloChannelGreeting(String msg) {
System.out.println("Message received:- "+msg);
}
}
Now how do I tell the producer whether consumer's action is a success or a failure so that producer service sends appropriate response?
The producer can not know what happens after a message has been successfully published to a topic. If you want feedback from the consumer then you need to create a new "response" topic on which the consumer communicates success or failure of processing that message.
You can map the messages by keys.

Spring Stomp CAN send unsolicited messages

In the Spring WebSocket docs I found this sentence:
It is important to know that a server cannot send unsolicited messages.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html
(25.4.1)
However I tried this code:
#Controller
public class WebsocketTest {
#Autowired
public SimpMessageSendingOperations messagingTemplate;
#PostConstruct
public void init(){
ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
statusTimerExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
messagingTemplate.convertAndSend("/topic/greetings", new Object());
}
}, 5000,5000, TimeUnit.MILLISECONDS);
}
}
And the message is broadcasted every 5000ms as expected.
So why Spring docs says that a server cannot send unsollicited messages?
The next sentence might mean that in the stomp.js client you are required to set a subscription:
All messages from a server must be in response to a specific client
subscription
But this does not necessarily mean in response to a request. For example a web socket could send information to the following:
Javascript:
stompClient.subscribe('/return/analyze', function(data) {
generateTableData(JSON.parse(data.body));
});
Spring:
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
public void sendSetpoint(String data) throws Exception {
this.simpMessagingTemplate.convertAndSend("/return/analyze", data);
}
But it cannot send unsolicited messages to the client unless that subscription exists. If this is their intended point it is a little poorly worded.

how to capture subscribe event in my webSocket server with Spring 4

I did a simple web socket communication with spring 4, STOMP and sock.js, following this https://spring.io/guides/gs/messaging-stomp-websocket/
Now I want to upgrade it to simple chat. My problem is that when user subscribes to new chat room, he should get past messages. I don't know how to capture the moment when he subscribed to send him the list of the messages.
I tried using #MessageMapping annotation, but didn't reach any success:
#Controller
public class WebSocketController {
#Autowired
private SimpMessagingTemplate messagingTemplate;
#MessageMapping("/chat/{chatId}")
public void chat(ChatMessage message, #DestinationVariable String chatId) {
messagingTemplate.convertAndSend("/chat/" + chatId, new ChatMessage("message: " + message.getText()));
}
#SubscribeMapping("/chat")
public void chatInit() {
System.out.println("worked");
int chatId = 1; //for example
messagingTemplate.convertAndSend("/chat/" + chatId, new ChatMessage("connected"));
}
}
Then I created that:
#Controller
public class ApplicationEventObserverController implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println(applicationEvent);
}
}
It works, but captures all possible events, I don't think it is a good practice.
So, my question can be rephrased: how to send initial data when user subscried to sth?
You can return anything directly to a client when it subscribes to a destination using a #SubscribeMapping handler method. The returned object won't go to the broker but will be sent directly to the client:
#SubscribeMapping("/chat")
public Collection<ChatMessage> chatInit() {
...
return messages;
}
On the client side:
socket.subscribe("/app/chat", function(message) {
...
});
Check out the chat example on GitHub, which shows this exact scenario.

Reply-To in SpringAMQP being set beforehand?

I am using SpringBoot to start a SpringAMQP application that connect to RabbitMQ queues. I would like to be able to send a message from the producer, specifying the reply-queue so that the consumer would only need to send without having to investigate the destination (hence not having to pass the reply data in the message itself).
this is the configuration I have (shared between producer and consumer)
private static final String QUEUE_NAME = "testQueue";
private static final String ROUTING_KEY = QUEUE_NAME;
public static final String REPLY_QUEUE = "replyQueue";
private static final String USERNAME = "guest";
private static final String PASSWORD = "guest";
private static final String IP = "localhost";
private static final String VHOST = "/";
private static final int PORT = 5672;
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
amqpAdmin().declareQueue(new Queue(QUEUE_NAME));
amqpAdmin().declareQueue(new Queue(REPLY_QUEUE));
return template;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(IP);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
connectionFactory.setVirtualHost(VHOST);
connectionFactory.setPort(PORT);
return connectionFactory;
}
I am sending a message as follows :
public Object sendAndReply(String queue, String content){
return template.convertSendAndReceive(queue, new Data(content), new MessagePostProcessor() {
#Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setReplyTo(ReplyTester.REPLY_QUEUE);
return message;
}
});
}
and awaiting a reply as follows:
public void replyToQueue(String queue){
template.receiveAndReply(queue, new ReceiveAndReplyCallback<Data, Data>() {
#Override
public Data handle(Data payload) {
System.out.println("Received: "+payload.toString());
return new Data("This is a reply for: "+payload.toString());
}
});
}
When sending however, I get the following exception:
Exception in thread "main" org.springframework.amqp.UncategorizedAmqpException: java.lang.IllegalArgumentException: Send-and-receive methods can only be used if the Message does not already have a replyTo property.
at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:66)
at org.springframework.amqp.rabbit.connection.RabbitAccessor.convertRabbitAccessException(RabbitAccessor.java:112)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:841)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:820)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doSendAndReceiveWithTemporary(RabbitTemplate.java:705)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doSendAndReceive(RabbitTemplate.java:697)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertSendAndReceive(RabbitTemplate.java:673)
at org.springframework.amqp.rabbit.core.RabbitTemplate.convertSendAndReceive(RabbitTemplate.java:663)
at prodsend.Prod.sendAndReply(ReplyTester.java:137)
at prodsend.ReplyTester.sendMessages(ReplyTester.java:49)
at prodsend.ReplyTester.main(ReplyTester.java:102)
Caused by: java.lang.IllegalArgumentException: Send-and-receive methods can only be used if the Message does not already have a replyTo property.
at org.springframework.util.Assert.isNull(Assert.java:89)
at org.springframework.amqp.rabbit.core.RabbitTemplate$6.doInRabbit(RabbitTemplate.java:711)
at org.springframework.amqp.rabbit.core.RabbitTemplate$6.doInRabbit(RabbitTemplate.java:705)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:835)
... 8 more
the line ReplyTest.137 points to the return line in the sendAndReply method above.
EDIT:
Here is the Data class that is mentioned above :)
class Data{
public String d;
public Data(String s){ d = s; }
public String toString() { return d; }
}
From the documentation:
Basic RPC pattern. Send a message to a default exchange with a specific routing key and attempt to receive a response. Implementations will normally set the reply-to header to an exclusive queue and wait up for some time limited by a timeout.
So the method convertSendAndReceive handles setting the replyTo header and returns a Messaage - the response. This is a synchronous pattern - RPC.
If you want to do this asynchronously - which you seem to - do not use this method. Use the appropriate convertAndSend method and use the appropriate MessagePostProcessor to add your replyTo header.
As this is asynchronous, you need to register a separate handler for receiving the reply. This needs to be done before sending the message to the other party. This handler will then be called at some point after sending the message - when is unknown. Read section 3.5.2 Asynchronous Consumer of the Spring AQMP Documentation.
So, asynchronous process flow:
sender registers a handler on replyTo queueue
sender sends message with replyTo set
client calls receiveAndReply, processes the message, and sends a reply to the replyTo
sender callback method is triggered
The synchronous process flow is:
sender sends message using sendAndReceive and blocks
client calls receiveAndReply, processes the message, and sends a reply to the replyTo
sender receives the reply, wakes and processes it
So the latter case requires the sender to wait. As you are using receiveXXX rather than registering asynchronous handlers, the sender could be waiting a very long time if the client takes a while to get around to calling receiveXXX.
Incidentally, if you want to use the synchronous approach but use a specific replyTo you can always call setReplyQueue. There is also a setReplyTimeout for the case I mention where the client either doesn't bother to read the message or forgets to reply.

Push message from Java with Spring 4 WebSocket

I'd like to push messages from Java to WebSocket clients. I've successfully made a js client send to the server and receive a message back on 2 js clients, so the client side code works fine.
My issue is that I'd like to initiate a send when events occur within the Java app. So for example every time 10 orders have been placed send a message to all subscribed clients. Is this possible?
My current config:
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/hello">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
#Controller
public class MessageController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting() throws Exception {
return new Greeting("Hello world");
}
}
What I'd like to be able to do is something like this:
public class OrderManager {
#Autowired MessageController messageController;
int orderCount = 0;
public void processOrder(Order o) {
orderCount++;
if(orderCount % 10 == 0)
messageController.greeting();
}
}
and all subscribed clients to the websocket receive a message.
You can use the SimpMessagingTemplate. It's automatically registered. Just autowire it in any Spring bean you want.
#Autowired
private SimpMessagingTemplate template;
...
this.template.convertAndSend("/topic/greetings", text);

Categories