org.wso2.andes.amqp.QpidAndesBridge.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.andes.amqp.QpidAndesBridge.java

Source

/*
 * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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 org.wso2.andes.amqp;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.andes.AMQException;
import org.wso2.andes.AMQInternalException;
import org.wso2.andes.exchange.ExchangeDefaults;
import org.wso2.andes.framing.AMQShortString;
import org.wso2.andes.framing.abstraction.ContentChunk;
import org.wso2.andes.kernel.Andes;
import org.wso2.andes.kernel.AndesAckData;
import org.wso2.andes.kernel.AndesChannel;
import org.wso2.andes.kernel.AndesContext;
import org.wso2.andes.kernel.AndesException;
import org.wso2.andes.kernel.AndesMessage;
import org.wso2.andes.kernel.AndesMessageMetadata;
import org.wso2.andes.kernel.AndesMessagePart;
import org.wso2.andes.kernel.AndesUtils;
import org.wso2.andes.kernel.DisablePubAckImpl;
import org.wso2.andes.kernel.MessagingEngine;
import org.wso2.andes.kernel.ProtocolType;
import org.wso2.andes.kernel.QueueBrowserMessageFlusher;
import org.wso2.andes.kernel.SubscriptionAlreadyExistsException;
import org.wso2.andes.kernel.disruptor.DisruptorEventCallback;
import org.wso2.andes.kernel.disruptor.inbound.InboundBindingEvent;
import org.wso2.andes.kernel.disruptor.inbound.InboundExchangeEvent;
import org.wso2.andes.kernel.disruptor.inbound.InboundQueueEvent;
import org.wso2.andes.kernel.disruptor.inbound.InboundSubscriptionEvent;
import org.wso2.andes.kernel.disruptor.inbound.InboundTransactionEvent;
import org.wso2.andes.kernel.disruptor.inbound.PubAckHandler;
import org.wso2.andes.kernel.subscription.OutboundSubscription;
import org.wso2.andes.kernel.subscription.StorageQueue;
import org.wso2.andes.kernel.subscription.SubscriberConnection;
import org.wso2.andes.protocol.AMQConstant;
import org.wso2.andes.server.AMQChannel;
import org.wso2.andes.server.ClusterResourceHolder;
import org.wso2.andes.server.binding.Binding;
import org.wso2.andes.server.exchange.Exchange;
import org.wso2.andes.server.message.AMQMessage;
import org.wso2.andes.server.queue.AMQQueue;
import org.wso2.andes.server.queue.IncomingMessage;
import org.wso2.andes.server.store.StorableMessageMetaData;
import org.wso2.andes.server.subscription.Subscription;
import org.wso2.andes.server.subscription.SubscriptionImpl;
import org.wso2.andes.tools.utils.MessageTracer;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Class is not instantiable from outside. This is used as a bridge between Qpid and
 * Andes. And the class doesn't store any state information hence the methods are made static
 */
public class QpidAndesBridge {

    private static Log log = LogFactory.getLog(QpidAndesBridge.class);

    /**
     * Following used by a performance counter
     */
    private static AtomicLong receivedMessageCounter = new AtomicLong();
    private static long last10kMessageReceivedTimestamp = System.currentTimeMillis();

    /**
     * Ignore pub acknowledgements in AMQP. AMQP doesn't have pub acks
     */
    private static PubAckHandler pubAckHandler;

    /**
     * Recover messages of AMQP channel
     *
     * @param channel           AMQP channel to recover messages
     * @param recoverOKCallback Callback to send recover-ok
     * @throws AMQException exception on recovering messages of channel
     */
    public static void recoverMessagesOfChannel(AMQChannel channel, DisruptorEventCallback recoverOKCallback)
            throws AMQException {
        try {
            UUID channelID = channel.getId();
            Andes.getInstance().recoverMessagesOfChannel(channelID, recoverOKCallback);
        } catch (AndesException e) {
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while handling recovered message", e);
        }
    }

    /**
     * Class is not instantiable from outside. This is a used as a bridge between Qpid and
     * Andes. And the class doesn't store any state information
     */
    private QpidAndesBridge() {
    }

    static {
        pubAckHandler = new DisablePubAckImpl();
    }

    /**
     * message metadata received from AMQP transport.
     * This should happen after all content chunks are received
     *
     * @param incomingMessage  message coming in
     * @param andesChannel     AndesChannel
     * @param transactionEvent not null if this is a message in a transaction, null otherwise
     * @throws AMQException when routing key is null
     */
    public static void messageReceived(IncomingMessage incomingMessage, AndesChannel andesChannel,
            InboundTransactionEvent transactionEvent) throws AMQException {
        try {
            AndesMessage andesMessage = convertToAndesMessage(incomingMessage);

            // Handover message to Andes
            if (null == transactionEvent) { // not a transaction
                Andes.getInstance().messageReceived(andesMessage, andesChannel, pubAckHandler);
            } else { // transaction event
                transactionEvent.preProcessEnqueue(andesMessage);
            }

        } catch (AndesException e) {
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while storing incoming message metadata", e);
        }

        //Following code is only a performance counter
        if (log.isDebugEnabled()) {
            Long localCount = receivedMessageCounter.incrementAndGet();
            if (localCount % 10000 == 0) {
                long timeTook = System.currentTimeMillis() - last10kMessageReceivedTimestamp;
                log.debug("Received " + localCount + ", throughput = " + (10000 * 1000 / timeTook) + " msg/sec, "
                        + timeTook);
                last10kMessageReceivedTimestamp = System.currentTimeMillis();
            }
        }

    }

    /**
     * Message metadata received from AMQP transport.
     *
     * @param andesMessage Message coming in
     * @param andesChannel AndesChannel
     */
    public static void messageReceived(AndesMessage andesMessage, AndesChannel andesChannel) {
        Andes.getInstance().messageReceived(andesMessage, andesChannel, pubAckHandler);

        //Following code is only a performance counter
        if (log.isDebugEnabled()) {
            Long localCount = receivedMessageCounter.incrementAndGet();
            if (localCount % 10000 == 0) {
                long timetook = System.currentTimeMillis() - last10kMessageReceivedTimestamp;
                log.debug("Received " + localCount + ", throughput = " + (10000 * 1000 / timetook) + " msg/sec, "
                        + timetook);
                last10kMessageReceivedTimestamp = System.currentTimeMillis();
            }
        }
    }

    /**
     * Convert Qpid Incoming message to an Andes message
     * @param incomingMessage Incoming message from transport
     * @return AndesMessage
     * @throws AndesException when routing key is null
     */
    public static AndesMessage convertToAndesMessage(IncomingMessage incomingMessage) throws AndesException {
        long receivedTime = System.currentTimeMillis();
        AMQMessage message = new AMQMessage(incomingMessage.getStoredMessage());
        //set the time to live value for AMQ Message
        message.setExpiration(incomingMessage.getExpiration());
        // message arrival time set to mb node's system time without using
        // message published time by publisher.
        message.getMessageMetaData().setArrivalTime(receivedTime);

        AndesMessageMetadata metadata = AMQPUtils.convertAMQMessageToAndesMetadata(message);
        String queue = message.getRoutingKey();

        if (queue == null) {
            throw new AndesException("Queue cannot be null, for " + incomingMessage.getMessageNumber());
        }

        AndesMessage andesMessage = new AndesMessage(metadata);

        // Update Andes message with all the chunk details
        int contentChunks = incomingMessage.getBodyCount();
        int offset = 0;
        for (int i = 0; i < contentChunks; i++) {
            ContentChunk chunk = incomingMessage.getContentChunk(i);
            AndesMessagePart messagePart = messageContentChunkReceived(metadata.getMessageID(), offset,
                    chunk.getData().buf());
            offset = offset + chunk.getSize();
            andesMessage.addMessagePart(messagePart);
        }
        return andesMessage;
    }

    /**
     * read metadata of a message from store
     *
     * @param messageID id of the message
     * @return StorableMessageMetaData
     * @throws AMQException
     */
    public static StorableMessageMetaData getMessageMetaData(long messageID) throws AMQException {
        StorableMessageMetaData metaData;
        try {
            metaData = AMQPUtils
                    .convertAndesMetadataToAMQMetadata(MessagingEngine.getInstance().getMessageMetaData(messageID));
        } catch (AndesException e) {
            log.error("Error in getting meta data for messageID", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR,
                    "Error in getting meta data for messageID " + messageID, e);
        }
        return metaData;
    }

    /**
     * message content chunk received to the server
     *
     * @param messageID       id of message to which content belongs
     * @param offsetInMessage chunk offset
     * @param src             Bytebuffer with content bytes
     */
    public static AndesMessagePart messageContentChunkReceived(long messageID, int offsetInMessage,
            ByteBuffer src) {

        if (log.isDebugEnabled()) {
            log.debug("Content Part Received id " + messageID + ", offset " + offsetInMessage);
        }
        AndesMessagePart part = new AndesMessagePart();
        src = src.slice();
        final byte[] chunkData = new byte[src.limit()];
        src.duplicate().get(chunkData);

        part.setData(chunkData);
        part.setMessageID(messageID);
        part.setOffSet(offsetInMessage);

        return part;
    }

    /**
     * Read a message content chunk form store
     *
     * @param messageId       id of the message
     * @param offsetInMessage offset to be read
     * @param dst             buffer to be filled by content bytes
     * @return written content length
     * @throws AMQException
     */
    public static int getMessageContentChunk(long messageId, int offsetInMessage, ByteBuffer dst)
            throws AMQException {
        int contentLenWritten;
        try {
            contentLenWritten = AMQPUtils.fillBufferFromContent(messageId, offsetInMessage, dst);
        } catch (AndesException e) {
            log.error("Error in getting message content", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR,
                    "Error in getting message content chunk messageId " + messageId + " offset=" + offsetInMessage,
                    e);
        }
        return contentLenWritten;
    }

    public static void ackReceived(UUID channelId, long messageId) throws AMQException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("ack received for message id= " + messageId + " channelId= " + channelId);
            }
            //Tracing Message
            AndesAckData andesAckData = new AndesAckData(channelId, messageId);
            MessageTracer.traceAck(andesAckData, MessageTracer.ACK_RECEIVED_FROM_PROTOCOL);
            Andes.getInstance().ackReceived(andesAckData);
        } catch (AndesException e) {
            log.error("Exception occurred while handling ack", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error in getting handling ack for " + messageId, e);
        }
    }

    /**
     * Reject message is received
     *
     * @param message message subjected to rejection
     * @param channel channel by which reject message is received
     * @param reQueue true if message should be re-queued to subscriber
     * @throws AMQException on a message re-schedule issue
     */
    public static void rejectMessage(AMQMessage message, AMQChannel channel, boolean reQueue) throws AMQException {
        try {
            channel.setLastRejectedMessageId(message.getMessageNumber());
            boolean isMessageBeyondLastRollback = channel.isMessageBeyondLastRollback(message.getMessageNumber());

            log.info("Reject received id = " + message.getMessageId() + " channel id= " + channel.getChannelId()
                    + "regueue = " + reQueue);

            Andes.getInstance().messageRejected(message.getMessageId(), channel.getId(), reQueue,
                    isMessageBeyondLastRollback);
            if (log.isDebugEnabled()) {
                log.debug("AMQP BRIDGE: rejected message id= " + message.getMessageId() + " channel = "
                        + channel.getId() + " reQueue= " + reQueue);
            }
        } catch (AndesException e) {
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while handling rejected message", e);
        }
    }

    /**
     * create a AMQP subscription in andes kernel
     *
     * @param subscription qpid subscription
     * @param queue        qpid queue
     * @param consumeCallback A thread to send Consume_ok frame to consumer.
     * @throws AMQException
     */
    public static void createAMQPSubscription(Subscription subscription, AMQQueue queue, Runnable consumeCallback)
            throws AMQException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("AMQP BRIDGE: create AMQP Subscription subID " + subscription.getSubscriptionID()
                        + " from queue " + queue.getName());
            }

            if (subscription instanceof SubscriptionImpl.BrowserSubscription) {
                QueueBrowserMessageFlusher deliveryWorker = new QueueBrowserMessageFlusher(subscription, queue);
                deliveryWorker.readAndSendFromMessageStore();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Adding Subscription " + subscription.getSubscriptionID() + " to queue "
                            + queue.getName());
                }
                addLocalSubscriptionsForAllBindingsOfQueue(queue, subscription, consumeCallback);
            }
        } catch (SubscriptionAlreadyExistsException e) {
            log.error("Error occurred while adding an already existing subscription", e);
            throw new AMQQueue.ExistingExclusiveSubscription();
        } catch (AndesException e) {
            log.error("Error while adding the subscription", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while registering subscription", e);
        }
    }

    /**
     * Close AMQP subscription in Andes kernel
     *
     * @param queue        qpid queue
     * @param subscription qpid subscription
     * @throws AndesException
     */
    public static void closeAMQPSubscription(AMQQueue queue, Subscription subscription) {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: close AMQP Subscription subID " + subscription.getSubscriptionID()
                    + " from queue " + queue.getName());
        }

        // Browser subscriptions are not registered and hence not needed to be closed.
        if (!(subscription instanceof SubscriptionImpl.BrowserSubscription)) {
            closeLocalSubscriptionsForAllBindingsOfQueue(queue, subscription);
        }
    }

    /**
     * Create an exchange in andes kernel
     *
     * @param exchange qpid exchange
     * @throws AMQException
     */
    public static void createExchange(Exchange exchange) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: create Exchange" + exchange.getName());
        }
        try {
            InboundExchangeEvent exchangeEvent = AMQPUtils.createAndesExchange(exchange);
            Andes.getInstance().createExchange(exchangeEvent);
        } catch (AndesException e) {
            log.error("error while creating exchange", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "error while creating exchange", e);
        }
    }

    /**
     * Delete exchange from andes kernel
     *
     * @param exchange qpid exchange
     * @throws AMQException
     */
    public static void deleteExchange(Exchange exchange) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: delete Exchange " + exchange.getName());
        }
        try {
            Andes.getInstance().deleteExchange(AMQPUtils.createAndesExchange(exchange));
        } catch (AndesException e) {
            log.error("error while deleting exchange", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "error while deleting exchange", e);
        }
    }

    /**
     * Create queue in andes kernel
     *
     * @param queue qpid queue
     * @throws AMQException
     */
    public static void createQueue(AMQQueue queue) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: create queue: " + queue.getName());
        }
        try {
            List<StorageQueue> queues = AndesContext.getInstance().getStorageQueueRegistry().getAllStorageQueues();
            for (StorageQueue storageQueue : queues) {
                if (storageQueue.getName().equals(queue.getName())) {
                    throw new AMQException("Cannot create already existing queue: " + queue.getName());
                }
            }
            Andes.getInstance().createQueue(AMQPUtils.createInboundQueueEvent(queue));
        } catch (AndesException e) {
            log.error("error while creating queue", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "error while creating queue", e);
        }
    }

    /**
     * Delete queue from andes kernel
     *
     * @param queue qpid queue
     * @throws AMQException
     */
    public static void deleteQueue(AMQQueue queue) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE:  delete queue : " + queue.getName());
        }
        try {
            /*
              Trigger delete for non durable queues only.
              Because queue name = storage queue name only for non durable queues. We cannot generate
              storage queue name here as we do not have binding information (bindings already removed).
             */
            if (queue.isDurable() && !queue.isAutoDelete()) { //storage queue name = queue name in this case
                InboundQueueEvent queueEvent = AMQPUtils.createInboundQueueEvent(queue);
                Andes.getInstance().deleteQueue(queueEvent);
            }
        } catch (AndesException e) {
            log.error("error while removing queue", e);
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "error while removing queue", e);
        }
    }

    /**
     * Create binding in andes kernel
     *
     * @param exchange   qpid exchange
     * @param routingKey routing key
     * @param queue      qpid queue
     * @throws AMQInternalException
     */
    public static void createBinding(Exchange exchange, AMQShortString routingKey, AMQQueue queue)
            throws AMQInternalException {

        // We ignore default exchange events. Andes doesn't honor creation of AMQ default exchange bindings
        if (exchange.getNameShortString().equals(ExchangeDefaults.DEFAULT_EXCHANGE_NAME)) {
            if (log.isDebugEnabled()) {
                log.debug("Ignored binding for default exchange " + exchange.getNameShortString());
            }
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: addBinding exchange=" + exchange.getName() + " routingKey=" + routingKey
                    + " queue=" + queue.getName());
        }
        try {
            /**
             * durable topic case is handled inside qpid itself.
             * So we do not check for it here
             */
            InboundBindingEvent binding = AMQPUtils.createAndesBindingEvent(exchange, queue, routingKey);
            Andes.getInstance().addBinding(binding);
        } catch (AndesException e) {
            log.error("error while creating binding", e);
            throw new AMQInternalException("error while creating binding", e);
        }
    }

    /**
     * remove binding from andes kernel
     *
     * @param binding           qpid binding
     * @throws AndesException
     */
    public static void removeBinding(Binding binding) throws AndesException {

        Exchange exchange = binding.getExchange();

        // We ignore default exchange events. Andes doesn't honor creation of AMQ default exchange bindings
        if (exchange.getNameShortString().equals(ExchangeDefaults.DEFAULT_EXCHANGE_NAME)) {
            if (log.isDebugEnabled()) {
                log.debug("Ignored binding for default exchange " + exchange.getNameShortString());
            }
            return;
        } else if (!binding.getQueue().isDurable()
                && exchange.getNameShortString().equals(ExchangeDefaults.TOPIC_EXCHANGE_NAME)) {

            if (log.isDebugEnabled()) {
                log.debug("Ignored binding for non durable topic " + binding.getBindingKey());
            }

            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: removeBinding binding key: " + binding.getBindingKey() + " exchange: "
                    + binding.getExchange().getName() + " queue: " + binding.getQueue().getName());
        }
        InboundBindingEvent inboundBindingEvent = AMQPUtils.createAndesBindingEvent(binding.getExchange(),
                binding.getQueue(), new AMQShortString(binding.getBindingKey()));
        Andes.getInstance().removeBinding(inboundBindingEvent);
    }

    /**
     * create local subscriptions and add for every unique binding of the queue
     *
     * @param queue        AMQ queue
     * @param subscription subscription
     * @param sendConsumeOk A thread to send Consume_ok frame to consumer.
     * @throws AndesException
     */
    private static void addLocalSubscriptionsForAllBindingsOfQueue(AMQQueue queue, Subscription subscription,
            Runnable sendConsumeOk) throws AndesException {

        String localNodeID = ClusterResourceHolder.getInstance().getClusterManager().getMyNodeID();
        List<Binding> bindingList = queue.getBindings();
        if (bindingList != null && !bindingList.isEmpty()) {
            Set<String> uniqueStorageQueues = new HashSet<>();
            List<InboundSubscriptionEvent> alreadyAddedSubscriptions = new ArrayList<>();

            // Iterate unique bindings of the queue and add subscription entries.
            try {
                for (Binding b : bindingList) {
                    // We ignore default exchange events. Andes doesn't honor creation of AMQ default exchange bindings
                    if (b.getExchange().getNameShortString()
                            .equals(ExchangeDefaults.DEFAULT_EXCHANGE_NAME.toString())) {
                        continue;
                    }

                    ProtocolType protocol = ProtocolType.AMQP;
                    // We are using storage queue name as the subscription identifier
                    String subscriptionIdentifier = queue.getName();
                    String boundExchangeName = b.getExchange().getName();
                    String bindingKey = b.getBindingKey();
                    String queueName = queue.getName();
                    boolean isDurable = b.getQueue().isDurable();

                    String storageQueueToBind = AndesUtils.getStorageQueueForDestination(bindingKey,
                            boundExchangeName, queueName, isDurable);

                    if (uniqueStorageQueues.add(storageQueueToBind)) {

                        OutboundSubscription outboundSubscription = new AMQPLocalSubscription(subscription);
                        String ipAddressOfSubscriber = ((SubscriptionImpl.AckSubscription) subscription)
                                .getChannel().getSessionName();
                        SubscriberConnection subscriberConnection = new SubscriberConnection(ipAddressOfSubscriber,
                                localNodeID, subscription.getIdOfUnderlyingChannel(), outboundSubscription);

                        InboundSubscriptionEvent subscriptionEvent = new InboundSubscriptionEvent(protocol,
                                subscriptionIdentifier, storageQueueToBind, bindingKey, subscriberConnection,
                                sendConsumeOk);

                        Andes.getInstance().openLocalSubscription(subscriptionEvent);
                        alreadyAddedSubscriptions.add(subscriptionEvent);
                    }
                }
            } catch (AndesException e) {
                log.warn("Reverting already created subscription entries for subscription " + subscription, e);
                for (InboundSubscriptionEvent alreadyAddedSub : alreadyAddedSubscriptions) {
                    Andes.getInstance().closeLocalSubscription(alreadyAddedSub);
                }
                throw e;
            }
        }
    }

    /**
     * remove local subscriptions and add for every unique binding of the queue
     *
     * @param queue        AMQ queue
     * @param subscription subscription to remove
     * @throws AndesException
     */
    private static void closeLocalSubscriptionsForAllBindingsOfQueue(AMQQueue queue, Subscription subscription) {

        String localNodeID = ClusterResourceHolder.getInstance().getClusterManager().getMyNodeID();

        List<Binding> bindingList = queue.getBindings();

        if (bindingList != null && !bindingList.isEmpty()) {

            Set<String> uniqueStorageQueues = new HashSet<>();

            // Iterate unique bindings of the queue and remove subscription entries.
            for (Binding b : bindingList) {
                // We ignore default exchange events. Andes doesn't honor creation of AMQ default exchange bindings
                if (b.getExchange().getNameShortString()
                        .equals(ExchangeDefaults.DEFAULT_EXCHANGE_NAME.toString())) {
                    continue;
                }

                ProtocolType protocol = ProtocolType.AMQP;
                String subscriptionIdentifier = "";
                String boundExchangeName = b.getExchange().getName();
                String bindingKey = b.getBindingKey();
                String queueName = queue.getName();
                boolean isDurable = b.getQueue().isDurable();

                String storageQueueToBind = AndesUtils.getStorageQueueForDestination(bindingKey, boundExchangeName,
                        queueName, isDurable);

                if (uniqueStorageQueues.add(storageQueueToBind)) {

                    OutboundSubscription outboundSubscription = new AMQPLocalSubscription(subscription);
                    String IPAddressOfSubscriber = ((SubscriptionImpl.AckSubscription) subscription).getChannel()
                            .getSessionName();
                    SubscriberConnection subscriberConnection = new SubscriberConnection(IPAddressOfSubscriber,
                            localNodeID, subscription.getIdOfUnderlyingChannel(), outboundSubscription);

                    InboundSubscriptionEvent subscriptionEvent = new InboundSubscriptionEvent(protocol,
                            subscriptionIdentifier, storageQueueToBind, bindingKey, subscriberConnection);

                    try {
                        Andes.getInstance().closeLocalSubscription(subscriptionEvent);
                    } catch (AndesException e) {
                        log.warn("Error occurred while closing the subscription", e);
                    }
                }

            }
        }
    }

    /**
     * Notifies andes that a channel suspend/resume request has been received.
     *
     * @param channelId           the channel id from which the request was received
     * @param active              whether the channel should be suspended/resumed. If set to true, channel will be
     *                            resumed and vis versa
     * @param channelFlowCallback the call back to be registered to send the response
     */
    public static void notifyChannelFlow(UUID channelId, boolean active,
            DisruptorEventCallback channelFlowCallback) {
        Andes.getInstance().notifySubscriptionFlow(channelId, active, channelFlowCallback);
    }
}