net.leanix.dropkit.amqp.QueueConsumer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of leanix-dropkit Show documentation
Show all versions of leanix-dropkit Show documentation
Base functionality for leanIX dropwizard-based services
package net.leanix.dropkit.amqp;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AlreadyClosedException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownSignalException;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Designed to consume from a single queue (holds the result from basicConsume
* as registeredConsumerTag).
*/
public abstract class QueueConsumer extends DefaultConsumer {
private final Logger log = LoggerFactory.getLogger(QueueConsumer.class);
private final String queueName;
private final ConsumerRegistry registry;
private String registeredConsumerTag;
private volatile boolean unregistering = false;
private volatile long lastUsed;
private final Charset utf8 = Charset.forName("UTF-8");
/**
* This method processes the message which comes from rabbitMQ and contains the business logic for each consumer.
*
* In case that any exception is thrown within this message, the corresponding message will NOT be acknowledged in rabbitMQ
* and therefore {@link #simpleHandle(String)} will be called with the same message again.
* Only in case that the exception is a {@linkplain JsonMappingException}, the message will be acknowledge in rabbitMQ and will
* not processed again.
*
* So, please avoid throwing exceptions here, because this can end in dead loops in case of unprocessable messages.
*
* @param body The pure message in json format which comes from rabbitMQ
* @throws IOException An exception which causes that the message will NOT be acknowledge in rabbitMQ
*/
public abstract void simpleHandle(String body) throws IOException;
public QueueConsumer(String queueName, Channel channel, ConsumerRegistry registry) {
super(channel);
this.queueName = queueName;
this.registry = registry;
this.lastUsed = System.currentTimeMillis();
}
public String getQueueName() {
return queueName;
}
public String getRegisteredConsumerTag() {
return registeredConsumerTag;
}
public void setRegisteredConsumerTag(String consumerTag) {
this.registeredConsumerTag = consumerTag;
}
public void setUnregistering() {
unregistering = true;
}
public long getLastUsed() {
return lastUsed;
}
@Override
public void handleDelivery(
String consumerTag,
Envelope env,
BasicProperties props,
byte[] body) throws IOException
{
// we receive a delivery from a single thread only (no concurrency)
log.debug("received message {} from queue {}", props.getMessageId(), queueName);
lastUsed = System.currentTimeMillis();
String stringBody = new String(body, utf8);
long startTime = System.currentTimeMillis();
try {
simpleHandle(stringBody);
} catch (JsonMappingException e) {
// the format of previous persisted messages can not be read with this version of code
log.warn("can not handle message: {}", stringBody);
}
// try to acknowledge the message, which could be fails in case of long running processing
long duration = System.currentTimeMillis() - startTime;
log.debug("finished message in time: {}", DurationFormatUtils.formatDurationWords(duration, true, false));
try {
getChannel().basicAck(env.getDeliveryTag(), false);
} catch (AlreadyClosedException e) {
if (duration >= ConsumerRegistry.QUEUE_X_EXPIRY_MILLIS) {
// raw: I assume we get sometimes the exception:
// "com.rabbitmq.client.AlreadyClosedException: channel is already closed due to clean channel shutdown" because
// the processing of the messages takes to long and rabbitMQ has removed the queue in the meanwhile.
log.info("Received expected exception {} during long processing message, which takes: {}",
e.getClass().getSimpleName(), DurationFormatUtils.formatDurationWords(duration, true, false));
} else {
throw e;
}
}
}
@Override
public void handleConsumeOk(String consumerTag) {
log.info("consumer started consuming for queue {}, consumerTag={}", queueName, consumerTag);
}
@Override
public void handleCancelOk(String consumerTag) {
log.info("consumer for queue {}, consumerTag={}, was regularly cancelled. unregister it.", queueName, consumerTag);
if (!unregistering) {
unregistering = true;
registry.unregister(this);
}
}
@Override
public void handleCancel(String consumerTag) {
log.info("consumer for queue {}, consumerTag={}, was cancelled, e.g. because queue was deleted. unregister it.",
queueName, consumerTag);
if (!unregistering) {
unregistering = true;
registry.unregister(this);
}
}
@Override
public void handleShutdownSignal(String consumerTag, ShutdownSignalException e) {
log.info("{} for consumer for queue {}, consumerTag={}, was closed. unregister consumer.",
e.isHardError() ? "connection" : "channel", queueName, consumerTag);
if (!unregistering) {
unregistering = true;
registry.unregister(this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy