/***************************************************************************** * * Copyright (C) Zenoss, Inc. 2010, all rights reserved. * * This content is made available according to terms specified in * License.zenoss under the directory where your Zenoss product is installed. * ****************************************************************************/ package org.zenoss.zep.impl; import com.codahale.metrics.MetricRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.TransientDataAccessException; import org.zenoss.amqp.AmqpException; import org.zenoss.amqp.Consumer; import org.zenoss.amqp.Message; import org.zenoss.amqp.QueueListener; import org.zenoss.zep.ZepUtils; import org.zenoss.zep.dao.impl.DaoUtils; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; public abstract class AbstractQueueListener extends QueueListener { private static final Logger logger = LoggerFactory.getLogger(AbstractQueueListener.class); protected ExecutorService executorService; @Autowired protected MetricRegistry metricRegistry; private final String ackMessageTimerName = this.getClass().getName() + ".ackMessage"; private final String handleMessageTimerName = this.getClass().getName() + ".handleMessage"; private final String receiveMessageTimerName = this.getClass().getName() + ".receiveMessage"; private final String rejectMessageTimerName = this.getClass().getName() + ".rejectMessage"; protected abstract String getQueueIdentifier(); public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } protected void rejectMessage(final Consumer<?> consumer, final Message<?> message, final boolean requeue) { try { metricRegistry.timer(rejectMessageTimerName).time(new Callable<Object>() { @Override public Object call() throws Exception { try { consumer.rejectMessage(message, requeue); } catch (AmqpException e) { logger.warn("Failed rejecting message", e); } return null; } }); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected void receive(final Message<com.google.protobuf.Message> message, final Consumer<com.google.protobuf.Message> consumer) throws Exception { metricRegistry.timer(receiveMessageTimerName).time(new Callable<Object>() { @Override public Object call() throws Exception { AbstractQueueListener.this.executorService.submit(new Runnable() { @Override public void run() { try { DaoUtils.deadlockRetry(new Callable<Object>() { @Override public Object call() throws Exception { metricRegistry.timer(handleMessageTimerName).time(new Callable<Object>() { @Override public Object call() throws Exception { handle(message.getBody()); return null; } }); return null; } }); metricRegistry.timer(ackMessageTimerName).time(new Callable<Object>() { @Override public Object call() throws Exception { consumer.ackMessage(message); return null; } }); } catch (Exception e) { if (ZepUtils.isExceptionOfType(e, TransientDataAccessException.class)) { /* Re-queue the message if we get a temporary database failure */ logger.debug("Transient database exception", e); logger.debug("Re-queueing message due to transient failure: {}", message); rejectMessage(consumer, message, true); } else if (!message.getEnvelope().isRedeliver()) { /* Attempt one redelivery of the message */ logger.debug("First failure processing message: " + message, e); rejectMessage(consumer, message, true); } else { /* TODO: Dead letter queue or other safety net? */ logger.warn("Failed processing message: " + message, e); rejectMessage(consumer, message, false); } } } }); return null; } }); } }