RabbitMQ listener can't convert message - java

I have message producer which sends message on rabbit using RabbitTemplate. It has message converter: Jackson2JsonMessageConverter.
I am trying to receive the message from the listener like that:
#RabbitListener(queues = "kolejka")
public void listen(String message) {
try {
log.info("Received ---------------------------------------------------");
log.info(message);
} catch (Exception e) {
log.debug("Error thrown while listening + " + e.getMessage());
}
}
Unfortunately, message is array of numbers from 45 to 130, it is like Ascii, so my listener has bad encoding.
I tried changing listener method to listen(Message message) and then
convert message to object like:
public void listen(Message messsage) {
Transfer transfer = (Transfer) jackson2JsonMessageConverter.fromMessage(messsage);
but it throws me warning that message wasn't encoded properly and was thrown on dead letter queue.

Related

Pending Messages in ActiveMQ

I have deployed my Java-MDB based application using ActiveMQ as messaging service . I could see that a few messages have been in pending status for quite some time on some queues. I have read that this happens when ActiveMQ delivers the message and consumer consumes the message but doesn't send the ack back. But I could not see any related loggers on the consumer/application side which proves that the message is consumed.
Could anyone please help me understand the reason of message being stuck in pending state.
Edit - Adding the details:
We are using Auto-acknowledge as acknowledgeMode and below is the onMessage method used on consumer side.
public void onMessage(Message message) {
try {
// Clear all ThreadLocal in SQLQueryHelper.
SQLQueryHelper.clearCache();
String messageOut = processMessage(message);
// if there is a reply, send it out
if (messageOut != null) {
logger.warn(LoggerKeys.LOG_1_ARGS,
new String[] {"Reply from MDB not supported. " + messageOut});
}
} catch (Throwable e) {
logger.error(LoggerKeys.LOG_1_ARGS,
new String[] {"Error encountered: " + e.toString()});
try {
//put message on error queue
handleError(message, e);
} catch (Throwable e2) {
//retry to put message on error queue
handleErrorAndRollBack(message, e2);
}
}
}

RabbitHandler: How to catch "ListenerExecutionFailedException: Listener method 'no match' threw exception" correctly and proceed working

For an application I am doing some tests with Spring Boot and RabbitMQ.
I set up a very simple Sender - Receiver application:
Sender:
public class Tut1Sender
{
private final Gson gson = new Gson();
#Autowired
private RabbitTemplate template;
#Autowired
private Queue queue;
public static int count = 1;
#Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() throws InterruptedException
{
String message = "Hello World! "+" Nr. "+count;
MessageObject mo = new MessageObject(message);
String toJson = gson.toJson(mo);
this.template.convertAndSend(queue.getName(), toJson);
System.out.println(" [x] Sent '" + toJson + "'");
Thread.sleep(5);
count++;
}
}
This part works just fine and fill my queue with messages.
Here is my receiver:
#RabbitListener(queues = "hello")
public class Tut1Receiver
{
private final Gson gson = new Gson();
#RabbitHandler
public void receive(String in) throws InterruptedException
{
System.out.println("Received Raw: " + in);
MessageObject fromJson = gson.fromJson(in, MessageObject.class);
System.out.println("Received Message '" + fromJson + "'");
int nextInt = ThreadLocalRandom.current().nextInt(1000, 5000);
System.out.println("Sleep for " + nextInt + " ms");
Thread.sleep(nextInt);
}
}
Messages created by the Sender are handled correctly by the receiver. I get a nice output, the message is acknowledged and deleted from the queue.
Then I put a message directly into the queue by the Web-GUI of RabbitMQ.
The sender grabs this message. I can say this because the message created by me switched from status "Ready" to "Unacked" (as displayed in Web-GUI)
The sender gave me no output.
Then I configured the ContainerFactory:
#Profile("receiver")
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory)
{
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(e -> {
System.out.println("Error: "+e);
System.out.println("Raw: "+((ListenerExecutionFailedException) e).getFailedMessage().toString());
});
return factory;
}
Now I am getting the following error (in an endless loop)
Error: org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method 'no match' threw exception
Raw: (Body:'[B#53452feb(byte[11])' MessageProperties [headers={content_type=text/plain, content_encoding=UTF-8}, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=true, receivedExchange=, receivedRoutingKey=hello, deliveryTag=1, consumerTag=NOT_SET, consumerQueue=hello])
How can I handle this error? The sender should just display the error, acknowledging the message and proceed with the next message.
What is the right way to handle faulty messages in general?
For broken message, consumers can reject or deliver the message. If you are sure the broken message can't be processed by any other consumers, you should tell the broker to discard the message or deliver it to a dead-letter-exchange.
From official document of spring amqp, I find:
Another alternative is to set the container's rejectRequeued property to false. This causes all failed messages to be discarded. When using RabbitMQ 2.8.x or higher, this also facilitates delivering the message to a Dead Letter Exchange.
Or, you can throw a AmqpRejectAndDontRequeueException; this prevents message requeuing, regardless of the setting of the rejectRequeued property.

How to identify the MQTT topic that received the message?

The client is subscribed to a x / # topic. There is the possibility of receiving message in the topics x / start and x / stop, and depending on the topic, it performs an action. I wonder how I can identify if it's coming up in the start or stop topic.
In the current code, I send an "action" key in the JSON: "start" or "stop". I want to delete this key and use the format that said above, identifying the topic.
Any further information they deem necessary, please request that I edit the post!
JDK 8
The code:
private MqttCallback callback = new MqttCallback() {
public void connectionLost(Throwable throwable) {
try {
connect();
} catch (MqttException e) {
e.printStackTrace();
}
}
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
String messageReceived = new String(mqttMessage.getPayload());
actionPerformed(messageReceived);
}
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
};
private void actionPerformed(String message) throws IOException {
ClientDTO clientDTO = new ObjectMapper().readValue(message, ClientDTO.class);
if (clientDTO.getAction().equalsIgnoreCase("start")) {
startView(clientDTO);
} else if (clientDTO.getAction().equalsIgnoreCase("stop")) {
stopView();
}
}
public void connect() throws MqttException {
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName("a_nice_username");
options.setPassword("a_cool_password".toCharArray());
options.setAutomaticReconnect(true);
MqttClient client = new MqttClient("someaddress", MqttClient.generateClientId());
client.setCallback(callback);
try {
client.connect(options);
client.subscribe(topic);
TaskbarIcon.alteraIconeOnline();
} catch (Exception e) {
TaskbarIcon.alteraIconeOffline();
}
}
public void tipoConexao(int tipoConex) throws IOException {
switch (tipoConex) {
case 0:
topic += "/operador/" + getIdReceived() + "/#";
System.out.println(topic);
break;
//etc
}
The s in this method is the topic: public void messageArrived(String s, MqttMessage mqttMessage)
As is very well documented here:
messageArrived
void messageArrived(java.lang.String topic, MqttMessage message) throws java.lang.Exception
This method is called when a message arrives from the server.
This method is invoked synchronously by the MQTT client. An acknowledgment is not sent back to the server until this method
returns cleanly.
If an implementation of this method throws an Exception, then the client will be shut down. When the client is next re-connected, any
QoS 1 or 2 messages will be redelivered by the server.
Any additional messages which arrive while an implementation of this method is running, will build up in memory, and will then back up
on the network.
If an application needs to persist data, then it should ensure the data is persisted prior to returning from this method, as after
returning from this method, the message is considered to have been
delivered, and will not be reproducible.
It is possible to send a new message within an implementation of this callback (for example, a response to this message), but the
implementation must not disconnect the client, as it will be
impossible to send an acknowledgment for the message being processed,
and a deadlock will occur.
Parameters:
topic - name of the topic on the message was published to
message - the actual message.
Throws:
java.lang.Exception - if a terminal error has occurred, and the client should be shut down.

Message Header Update Error in ActiveMQ during Retry

I have a requirement where I have to add and update message header in case of message retry.
Here is my listener or consumer. My message is getting retried but I am getting Exception when setting the header. Please advise the correct way of doing this.
As per JMS spec and it says that Message Headers are never read-only.
javax.jms.MessageNotWriteableException: Message properties are read-only
public void onMessage(Message message) {
if (message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
String input = textMessage.getText();
throw new Exception();
} catch (Throwable t) {
t.printStackTrace();
try {
message.setStringProperty("retryable","YES");
} catch (JMSException e) {
e.printStackTrace();
}
throw new RuntimeException(t);
}
}
}
What you are trying won't work for a number of reasons. You are attempting to set a message PROPERTY on an incoming message which will indeed be read-only. The message you receive is a copy of the actual message and not the one that would be resent if inside a TX and was eligible for redelivery.
To do any sort of update to a delivered message that encounters an error during its processing you need to create a new instance and decorate it with the appropriate information and then place it back on the Destination using a MessageProducer.

How can we make producer in spring amqp rabbitmq waiting after sending all messages and release upon receiving all?

I am queuing all messages to rabbitmq queue and processing those on remote server. Below is my producer and reply handler in same class.
public class AmqpAsynchRpcItemWriter<T> implements ItemWriter<T>,
MessageListener {
protected String exchange;
protected String routingKey;
protected String queue;
protected String replyQueue;
protected RabbitTemplate template;
// Reply handler
#Override
public void onMessage(Message message) {
try {
String corrId = new String(message.getMessageProperties()
.getCorrelationId(), "UTF-8");
System.out.println("received " + corrId + " from " + this.replyQueue);
} catch (IOException e) {
e.printStackTrace();
}
}
//Producer
#Override
public void write(List<? extends T> items) throws Exception {
for (T item : items) {
System.out.println(item);
System.out.println("Queing " + item + " to " + this.queue);
Message message = MessageBuilder
.withBody(item.toString().getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setReplyTo(this.replyQueue)
.setCorrelationId(item.toString().getBytes()).build();
template.send(this.exchange, this.routingKey, message);
System.out.println("Queued " + item + " to " + this.queue);
}
// It should wait here untill we get all replies in onMessage, How can we do this ?
}
I am sending all messages in write method and getting replies in onMessage. This is working properly but write doesnt wait for replies, it returns to caller and spring-batch step is marked completed.
But I want the process to wait for replies after sending all message till we get all replies in onMessage. How can we do this ?
You can use any number of synchronization techniques; for example have the listener put the replies in a LinkedBlockingQueue and have the sender take (or poll with timeout) from the queue until all the replies are received.
Or, don't use a listener at all and simply use the same RabbitTemplate to receive() from the reply queue until all the replies are received.
However, receive() returns null if the queue is empty so you'll have to sleep between receives to avoid spinning the CPU.

Categories