com.github.fridujo.rabbitmq.mock.MockChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rabbitmq-mock Show documentation
Show all versions of rabbitmq-mock Show documentation
Mock for RabbitMQ Java amqp-client
package com.github.fridujo.rabbitmq.mock;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Command;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.ConsumerShutdownSignalCallback;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.Method;
import com.rabbitmq.client.ReturnCallback;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;
import com.rabbitmq.client.impl.AMQImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.fridujo.rabbitmq.mock.exchange.MockExchange;
import com.github.fridujo.rabbitmq.mock.metrics.MetricsCollectorWrapper;
public class MockChannel implements Channel {
private static final Logger LOGGER = LoggerFactory.getLogger(MockChannel.class);
private final int channelNumber;
private final MockNode node;
private final MockConnection mockConnection;
private final MetricsCollectorWrapper metricsCollectorWrapper;
private final AtomicBoolean opened = new AtomicBoolean(true);
private final AtomicLong deliveryTagSequence = new AtomicLong();
private final RandomStringGenerator queueNameGenerator = new RandomStringGenerator(
"amq.gen-",
"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
22);
private final Set confirmListeners = new HashSet<>();
private String lastGeneratedQueueName;
private Transaction transaction;
private boolean confirmMode = false;
private long nextPublishSeqNo = 1L;
public MockChannel(int channelNumber, MockNode node, MockConnection mockConnection, MetricsCollectorWrapper metricsCollectorWrapper) {
this.channelNumber = channelNumber;
this.node = node;
this.mockConnection = mockConnection;
this.metricsCollectorWrapper = metricsCollectorWrapper;
metricsCollectorWrapper.newChannel(this);
}
@Override
public int getChannelNumber() {
return channelNumber;
}
@Override
public MockConnection getConnection() {
return mockConnection;
}
@Override
public void close() {
// Called by RabbitTemplate#execute, RabbitTemplate#send, BlockingQueueConsumer#stop
close(AMQP.REPLY_SUCCESS, "OK");
}
@Override
public void close(int closeCode, String closeMessage) {
metricsCollectorWrapper.closeChannel(this);
opened.set(false);
}
@Override
public void abort() {
abort(AMQP.REPLY_SUCCESS, "OK");
}
@Override
public void abort(int closeCode, String closeMessage) {
close(closeCode, closeMessage);
}
@Override
public void addReturnListener(ReturnListener listener) {
throw new UnsupportedOperationException();
}
@Override
public ReturnListener addReturnListener(ReturnCallback returnCallback) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeReturnListener(ReturnListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void clearReturnListeners() {
throw new UnsupportedOperationException();
}
@Override
public void addConfirmListener(ConfirmListener listener) {
confirmListeners.add(listener);
}
@Override
public ConfirmListener addConfirmListener(ConfirmCallback ackCallback, ConfirmCallback nackCallback) {
ConfirmListener confirmListener = new ConfirmListenerWrapper(ackCallback, nackCallback);
addConfirmListener(confirmListener);
return confirmListener;
}
@Override
public boolean removeConfirmListener(ConfirmListener listener) {
return confirmListeners.remove(listener);
}
@Override
public void clearConfirmListeners() {
confirmListeners.clear();
}
@Override
public Consumer getDefaultConsumer() {
throw new UnsupportedOperationException();
}
@Override
public void setDefaultConsumer(Consumer consumer) {
throw new UnsupportedOperationException();
}
@Override
public void basicQos(int prefetchSize, int prefetchCount, boolean global) {
// Nothing to be done
}
@Override
public void basicQos(int prefetchCount, boolean global) {
basicQos(0, prefetchCount, true);
}
@Override
public void basicQos(int prefetchCount) {
// Called when BlockingQueueConsumer#start
basicQos(prefetchCount, true);
}
@Override
public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) {
basicPublish(exchange, routingKey, false, props, body);
}
@Override
public void basicPublish(String exchange, String routingKey, boolean mandatory, AMQP.BasicProperties props, byte[] body) {
basicPublish(exchange, routingKey, false, false, props, body);
}
@Override
public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, AMQP.BasicProperties props, byte[] body) {
getTransactionOrNode().basicPublish(exchange, routingKey, mandatory, immediate, nullToEmpty(props), body);
metricsCollectorWrapper.basicPublish(this);
if (confirmMode) {
safelyInvokeConfirmListeners();
nextPublishSeqNo++;
}
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException {
return exchangeDeclare(exchange, type, false, true, false, Collections.emptyMap());
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) throws IOException {
return exchangeDeclare(exchange, type, false);
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException {
return exchangeDeclare(exchange, type, durable, true, Collections.emptyMap());
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable) throws IOException {
return exchangeDeclare(exchange, type, durable, true, Collections.emptyMap());
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) throws IOException {
return exchangeDeclare(exchange, type, durable, autoDelete, false, arguments);
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, Map arguments) throws IOException {
return exchangeDeclare(exchange, type, durable, autoDelete, false, arguments);
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchangeName, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException {
Optional exchange = node.getExchange(exchangeName);
if (exchange.isPresent() && !exchange.get().getType().equals(type)) {
throw AmqpExceptions.inequivalentExchangeRedeclare(this, "/", exchangeName, exchange.get().getType(), type);
}
return node.exchangeDeclare(exchangeName, type, durable, autoDelete, internal, nullToEmpty(arguments));
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException {
return exchangeDeclare(exchange, type.getType(), durable, autoDelete, internal, arguments);
}
@Override
public void exchangeDeclareNoWait(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException {
exchangeDeclare(exchange, type, durable, autoDelete, internal, arguments);
}
@Override
public void exchangeDeclareNoWait(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException {
exchangeDeclareNoWait(exchange, type.getType(), durable, autoDelete, internal, arguments);
}
@Override
public AMQP.Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException {
if (!node.getExchange(name).isPresent()) {
throw AmqpExceptions.exchangeNotFound(this, "/", name);
}
return new AMQImpl.Exchange.DeclareOk();
}
@Override
public AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) {
return node.exchangeDelete(exchange);
}
@Override
public void exchangeDeleteNoWait(String exchange, boolean ifUnused) {
exchangeDelete(exchange, ifUnused);
}
@Override
public AMQP.Exchange.DeleteOk exchangeDelete(String exchange) {
return exchangeDelete(exchange, false);
}
@Override
public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey) {
return exchangeBind(destination, source, routingKey, Collections.emptyMap());
}
@Override
public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) {
return node.exchangeBind(destination, source, routingKey, nullToEmpty(arguments));
}
@Override
public void exchangeBindNoWait(String destination, String source, String routingKey, Map arguments) {
exchangeBind(destination, source, routingKey, arguments);
}
@Override
public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey) {
return exchangeUnbind(destination, source, routingKey, Collections.emptyMap());
}
@Override
public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map arguments) {
return node.exchangeUnbind(destination, source, routingKey, nullToEmpty(arguments));
}
@Override
public void exchangeUnbindNoWait(String destination, String source, String routingKey, Map arguments) {
exchangeUnbind(destination, source, routingKey, arguments);
}
@Override
public AMQP.Queue.DeclareOk queueDeclare() {
return queueDeclare("", false, true, true, Collections.emptyMap());
}
@Override
public AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) {
return node.queueDeclare(generateIfEmpty(queue), durable, exclusive, autoDelete, nullToEmpty(arguments), this);
}
@Override
public void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) {
queueDeclare(queue, durable, exclusive, autoDelete, arguments);
}
@Override
public AMQP.Queue.DeclareOk queueDeclarePassive(String queueName) throws IOException {
String definitiveQueueName = lastGeneratedIfEmpty(queueName);
Optional queue = node.getQueue(definitiveQueueName);
if (!queue.isPresent()) {
throw AmqpExceptions.queueNotFound(this, "/", queueName);
}
return new AMQImpl.Queue.DeclareOk(definitiveQueueName, queue.get().messageCount(), queue.get().consumerCount());
}
@Override
public AMQP.Queue.DeleteOk queueDelete(String queue) {
return queueDelete(queue, false, false);
}
@Override
public AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) {
return node.queueDelete(lastGeneratedIfEmpty(queue), ifUnused, ifEmpty);
}
@Override
public void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) {
queueDelete(queue, ifUnused, ifEmpty);
}
@Override
public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey) {
return queueBind(queue, exchange, routingKey, Collections.emptyMap());
}
@Override
public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) {
return node.queueBind(lastGeneratedIfEmpty(queue), exchange, routingKey, nullToEmpty(arguments));
}
@Override
public void queueBindNoWait(String queue, String exchange, String routingKey, Map arguments) {
queueBind(queue, exchange, routingKey, arguments);
}
@Override
public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) {
return queueUnbind(queue, exchange, routingKey, Collections.emptyMap());
}
@Override
public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map arguments) {
return node.queueUnbind(lastGeneratedIfEmpty(queue), exchange, routingKey, nullToEmpty(arguments));
}
@Override
public AMQP.Queue.PurgeOk queuePurge(String queue) {
return node.queuePurge(lastGeneratedIfEmpty(queue));
}
@Override
public GetResponse basicGet(String queue, boolean autoAck) {
GetResponse getResponse = node.basicGet(lastGeneratedIfEmpty(queue), autoAck, this::nextDeliveryTag);
if (getResponse != null) {
metricsCollectorWrapper.consumedMessage(this, getResponse.getEnvelope().getDeliveryTag(), autoAck);
}
return getResponse;
}
@Override
public void basicAck(long deliveryTag, boolean multiple) {
getTransactionOrNode().basicAck(deliveryTag, multiple);
metricsCollectorWrapper.basicAck(this, deliveryTag, multiple);
}
@Override
public void basicNack(long deliveryTag, boolean multiple, boolean requeue) {
getTransactionOrNode().basicNack(deliveryTag, multiple, requeue);
metricsCollectorWrapper.basicNack(this, deliveryTag);
}
@Override
public void basicReject(long deliveryTag, boolean requeue) {
getTransactionOrNode().basicReject(deliveryTag, requeue);
metricsCollectorWrapper.basicReject(this, deliveryTag);
}
@Override
public String basicConsume(String queue, Consumer callback) {
return basicConsume(queue, false, callback);
}
@Override
public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback) {
return basicConsume(queue, false, deliverCallback, cancelCallback);
}
@Override
public String basicConsume(String queue, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, false, deliverCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, false, deliverCallback, cancelCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, Consumer callback) {
return basicConsume(queue, autoAck, Collections.emptyMap(), callback);
}
@Override
public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) {
return basicConsume(queue, autoAck, Collections.emptyMap(), deliverCallback, cancelCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, Collections.emptyMap(), deliverCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, Collections.emptyMap(), deliverCallback, cancelCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) {
return basicConsume(queue, autoAck, "", false, false, arguments, callback);
}
@Override
public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) {
return basicConsume(queue, autoAck, arguments, deliverCallback, cancelCallback, null);
}
@Override
public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, arguments, deliverCallback, null, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, "", false, false, arguments, deliverCallback, cancelCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) {
return basicConsume(queue, autoAck, consumerTag, false, false, Collections.emptyMap(), callback);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback) {
return basicConsume(queue, autoAck, consumerTag, deliverCallback, cancelCallback, null);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, consumerTag, deliverCallback, null, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, consumerTag, false, false, Collections.emptyMap(), deliverCallback, cancelCallback, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) {
String serverConsumerTag = node.basicConsume(lastGeneratedIfEmpty(queue), autoAck, consumerTag, noLocal, exclusive, nullToEmpty(arguments), callback, this::nextDeliveryTag);
metricsCollectorWrapper.basicConsume(this, serverConsumerTag, autoAck);
return serverConsumerTag;
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) {
return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, deliverCallback, cancelCallback, null);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, deliverCallback, null, shutdownSignalCallback);
}
@Override
public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) {
return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, new ConsumerWrapper(deliverCallback, cancelCallback, shutdownSignalCallback));
}
@Override
public void basicCancel(String consumerTag) {
// Called when BlockingQueueConsumer#basicCancel (after AbstractMessageListenerContainer#stop)
node.basicCancel(consumerTag);
metricsCollectorWrapper.basicCancel(this, consumerTag);
}
@Override
public AMQP.Basic.RecoverOk basicRecover() {
return basicRecover(true);
}
@Override
public AMQP.Basic.RecoverOk basicRecover(boolean requeue) {
return node.basicRecover(true);
}
@Override
public AMQP.Tx.SelectOk txSelect() {
if (confirmMode) {
throw new IllegalStateException("Once a channel is in confirm mode, it cannot be made transactional");
}
if (transaction == null) {
transaction = new Transaction(this.node);
}
return new AMQImpl.Tx.SelectOk();
}
@Override
public AMQP.Tx.CommitOk txCommit() {
if (transaction == null) {
throw new IllegalStateException("No started transaction (make sure you called txSelect before txCommit");
}
transaction.commit();
transaction = null;
return new AMQImpl.Tx.CommitOk();
}
@Override
public AMQP.Tx.RollbackOk txRollback() {
if (transaction == null) {
throw new IllegalStateException("No started transaction (make sure you called txSelect before txRollback");
}
transaction = null;
return new AMQImpl.Tx.RollbackOk();
}
@Override
public AMQP.Confirm.SelectOk confirmSelect() {
if (transaction != null) {
throw new IllegalStateException("A transactional channel cannot be put into confirm mode");
}
confirmMode = true;
return new AMQImpl.Confirm.SelectOk();
}
@Override
public long getNextPublishSeqNo() {
if (!confirmMode) {
return 0L;
} else {
return nextPublishSeqNo;
}
}
@Override
public boolean waitForConfirms() throws IllegalStateException {
if (!confirmMode) {
throw new IllegalStateException("Not in confirm mode");
}
return true;
}
@Override
public boolean waitForConfirms(long timeout) throws IllegalStateException {
return waitForConfirms();
}
@Override
public void waitForConfirmsOrDie() {
waitForConfirms();
}
@Override
public void waitForConfirmsOrDie(long timeout) {
waitForConfirms(timeout);
}
@Override
public void asyncRpc(Method method) {
throw new UnsupportedOperationException();
}
@Override
public Command rpc(Method method) {
throw new UnsupportedOperationException();
}
@Override
public long messageCount(String queue) {
return node.messageCount(lastGeneratedIfEmpty(queue));
}
@Override
public long consumerCount(String queue) {
return node.consumerCount(lastGeneratedIfEmpty(queue));
}
@Override
public CompletableFuture asyncCompletableRpc(Method method) {
throw new UnsupportedOperationException();
}
@Override
public void addShutdownListener(ShutdownListener listener) {
// do nothing
}
@Override
public void removeShutdownListener(ShutdownListener listener) {
throw new UnsupportedOperationException();
}
@Override
public ShutdownSignalException getCloseReason() {
return null;
}
@Override
public void notifyListeners() {
throw new UnsupportedOperationException();
}
@Override
public boolean isOpen() {
return opened.get();
}
private AMQP.BasicProperties nullToEmpty(AMQP.BasicProperties props) {
return props != null ? props : new AMQP.BasicProperties.Builder().build();
}
private Map nullToEmpty(Map arguments) {
return arguments != null ? arguments : Collections.emptyMap();
}
private String generateIfEmpty(String queue) {
final String definitiveQueueName;
if ("".equals(queue)) {
definitiveQueueName = queueNameGenerator.generate();
this.lastGeneratedQueueName = definitiveQueueName;
} else {
definitiveQueueName = queue;
}
return definitiveQueueName;
}
private String lastGeneratedIfEmpty(String queue) {
return "".equals(queue) ? Objects.requireNonNull(lastGeneratedQueueName, "No server-named queue previously created") : queue;
}
private long nextDeliveryTag() {
return deliveryTagSequence.incrementAndGet();
}
/**
* @return either a {@link Transaction} if {@link MockChannel#txSelect()} have been called, or {@link MockNode} otherwise
*/
private TransactionalOperations getTransactionOrNode() {
return Optional.ofNullable(transaction).orElse(node);
}
private void safelyInvokeConfirmListeners() {
confirmListeners.forEach(confirmListener -> {
try {
confirmListener.handleAck(nextPublishSeqNo, false);
} catch (IOException | RuntimeException e) {
LOGGER.warn("ConfirmListener threw an exception " + confirmListener, e);
}
});
}
public MetricsCollectorWrapper getMetricsCollector() {
return metricsCollectorWrapper;
}
}