com.github.larsq.spring.embeddedamqp.SimpleAmqpConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.github.larsq.spring.embeddedamqp.SimpleAmqpConnectionFactory.java

Source

/*
 * The MIT License
 *
 * Copyright 2014 lars.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.larsq.spring.embeddedamqp;

import com.github.larsq.support.SuppressedThrowable;
import com.google.common.collect.ImmutableMap;
import com.rabbitmq.client.*;
import com.rabbitmq.client.impl.AMQImpl;
import com.rabbitmq.client.impl.DefaultExceptionHandler;
import org.jooq.lambda.Unchecked;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AnonymousQueue;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author lars
 */
public class SimpleAmqpConnectionFactory extends ConnectionFactory {
    private static void LOG(Logger logger, String operation, String data) {
        logger.debug("op={}, data={}", operation, data);
    }

    private final static Logger LOG = LoggerFactory
            .getLogger(SimpleAmqpConnectionFactory.class.getPackage().getName());
    private final static Supplier<SuppressedThrowable<IOException>> IOException = () -> SuppressedThrowable
            .wrap(IOException.class);
    private final SimpleAmqpMessageContainer container;

    public SimpleAmqpConnectionFactory() {
        this(new SimpleAmqpMessageContainer());
    }

    public SimpleAmqpConnectionFactory(SimpleAmqpMessageContainer container) {
        this.container = container;
    }

    @Override
    public Connection newConnection(ExecutorService executor) throws IOException {
        return newConnection();
    }

    @Override
    public Connection newConnection() throws IOException {
        return new ConnectionImpl(new Address(getHost(), getPort()), this); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public Connection newConnection(ExecutorService executor, Address[] addrs) throws IOException {
        return new ConnectionImpl(addrs[0], this);
    }

    @Override
    public Connection newConnection(Address[] addrs) throws IOException {
        return new ConnectionImpl(addrs[0], this); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     * Support class to define event listeners.
     *
     * @param <T> the listener type
     */
    private static class EventListenerList<T> {
        private final List<T> listeners = new ArrayList<>();

        public EventListenerList() {
        }

        public void add(T listener) {
            if (listener != null) {
                listeners.add(listener);
            }
        }

        public boolean remove(T listener) {
            return listeners.remove(listener);
        }

        public void clear() {
            this.listeners.clear();
        }

        public void forEach(Consumer<T> action) {
            listeners.forEach(action);
        }
    }

    protected static class ChannelImpl implements Channel {

        private final SimpleAmqpConnectionFactory.ConnectionImpl connection;
        private final int channelNumber;
        private final EventListenerList<ReturnListener> returnListeners = new EventListenerList<>();
        private final EventListenerList<ConfirmListener> confirmListeners = new EventListenerList<>();
        private final EventListenerList<FlowListener> flowListeners = new EventListenerList<>();
        private final EventListenerList<ShutdownListener> shutdownListeners = new EventListenerList<>();
        private Integer closeCode;
        private String closeMessage;
        private boolean flowBlocked = false;
        private com.rabbitmq.client.Consumer defaultConsumer;

        ChannelImpl(int channelNumber, SimpleAmqpConnectionFactory.ConnectionImpl connection) {
            this.channelNumber = channelNumber;
            this.connection = connection;
        }

        /**
         * Used to simulated connection blockings
         *
         * @param flowBlocked of the flow is set as "blocked"
         */
        public void setFlowBlocked(boolean flowBlocked) {
            this.flowBlocked = flowBlocked;
            //is not expected that any exception are raised
            flowListeners.forEach(Unchecked.consumer(l -> l.handleFlow(!flowBlocked)));
        }

        @Override
        public int getChannelNumber() {
            return channelNumber;
        }

        @Override
        public Connection getConnection() {
            return connection;
        }

        @Override
        public void close() throws IOException {
            close(AMQP.REPLY_SUCCESS, "OK");
        }

        @Override
        public void close(int closeCode, String closeMessage) throws IOException {
            this.closeCode = closeCode;
            this.closeMessage = closeMessage;

            container().unsubscribeAll(this);
        }

        @Override
        public boolean flowBlocked() {
            return flowBlocked;
        }

        @Override
        public void abort() throws IOException {
            close();
        }

        @Override
        public void abort(int closeCode, String closeMessage) throws IOException {
            close(closeCode, closeMessage);
        }

        @Override
        public void addReturnListener(ReturnListener listener) {
            returnListeners.add(listener);
        }

        @Override
        public boolean removeReturnListener(ReturnListener listener) {
            return returnListeners.remove(listener);
        }

        @Override
        public void clearReturnListeners() {
            returnListeners.clear();
        }

        @Override
        public void addFlowListener(FlowListener listener) {
            flowListeners.add(listener);
        }

        @Override
        public boolean removeFlowListener(FlowListener listener) {
            return flowListeners.remove(listener);
        }

        @Override
        public void clearFlowListeners() {
            flowListeners.clear();
        }

        @Override
        public void addConfirmListener(ConfirmListener listener) {
            confirmListeners.add(listener);
        }

        @Override
        public boolean removeConfirmListener(ConfirmListener listener) {
            return confirmListeners.remove(listener);
        }

        @Override
        public void clearConfirmListeners() {
            confirmListeners.clear();
        }

        @Override
        public com.rabbitmq.client.Consumer getDefaultConsumer() {
            return defaultConsumer;
        }

        @Override
        public void setDefaultConsumer(com.rabbitmq.client.Consumer consumer) {
            this.defaultConsumer = consumer;
        }

        @Override
        /**
         * Not yet supported
         */
        public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException {
        }

        @Override
        public void basicQos(int i, boolean b) throws IOException {

        }

        @Override
        /**
         * Not yet supported
         */
        public void basicQos(int prefetchCount) throws IOException {

        }

        protected SimpleAmqpMessageContainer container() {
            return connection.factory.container;
        }

        @Override
        public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body)
                throws IOException {
            basicPublish(exchange, routingKey, false, false, props, body);
        }

        @Override
        public void basicPublish(String exchange, String routingKey, boolean mandatory, AMQP.BasicProperties props,
                byte[] body) throws IOException {
            basicPublish(exchange, routingKey, mandatory, false, props, body);
        }

        @Override
        public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate,
                AMQP.BasicProperties props, byte[] body) throws IOException {
            LOG(LOG, "basicPublish", String.format("{exchange=%s, key=%s}", exchange, routingKey));

            container().publish(this, exchange, routingKey, mandatory, immediate, props, body);
        }

        @Override
        public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException {
            return exchangeDeclare(exchange, type, true, true, false, null);
        }

        @Override
        public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable)
                throws IOException {
            return exchangeDeclare(exchange, type, durable, true, false, null);
        }

        @Override
        public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable,
                boolean autoDelete, Map<String, Object> arguments) throws IOException {
            return exchangeDeclare(exchange, type, durable, autoDelete, false, arguments);
        }

        @Override
        public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable,
                boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException {
            LOG(LOG, "exchangeDeclare", String.format("{exchange=%s, type=%s}", exchange, type));

            container().declareExchange(exchange, type);
            return new AMQImpl.Exchange.DeclareOk();
        }

        @Override
        public AMQP.Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException {

            ExchangeWrapper wrapper = container().exchange(name)
                    .orElseThrow(ExceptionSuppliers.Exception.exchangeNotFound(name));
            return new AMQImpl.Exchange.DeclareOk();
        }

        @Override
        public AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException {
            LOG(LOG, "exchangeDelete", String.format("{exchange=%s}", exchange));

            container().exchangeDelete(exchange);
            return new AMQImpl.Exchange.DeleteOk();
        }

        @Override
        public AMQP.Exchange.DeleteOk exchangeDelete(String exchange) throws IOException {
            return exchangeDelete(exchange, true);
        }

        @Override
        public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey)
                throws IOException {
            return exchangeBind(destination, source, routingKey, null);
        }

        @Override
        public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey,
                Map<String, Object> arguments) throws IOException {
            container().exchangeBind(destination, source, routingKey, arguments);
            return new AMQImpl.Exchange.BindOk();
        }

        @Override
        public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey)
                throws IOException {
            return exchangeUnbind(destination, source, routingKey, null);
        }

        @Override
        public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey,
                Map<String, Object> arguments) throws IOException {
            container().exchangeUnbind(destination, source, routingKey, arguments);

            return new AMQImpl.Exchange.UnbindOk();
        }

        @Override
        public AMQP.Queue.DeclareOk queueDeclare() throws IOException {
            AnonymousQueue anonymousQueue = new AnonymousQueue();
            return queueDeclare(anonymousQueue.getName(), false, false, true, null);
        }

        @Override
        public AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive,
                boolean autoDelete, Map<String, Object> arguments) throws IOException {
            QueueInfo info = container().queueDeclare(
                    new org.springframework.amqp.core.Queue(queue, durable, exclusive, autoDelete, arguments));

            return new AMQImpl.Queue.DeclareOk(info.getQueue(), info.getMessageCount(), info.getConsumerCount());
        }

        @Override
        public AMQP.Queue.DeclareOk queueDeclarePassive(String queue) throws IOException {
            Optional<QueueInfo> optionalInfo = container().requestQueue(queue);

            QueueInfo info = optionalInfo.orElseThrow(ExceptionSuppliers.Exception.queueNotFound(queue));

            return new AMQImpl.Queue.DeclareOk(info.getQueue(), info.getMessageCount(), info.getConsumerCount());
        }

        @Override
        public AMQP.Queue.DeleteOk queueDelete(String queue) throws IOException {
            return queueDelete(queue, true, true);
        }

        @Override
        public AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException {
            QueueInfo info = container().queueDelete(queue);

            return new AMQImpl.Queue.DeleteOk(info.getMessageCount());
        }

        @Override
        public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException {
            return queueBind(queue, exchange, routingKey, null);
        }

        @Override
        public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey,
                Map<String, Object> arguments) throws IOException {
            container().queueBind(queue, exchange, routingKey, arguments);
            return new AMQImpl.Queue.BindOk();
        }

        @Override
        public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey)
                throws IOException {
            return queueUnbind(queue, exchange, routingKey, null);
        }

        @Override
        public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey,
                Map<String, Object> arguments) throws IOException {
            container().queueUnbind(routingKey, queue, routingKey, arguments);
            return new AMQImpl.Queue.UnbindOk();
        }

        @Override
        public AMQP.Queue.PurgeOk queuePurge(String queue) throws IOException {
            Optional<QueueInfo> info = container().requestQueue(queue);

            info.ifPresent(q -> q.purge());

            return new AMQImpl.Queue.PurgeOk(info.map(q -> q.getMessageCount()).orElse(0));
        }

        @Override
        public GetResponse basicGet(String queue, boolean autoAck) throws IOException {
            SuppressedThrowable<IOException> suppressed = IOException.get();
            LOG.debug("get message: {}", queue);

            Optional<QueueInfo> info = container().requestQueue(queue);

            Optional<GetResponse> response = info.flatMap(q -> q.receive());

            if (!response.isPresent()) {
                return null;
            }

            if (autoAck) {
                long deliveryTag = response.get().getEnvelope().getDeliveryTag();
                confirmListeners.forEach(Unchecked.consumer(l -> l.handleAck(deliveryTag, false), suppressed));

                suppressed.check();
            }

            LOG.debug("recevied message: q={} msg={}", queue, response.get());

            return response.get();
        }

        @Override
        public void basicAck(long deliveryTag, boolean multiple) throws IOException {
            SuppressedThrowable<IOException> suppressed = IOException.get();

            confirmListeners.forEach(Unchecked.consumer(l -> l.handleAck(deliveryTag, false), suppressed));

            suppressed.check();
        }

        @Override
        public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException {
            LOG(LOG, "basicNack", String.format("%s-%s-%s", deliveryTag, multiple, requeue));

            SuppressedThrowable<IOException> suppressed = IOException.get();

            confirmListeners.forEach(Unchecked.consumer(l -> l.handleNack(deliveryTag, multiple), suppressed));

            suppressed.check();
        }

        @Override
        /**
         * Not supported
         */
        public void basicReject(long deliveryTag, boolean requeue) throws IOException {

        }

        @Override
        public String basicConsume(String queue, com.rabbitmq.client.Consumer callback) throws IOException {

            return basicConsume(queue, true, null, false, false, null, callback);
        }

        @Override
        public String basicConsume(String queue, boolean autoAck, com.rabbitmq.client.Consumer callback)
                throws IOException {
            return basicConsume(queue, autoAck, null, false, false, null, callback);
        }

        @Override
        public String basicConsume(String queue, boolean autoAck, Map<String, Object> map,
                com.rabbitmq.client.Consumer consumer) throws IOException {
            return basicConsume(queue, autoAck, null, false, false, map, consumer);
        }

        @Override
        public String basicConsume(String queue, boolean autoAck, String consumerTag,
                com.rabbitmq.client.Consumer callback) throws IOException {
            return basicConsume(queue, autoAck, consumerTag, false, false, null, callback);
        }

        @Override
        public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal,
                boolean exclusive, Map<String, Object> arguments, com.rabbitmq.client.Consumer callback)
                throws IOException {
            LOG(LOG, "basicConsume", queue);

            String tag = container().subscribe(this, consumerTag, queue, callback);

            LOG.debug("register consumer: {} {}", tag, queue);

            return tag;
        }

        @Override
        public void basicCancel(String consumerTag) throws IOException {
            container().unsubscribe(consumerTag);
        }

        @Override
        /**
         * Not supported
         */
        public AMQP.Basic.RecoverOk basicRecover() throws IOException {
            return new AMQImpl.Basic.RecoverOk();
        }

        @Override
        /**
         * Not supported
         */
        public AMQP.Basic.RecoverOk basicRecover(boolean requeue) throws IOException {
            return new AMQImpl.Basic.RecoverOk();
        }

        @Override
        /**
         * Not supported
         */
        public void basicRecoverAsync(boolean requeue) throws IOException {
        }

        @Override
        /**
         * Not supported
         */
        public AMQP.Tx.SelectOk txSelect() throws IOException {
            return new AMQImpl.Tx.SelectOk();
        }

        @Override
        /**
         * Not supported
         */

        public AMQP.Tx.CommitOk txCommit() throws IOException {
            return new AMQImpl.Tx.CommitOk();
        }

        @Override
        /**
         * Not supported
         */

        public AMQP.Tx.RollbackOk txRollback() throws IOException {
            return new AMQImpl.Tx.RollbackOk();
        }

        @Override
        public AMQP.Confirm.SelectOk confirmSelect() throws IOException {
            return new AMQImpl.Confirm.SelectOk();
        }

        @Override
        public long getNextPublishSeqNo() {
            return container().nextSequenceNumber(this);
        }

        @Override
        /**
         * Not supported
         */

        public boolean waitForConfirms() throws InterruptedException {
            return true;
        }

        @Override
        /**
         * Not supported
         */

        public boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException {
            return true;
        }

        @Override
        /**
         * Not supported
         */

        public void waitForConfirmsOrDie() throws IOException, InterruptedException {

        }

        @Override
        /**
         * Not supported
         */

        public void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException {
        }

        @Override
        /**
         * Not supported
         */
        public void asyncRpc(Method method) throws IOException {
            throw new UnsupportedOperationException("not yet supported");
        }

        @Override
        /**
         * Not supported
         */
        public Command rpc(Method method) throws IOException {
            throw new UnsupportedOperationException("not yet supported");
        }

        @Override
        public void addShutdownListener(ShutdownListener listener) {
            shutdownListeners.add(listener);
        }

        @Override
        public void removeShutdownListener(ShutdownListener listener) {
            shutdownListeners.remove(listener);
        }

        @Override
        public ShutdownSignalException getCloseReason() {
            return new ShutdownSignalException(false, true, null, closeMessage);
        }

        @Override
        public void notifyListeners() {
        }

        @Override
        public boolean isOpen() {
            return closeCode == null;
        }

        @Override
        public String toString() {
            return String.format("%s{channelNumber=%s}", getClass().getSimpleName(), getChannelNumber());
        }

    }

    protected static class ConnectionImpl implements Connection {
        private final static int NOT_YET_CLOSED = -1;

        protected final Address connectedTo;
        protected final SimpleAmqpConnectionFactory factory;
        private final Map<String, List<com.rabbitmq.client.Consumer>> consumers = new HashMap<>();
        private final EventListenerList<BlockedListener> blockedListeners = new EventListenerList<>();
        private final EventListenerList<ShutdownListener> shutdownListeners = new EventListenerList<>();
        private int closeCode = NOT_YET_CLOSED;
        private String closeMessage;
        private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();

        protected ConnectionImpl(Address connectedTo, SimpleAmqpConnectionFactory factory) {
            this.connectedTo = connectedTo;
            this.factory = factory;
        }

        @Override
        public InetAddress getAddress() {
            try {
                return InetAddress.getByName(connectedTo.getHost());
            } catch (UnknownHostException ex) {
                throw new RuntimeException(ex);
            }
        }

        @Override
        public int getPort() {
            return connectedTo.getPort();
        }

        @Override
        public int getChannelMax() {
            return factory.getRequestedChannelMax();
        }

        @Override
        public int getFrameMax() {
            return factory.getRequestedFrameMax();
        }

        @Override
        public int getHeartbeat() {
            return factory.getRequestedHeartbeat();
        }

        @Override
        public Map<String, Object> getClientProperties() {
            return factory.getClientProperties();
        }

        @Override
        public Map<String, Object> getServerProperties() {
            //get empty map
            return ImmutableMap.of();
        }

        @Override
        public Channel createChannel() throws IOException {
            return createChannel(0);
        }

        @Override
        public Channel createChannel(int channelNumber) throws IOException {
            return new ChannelImpl(channelNumber, this);
        }

        @Override
        public void close() throws IOException {
            close(AMQP.REPLY_SUCCESS, "OK");
        }

        @Override
        public void close(int closeCode, String closeMessage) throws IOException {
            this.closeCode = closeCode;
            this.closeMessage = closeMessage;
        }

        @Override
        public void close(int timeout) throws IOException {
            close(AMQP.REPLY_SUCCESS, "OK");
        }

        @Override
        public void close(int closeCode, String closeMessage, int timeout) throws IOException {
            close(closeCode, closeMessage);
        }

        @Override
        public void abort() {
            abort(AMQP.REPLY_SUCCESS, "OK");
        }

        @Override
        public void abort(int closeCode, String closeMessage) {
            this.closeCode = closeCode;
            this.closeMessage = closeMessage;
        }

        @Override
        public void abort(int timeout) {
            abort();
        }

        @Override
        public void abort(int closeCode, String closeMessage, int timeout) {
            abort(closeCode, closeMessage);
        }

        @Override
        public void addBlockedListener(BlockedListener listener) {
            blockedListeners.add(listener);
        }

        @Override
        public boolean removeBlockedListener(BlockedListener listener) {
            return blockedListeners.remove(listener);
        }

        @Override
        public void clearBlockedListeners() {
            blockedListeners.clear();
        }

        @Override
        public ExceptionHandler getExceptionHandler() {
            return exceptionHandler;
        }

        @Override
        public boolean isOpen() {
            return closeCode == NOT_YET_CLOSED;
        }

        @Override
        public void addShutdownListener(ShutdownListener listener) {
            shutdownListeners.add(listener);
        }

        @Override
        public void removeShutdownListener(ShutdownListener listener) {
            shutdownListeners.remove(listener);
        }

        @Override
        public ShutdownSignalException getCloseReason() {
            return new ShutdownSignalException(false, true, null, null);
        }

        @Override
        public void notifyListeners() {
        }

    }

}