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

Java tutorial

Introduction

Here is the source code for org.wso2.andes.amqp.QpidAMQPBridge.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.framing.AMQShortString;
import org.wso2.andes.framing.FieldTable;
import org.wso2.andes.framing.abstraction.ContentChunk;
import org.wso2.andes.kernel.*;
import org.wso2.andes.protocol.AMQConstant;
import org.wso2.andes.server.AMQChannel;
import org.wso2.andes.server.binding.Binding;
import org.wso2.andes.server.cassandra.QueueBrowserDeliveryWorker;
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.stats.PerformanceCounter;
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.server.virtualhost.VirtualHost;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

public class QpidAMQPBridge {

    private static Log log = LogFactory.getLog(QpidAMQPBridge.class);
    private static QpidAMQPBridge qpidAMQPBridge = null;
    /**
     * Following used by a performance counter
     */
    private static AtomicLong receivedMessageCounter = new AtomicLong();
    private static long last10kMessageReceivedTimestamp = System.currentTimeMillis();

    /**
     * get QpidAMQPBridge instance
     *
     * @return QpidAMQPBridge instance
     */
    public static synchronized QpidAMQPBridge getInstance() {
        if (qpidAMQPBridge == null) {
            qpidAMQPBridge = new QpidAMQPBridge();
        }
        return qpidAMQPBridge;
    }

    private QpidAMQPBridge() {

    }

    /**
     * message metadata received from AMQP transport.
     * This should happen after all content chunks are received
     *
     * @param incomingMessage message coming in
     * @param channelID       id of the channel
     * @throws AMQException
     */
    public void messageReceived(IncomingMessage incomingMessage, UUID channelID) throws AMQException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("Message id " + incomingMessage.getMessageNumber() + " received");
            }
            AMQMessage message = new AMQMessage(incomingMessage.getStoredMessage());
            message.getMessageMetaData().setArrivalTime(incomingMessage.getArrivalTime());

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

            if (queue == null) {
                log.error("Queue cannot be null, for " + incomingMessage.getMessageNumber());
                return;
            }

            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);
            }

            // Handover message to Andes
            Andes.getInstance().messageReceived(andesMessage);

            if (log.isDebugEnabled()) {
                PerformanceCounter.recordMessageReceived(queue, incomingMessage.getReceivedChunkCount());
            }
        } catch (AndesException e) {
            throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while storing incoming message metadata", e);
        }

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

    }

    /**
     * read metadata of a message from store
     *
     * @param messageID id of the message
     * @return StorableMessageMetaData
     * @throws AMQException
     */
    public 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 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);
        part.setDataLength(chunkData.length);

        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 int getMessageContentChunk(long messageID, int offsetInMessage, ByteBuffer dst) throws AMQException {
        int contentLenWritten;
        try {
            contentLenWritten = AMQPUtils.getMessageContentChunkConvertedCorrectly(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 void ackReceived(UUID channelID, long messageID, String routingKey, boolean isTopic)
            throws AMQException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("ack received for message id= " + messageID + " channelId= " + channelID);
            }
            AndesSubscription ackSentSubscription = AndesContext.getInstance().getSubscriptionStore()
                    .getLocalSubscriptionForChannelId(channelID, routingKey, isTopic);
            if (ackSentSubscription == null) {
                //TODO : if an ack came here after subscription is closed, should we discard message?
                log.error("Cannot handle Ack. Subscription is null for channel= " + channelID
                        + "Message Destination= " + routingKey);
                return;
            }
            //This can be different from routing key in hierarchical topic case
            String subscriptionBoundDestination = ackSentSubscription.getSubscribedDestination();
            String storageQueueNameOfSubscription = ackSentSubscription.getStorageQueueName();
            AndesAckData andesAckData = AMQPUtils.generateAndesAckMessage(channelID, messageID,
                    subscriptionBoundDestination, storageQueueNameOfSubscription, isTopic);
            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);
        }
    }

    public void rejectMessage(AMQMessage message, AMQChannel channel) throws AMQException {
        try {
            AndesMessageMetadata rejectedMessage = AMQPUtils.convertAMQMessageToAndesMetadata(message,
                    channel.getId());
            log.debug("AMQP BRIDGE: rejected message id= " + rejectedMessage.getMessageID() + " channel = "
                    + rejectedMessage.getChannelId());
            MessagingEngine.getInstance().messageRejected(rejectedMessage);
        } 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
     * @throws AMQException
     */
    public void createAMQPSubscription(Subscription subscription, AMQQueue queue) 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) {
                QueueBrowserDeliveryWorker deliveryWorker = new QueueBrowserDeliveryWorker(subscription, queue,
                        ((SubscriptionImpl.BrowserSubscription) subscription).getProtocolSession());
                deliveryWorker.send();
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Adding Subscription " + subscription.getSubscriptionID() + " to queue "
                            + queue.getName());
                }
                addLocalSubscriptionsForAllBindingsOfQueue(queue, subscription);
            }

        } 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 void closeAMQPSubscription(AMQQueue queue, Subscription subscription) throws AndesException {
        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 void createExchange(Exchange exchange) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: create Exchange" + exchange.getName());
        }
        try {
            AndesExchange andesExchange = AMQPUtils.createAndesExchange(exchange);
            AndesContextInformationManager.getInstance().createExchange(andesExchange);
        } 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 void deleteExchange(Exchange exchange) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: delete Exchange " + exchange.getName());
        }
        try {
            AndesContextInformationManager.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 void createQueue(AMQQueue queue) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: create queue: " + queue.getName());
        }
        try {
            AndesContextInformationManager.getInstance().createQueue(AMQPUtils.createAndesQueue(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 void deleteQueue(AMQQueue queue) throws AMQException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE:  delete queue : " + queue.getName());
        }
        try {
            AndesContextInformationManager.getInstance().deleteQueue(queue.getName());

        } 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
     * @param args       qpid queue arguments
     * @throws AMQInternalException
     */
    public void createBinding(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args)
            throws AMQInternalException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: createBinding 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
             */
            AndesBinding binding = AMQPUtils.createAndesBinding(exchange, queue, routingKey);
            AndesContextInformationManager.getInstance().createBinding(binding);
        } catch (AndesException e) {
            log.error("error while creating binding", e);
            throw new AMQInternalException("error while removing queue", e);
        }
    }

    /**
     * remove binding from andes kernel
     *
     * @param b           qpid binding
     * @param virtualHost virtualhost binding belongs
     * @throws AndesException
     */
    public void removeBinding(Binding b, VirtualHost virtualHost) throws AndesException {
        if (log.isDebugEnabled()) {
            log.debug("AMQP BRIDGE: removeBinding binding key: " + b.getBindingKey() + " exchange: "
                    + b.getExchange().getName() + " queue: " + b.getQueue().getName());
        }
        AndesBinding binding = AMQPUtils.createAndesBinding(b.getExchange(), b.getQueue(),
                new AMQShortString(b.getBindingKey()));
        AndesContextInformationManager.getInstance().removeBinding(binding);
    }

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

        List<Binding> bindingList = queue.getBindings();
        if (bindingList != null && !bindingList.isEmpty()) {
            Set<AndesBinding> uniqueBindings = new HashSet<AndesBinding>();
            List<LocalSubscription> alreadyAddedSubscriptions = new ArrayList<LocalSubscription>();
            /**
             * Iterate unique bindings of the queue and add subscription entries.
             */
            try {
                for (Binding b : bindingList) {
                    AndesBinding andesBinding = AMQPUtils.createAndesBinding(b.getExchange(), b.getQueue(),
                            new AMQShortString(b.getBindingKey()));
                    if (uniqueBindings.add(andesBinding)) {
                        LocalSubscription localSubscription = AMQPUtils.createAMQPLocalSubscription(queue,
                                subscription, b);
                        Andes.getInstance().openLocalSubscription(localSubscription);
                        alreadyAddedSubscriptions.add(localSubscription);
                    }
                }
            } catch (AndesException e) {
                log.warn("Reverting already created subscription entries for subscription " + subscription, e);
                for (LocalSubscription alreadyAddedSub : alreadyAddedSubscriptions) {
                    Andes.getInstance().closeLocalSubscription(alreadyAddedSub);
                }
                throw new AndesException("error while adding the local subscription", 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 void closeLocalSubscriptionsForAllBindingsOfQueue(AMQQueue queue, Subscription subscription)
            throws AndesException {
        List<Binding> bindingList = queue.getBindings();
        if (bindingList != null && !bindingList.isEmpty()) {
            Set<AndesBinding> uniqueBindings = new HashSet<AndesBinding>();
            /**
             * Iterate unique bindings of the queue and remove subscription entries.
             */
            for (Binding b : bindingList) {
                AndesBinding andesBinding = AMQPUtils.createAndesBinding(b.getExchange(), b.getQueue(),
                        new AMQShortString(b.getBindingKey()));
                if (uniqueBindings.add(andesBinding)) {
                    LocalSubscription localSubscription = AMQPUtils.createAMQPLocalSubscription(queue, subscription,
                            b);
                    Andes.getInstance().closeLocalSubscription(localSubscription);
                }
            }
        }
    }

    /**
     * Channel closed. Clear status
     *
     * @param channelID id of the closed channel
     */
    public void channelIsClosing(UUID channelID) {
        Andes.getInstance().clientConnectionClosed(channelID);
    }
}