/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.hydra.mq;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import com.addthis.codec.jackson.Jackson;
import com.google.common.collect.ImmutableList;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RabbitMessageConsumer<T> extends DefaultConsumer implements MessageConsumer<T> {
private static final Logger log = LoggerFactory.getLogger(RabbitMessageConsumer.class);
@Nonnull
private final String exchange;
@Nonnull
private final ImmutableList<String> routingKeys;
@Nonnull
private final ImmutableList<String> closeUnbindKeys;
@Nonnull
private final String queueName;
private final Set<MessageListener<T>> messageListeners = new HashSet<>();
private final Class<T> messageType;
public RabbitMessageConsumer(@Nonnull Channel channel, @Nonnull String exchange,
@Nonnull String queueName, @Nonnull MessageListener<T> messageListener,
@Nonnull ImmutableList<String> routingKey,
@Nonnull ImmutableList<String> closeUnbindKeys,
@Nonnull Class<T> messageType) {
super(channel);
this.exchange = exchange;
this.queueName = queueName;
this.routingKeys = routingKey;
this.closeUnbindKeys = closeUnbindKeys;
this.messageType = messageType;
addMessageListener(messageListener);
try {
open();
} catch (IOException e) {
log.warn("[rabit.consumer] error starting consumer" + e, e);
}
}
@Override public void open() throws IOException {
getChannel().exchangeDeclare(exchange, "direct");
getChannel().queueDeclare(queueName, true, false, false, null);
for (String routingKey : routingKeys) {
getChannel().queueBind(queueName, exchange, routingKey);
}
getChannel().basicConsume(queueName, false, this);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
T message = Jackson.defaultMapper().readValue(body, messageType);
for (MessageListener<T> messageListener : messageListeners) {
messageListener.onMessage(message);
}
getChannel().basicAck(envelope.getDeliveryTag(), false);
} catch (IOException e) {
log.warn("[rabbitConsumer] error reading message", e);
}
}
@Override public boolean addMessageListener(MessageListener<T> hostMessageListener) {
return messageListeners.add(hostMessageListener);
}
@Override public boolean removeMessageListener(MessageListener<T> hostMessageListener) {
return messageListeners.remove(hostMessageListener);
}
@Override public void close() throws IOException {
IOException firstError = null;
Channel channel = getChannel();
if (channel != null) {
for(String routingKey : closeUnbindKeys) {
try {
channel.queueUnbind(queueName, exchange, routingKey);
} catch (IOException ex) {
firstError = (firstError == null) ? ex : firstError;
}
}
try {
channel.close();
} catch (IOException ex) {
firstError = (firstError == null) ? ex : firstError;
}
}
if (firstError != null) {
throw firstError;
}
}
}