Package org.apache.qpid.server.queue

Source Code of org.apache.qpid.server.queue.SimpleAMQQueue

package org.apache.qpid.server.queue;

import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.framing.FieldTable;
import org.apache.qpid.server.virtualhost.VirtualHost;
import org.apache.qpid.server.configuration.Configurator;
import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.store.StoreContext;
import org.apache.qpid.server.store.MessageStore;
import org.apache.qpid.server.subscription.Subscription;
import org.apache.qpid.server.subscription.SubscriptionList;
import org.apache.qpid.server.output.ProtocolOutputConverter;
import org.apache.qpid.server.management.ManagedObject;
import org.apache.qpid.AMQException;
import org.apache.qpid.pool.ReadWriteRunnable;
import org.apache.qpid.pool.ReferenceCountingExecutorService;
import org.apache.qpid.configuration.Configured;
import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;

import javax.management.JMException;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF 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.
*
*/
public class SimpleAMQQueue implements AMQQueue, Subscription.StateListener
{
    private static final Logger _logger = Logger.getLogger(SimpleAMQQueue.class);

    private final AMQShortString _name;

    /** null means shared */
    private final AMQShortString _owner;

    private final boolean _durable;

    /** If true, this queue is deleted when the last subscriber is removed */
    private final boolean _autoDelete;

    private final VirtualHost _virtualHost;

    /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */
    private final ExchangeBindings _bindings = new ExchangeBindings(this);

    private final AtomicBoolean _deleted = new AtomicBoolean(false);

    private final List<Task> _deleteTaskList = new CopyOnWriteArrayList<Task>();

    private final AtomicInteger _atomicQueueCount = new AtomicInteger(0);

    private final AtomicLong _atomicQueueSize = new AtomicLong(0L);

    private final AtomicInteger _activeSubscriberCount = new AtomicInteger();

    protected final SubscriptionList _subscriptionList = new SubscriptionList(this);
    private final AtomicReference<SubscriptionList.SubscriptionNode> _lastSubscriptionNode = new AtomicReference<SubscriptionList.SubscriptionNode>(_subscriptionList.getHead());

    private volatile Subscription _exclusiveSubscriber;

    protected final QueueEntryList _entries;

    private final AMQQueueMBean _managedObject;
    private final Executor _asyncDelivery;
    private final AtomicLong _totalMessagesReceived = new AtomicLong();

    /** max allowed size(KB) of a single message */
    @Configured(path = "maximumMessageSize", defaultValue = "0")
    public long _maximumMessageSize;

    /** max allowed number of messages on a queue. */
    @Configured(path = "maximumMessageCount", defaultValue = "0")
    public long _maximumMessageCount;

    /** max queue depth for the queue */
    @Configured(path = "maximumQueueDepth", defaultValue = "0")
    public long _maximumQueueDepth;

    /** maximum message age before alerts occur */
    @Configured(path = "maximumMessageAge", defaultValue = "0")
    public long _maximumMessageAge;

    /** the minimum interval between sending out consequetive alerts of the same type */
    @Configured(path = "minimumAlertRepeatGap", defaultValue = "0")
    public long _minimumAlertRepeatGap;

    private static final int MAX_ASYNC_DELIVERIES = 10;

    private final Set<NotificationCheck> _notificationChecks = EnumSet.noneOf(NotificationCheck.class);

    private final AtomicLong _stateChangeCount = new AtomicLong(Long.MIN_VALUE);
    private AtomicReference _asynchronousRunner = new AtomicReference(null);
    private AtomicInteger _deliveredMessages = new AtomicInteger();
    private AtomicBoolean _stopped = new AtomicBoolean(false);

    protected SimpleAMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost)
            throws AMQException
    {
        this(name, durable, owner, autoDelete, virtualHost, new SimpleQueueEntryList.Factory());
    }

    protected SimpleAMQQueue(AMQShortString name,
                             boolean durable,
                             AMQShortString owner,
                             boolean autoDelete,
                             VirtualHost virtualHost,
                             QueueEntryListFactory entryListFactory)
            throws AMQException
    {

        if (name == null)
        {
            throw new IllegalArgumentException("Queue name must not be null");
        }

        if (virtualHost == null)
        {
            throw new IllegalArgumentException("Virtual Host must not be null");
        }

        _name = name;
        _durable = durable;
        _owner = owner;
        _autoDelete = autoDelete;
        _virtualHost = virtualHost;
        _entries = entryListFactory.createQueueEntryList(this);

        _asyncDelivery = ReferenceCountingExecutorService.getInstance().acquireExecutorService();

        try
        {
            _managedObject = new AMQQueueMBean(this);
            _managedObject.register();
        }
        catch (JMException e)
        {
            throw new AMQException("AMQQueue MBean creation has failed ", e);
        }

        resetNotifications();

    }

    private void resetNotifications()
    {
        // This ensure that the notification checks for the configured alerts are created.
        setMaximumMessageAge(_maximumMessageAge);
        setMaximumMessageCount(_maximumMessageCount);
        setMaximumMessageSize(_maximumMessageSize);
        setMaximumQueueDepth(_maximumQueueDepth);
    }

    // ------ Getters and Setters

    public AMQShortString getName()
    {
        return _name;
    }

    public boolean isDurable()
    {
        return _durable;
    }

    public boolean isAutoDelete()
    {
        return _autoDelete;
    }

    public AMQShortString getOwner()
    {
        return _owner;
    }

    public VirtualHost getVirtualHost()
    {
        return _virtualHost;
    }

    // ------ bind and unbind

    public void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
    {
        exchange.registerQueue(routingKey, this, arguments);
        if (isDurable() && exchange.isDurable())
        {
            _virtualHost.getMessageStore().bindQueue(exchange, routingKey, this, arguments);
        }

        _bindings.addBinding(routingKey, arguments, exchange);
    }

    public void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException
    {
        exchange.deregisterQueue(routingKey, this, arguments);
        if (isDurable() && exchange.isDurable())
        {
            _virtualHost.getMessageStore().unbindQueue(exchange, routingKey, this, arguments);
        }

        boolean removed = _bindings.remove(routingKey, arguments, exchange);
        if (!removed)
        {
            _logger.error("Mismatch between queue bindings and exchange record of bindings");
        }
    }

    public List<ExchangeBinding> getExchangeBindings()
    {
        return new ArrayList<ExchangeBinding>(_bindings.getExchangeBindings());
    }

    // ------ Manage Subscriptions

    public synchronized void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException
    {

        if (isExclusiveSubscriber())
        {
            throw new ExistingExclusiveSubscription();
        }

        if (exclusive)
        {
            if (getConsumerCount() != 0)
            {
                throw new ExistingSubscriptionPreventsExclusive();
            }
            else
            {
                _exclusiveSubscriber = subscription;

            }
        }

        _activeSubscriberCount.incrementAndGet();
        subscription.setStateListener(this);
        subscription.setLastSeenEntry(null, _entries.getHead());

        if (!isDeleted())
        {
            subscription.setQueue(this);
            _subscriptionList.add(subscription);
            if (isDeleted())
            {
                subscription.queueDeleted(this);
            }
        }
        else
        {
            // TODO
        }

        deliverAsync(subscription);

    }

    public synchronized void unregisterSubscription(final Subscription subscription) throws AMQException
    {
        if (subscription == null)
        {
            throw new NullPointerException("subscription argument is null");
        }

        boolean removed = _subscriptionList.remove(subscription);

        if (removed)
        {
            subscription.close();
            // No longer can the queue have an exclusive consumer
            setExclusiveSubscriber(null);

            QueueEntry lastSeen;

            while ((lastSeen = subscription.getLastSeenEntry()) != null)
            {
                subscription.setLastSeenEntry(lastSeen, null);
            }

            // auto-delete queues must be deleted if there are no remaining subscribers

            if (_autoDelete && getConsumerCount() == 0)
            {
                if (_logger.isInfoEnabled())
                {
                    _logger.info("Auto-deleteing queue:" + this);
                }

                delete();

                // we need to manually fire the event to the removed subscription (which was the last one left for this
                // queue. This is because the delete method uses the subscription set which has just been cleared
                subscription.queueDeleted(this);
            }
        }

    }

    // ------ Enqueue / Dequeue

    public QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException
    {

        incrementQueueCount();
        incrementQueueSize(message);

        _totalMessagesReceived.incrementAndGet();

        QueueEntry entry;
        Subscription exclusiveSub = _exclusiveSubscriber;

        if (exclusiveSub != null)
        {
            exclusiveSub.getSendLock();

            try
            {
                entry = _entries.add(message);

                deliverToSubscription(exclusiveSub, entry);

                // where there is more than one producer there's a reasonable chance that even though there is
                // no "queueing" we do not deliver because we get an interleving of _entries.add and
                // deliverToSubscription between threads.  Therefore have one more try.
                if (!(entry.isAcquired() || entry.isDeleted()))
                {
                    deliverToSubscription(exclusiveSub, entry);
                }
            }
            finally
            {
                exclusiveSub.releaseSendLock();
            }
        }
        else
        {
            entry = _entries.add(message);
            /*

            iterate over subscriptions and if any is at the end of the queue and can deliver this message, then deliver the message

             */
            SubscriptionList.SubscriptionNode node = _lastSubscriptionNode.get();
            SubscriptionList.SubscriptionNode nextNode = node.getNext();
            if (nextNode == null)
            {
                nextNode = _subscriptionList.getHead().getNext();
            }
            while (nextNode != null)
            {
                if (_lastSubscriptionNode.compareAndSet(node, nextNode))
                {
                    break;
                }
                else
                {
                    node = _lastSubscriptionNode.get();
                    nextNode = node.getNext();
                    if (nextNode == null)
                    {
                        nextNode = _subscriptionList.getHead().getNext();
                    }
                }
            }

            // always do one extra loop after we believe we've finished
            // this catches the case where we *just* miss an update
            int loops = 2;

            while (!(entry.isAcquired() || entry.isDeleted()) && loops != 0)
            {
                if (nextNode == null)
                {
                    loops--;
                    nextNode = _subscriptionList.getHead();
                }
                else
                {
                    // if subscription at end, and active, offer
                    Subscription sub = nextNode.getSubscription();
                    deliverToSubscription(sub, entry);
                }
                nextNode = nextNode.getNext();

            }
        }

        if (entry.immediateAndNotDelivered())
        {
            dequeue(storeContext, entry);
            entry.dispose(storeContext);
        }
        else if (!(entry.isAcquired() || entry.isDeleted()))
        {
            checkSubscriptionsNotAheadOfDelivery(entry);

            deliverAsync();
        }

        try
        {
            _managedObject.checkForNotification(entry.getMessage());
        }
        catch (JMException e)
        {
            throw new AMQException("Unable to get notification from manage queue: " + e, e);
        }

        return entry;

    }

    private void deliverToSubscription(final Subscription sub, final QueueEntry entry)
            throws AMQException
    {

        sub.getSendLock();
        try
        {
            if (subscriptionReadyAndHasInterest(sub, entry)
                && !sub.isSuspended())
            {
                if (!sub.wouldSuspend(entry))
                {
                    if (!sub.isBrowser() && !entry.acquire(sub))
                    {
                        // restore credit here that would have been taken away by wouldSuspend since we didn't manage
                        // to acquire the entry for this subscription
                        sub.restoreCredit(entry);
                    }
                    else
                    {

                        deliverMessage(sub, entry);

                    }
                }
            }
        }
        finally
        {
            sub.releaseSendLock();
        }
    }

    protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry)
    {
        // This method is only required for queues which mess with ordering
        // Simple Queues don't :-)
    }

    private void incrementQueueSize(final AMQMessage message)
    {
        getAtomicQueueSize().addAndGet(message.getSize());
    }

    private void incrementQueueCount()
    {
        getAtomicQueueCount().incrementAndGet();
    }

    private void deliverMessage(final Subscription sub, final QueueEntry entry)
            throws AMQException
    {
        _deliveredMessages.incrementAndGet();
        sub.send(entry);

    }

    private boolean subscriptionReadyAndHasInterest(final Subscription sub, final QueueEntry entry)
    {

        // We need to move this subscription on, past entries which are already acquired, or deleted or ones it has no
        // interest in.
        QueueEntry node = sub.getLastSeenEntry();
        while (node != null && (node.isAcquired() || node.isDeleted() || !sub.hasInterest(node)))
        {

            QueueEntry newNode = _entries.next(node);
            if (newNode != null)
            {
                sub.setLastSeenEntry(node, newNode);
                node = sub.getLastSeenEntry();
            }
            else
            {
                node = null;
                break;
            }

        }

        if (node == entry)
        {
            // If the first entry that subscription can process is the one we are trying to deliver to it, then we are
            // good
            return true;
        }
        else
        {
            // Otherwise we should try to update the subscription's last seen entry to the entry we got to, providing
            // no-one else has updated it to something furhter on in the list
            //TODO - check
            //updateLastSeenEntry(sub, entry);
            return false;
        }

    }

    private void updateLastSeenEntry(final Subscription sub, final QueueEntry entry)
    {
        QueueEntry node = sub.getLastSeenEntry();

        if (node != null && entry.compareTo(node) < 0 && sub.hasInterest(entry))
        {
            do
            {
                if (sub.setLastSeenEntry(node, entry))
                {
                    return;
                }
                else
                {
                    node = sub.getLastSeenEntry();
                }
            }
            while (node != null && entry.compareTo(node) < 0);
        }

    }

    public void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException
    {

        SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator();
        // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards
        while (subscriberIter.advance())
        {
            Subscription sub = subscriberIter.getNode().getSubscription();

            // we don't make browsers send the same stuff twice
            if (!sub.isBrowser())
            {
                updateLastSeenEntry(sub, entry);
            }
        }

        deliverAsync();

    }

    public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException
    {
        decrementQueueCount();
        decrementQueueSize(entry);
        if (entry.acquiredBySubscription())
        {
            _deliveredMessages.decrementAndGet();
        }

        try
        {
            AMQMessage msg = entry.getMessage();
            if (msg.isPersistent())
            {
                _virtualHost.getMessageStore().dequeueMessage(storeContext, this, msg.getMessageId());
            }
            //entry.dispose(storeContext);

        }
        catch (MessageCleanupException e)
        {
            // Message was dequeued, but could not then be deleted
            // though it is no longer referenced. This should be very
            // rare and can be detected and cleaned up on recovery or
            // done through some form of manual intervention.
            _logger.error(e, e);
        }
        catch (AMQException e)
        {
            throw new FailedDequeueException(_name.toString(), e);
        }

    }

    private void decrementQueueSize(final QueueEntry entry)
    {
        getAtomicQueueSize().addAndGet(-entry.getMessage().getSize());
    }

    void decrementQueueCount()
    {
        getAtomicQueueCount().decrementAndGet();
    }

    public boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException
    {
        /* TODO : This is wrong as the subscription may be suspended, we should instead change the state of the message
                  entry to resend and move back the subscription pointer. */

        subscription.getSendLock();
        try
        {
            if (!subscription.isClosed())
            {
                deliverMessage(subscription, entry);
                return true;
            }
            else
            {
                return false;
            }
        }
        finally
        {
            subscription.releaseSendLock();
        }
    }

    public int getConsumerCount()
    {
        return _subscriptionList.size();
    }

    public int getActiveConsumerCount()
    {
        return _activeSubscriberCount.get();
    }

    public boolean isUnused()
    {
        return getConsumerCount() == 0;
    }

    public boolean isEmpty()
    {
        return getMessageCount() == 0;
    }

    public int getMessageCount()
    {
        return getAtomicQueueCount().get();
    }

    public long getQueueDepth()
    {
        return getAtomicQueueSize().get();
    }

    public int getUndeliveredMessageCount()
    {
        int count = getMessageCount() - _deliveredMessages.get();
        if (count < 0)
        {
            return 0;
        }
        else
        {
            return count;
        }
    }

    public long getReceivedMessageCount()
    {
        return _totalMessagesReceived.get();
    }

    public long getOldestMessageArrivalTime()
    {
        QueueEntry entry = getOldestQueueEntry();
        return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime();
    }

    protected QueueEntry getOldestQueueEntry()
    {
        return _entries.next(_entries.getHead());
    }

    public boolean isDeleted()
    {
        return _deleted.get();
    }

    public List<QueueEntry> getMessagesOnTheQueue()
    {
        ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>();
        QueueEntryIterator queueListIterator = _entries.iterator();
        while (queueListIterator.advance())
        {
            QueueEntry node = queueListIterator.getNode();
            if (node != null && !node.isDeleted())
            {
                entryList.add(node);
            }
        }
        return entryList;

    }

    public void stateChange(Subscription sub, Subscription.State oldState, Subscription.State newState)
    {
        if (oldState == Subscription.State.ACTIVE && newState != Subscription.State.ACTIVE)
        {
            _activeSubscriberCount.decrementAndGet();

        }
        else if (newState == Subscription.State.ACTIVE)
        {
            if (oldState != Subscription.State.ACTIVE)
            {
                _activeSubscriberCount.incrementAndGet();

            }
            deliverAsync(sub);
        }
    }

    public int compareTo(final AMQQueue o)
    {
        return _name.compareTo(o.getName());
    }

    public AtomicInteger getAtomicQueueCount()
    {
        return _atomicQueueCount;
    }

    public AtomicLong getAtomicQueueSize()
    {
        return _atomicQueueSize;
    }

    private boolean isExclusiveSubscriber()
    {
        return _exclusiveSubscriber != null;
    }

    private void setExclusiveSubscriber(Subscription exclusiveSubscriber)
    {
        _exclusiveSubscriber = exclusiveSubscriber;
    }

    public static interface QueueEntryFilter
    {
        public boolean accept(QueueEntry entry);

        public boolean filterComplete();
    }

    public List<QueueEntry> getMessagesOnTheQueue(final long fromMessageId, final long toMessageId)
    {
        return getMessagesOnTheQueue(new QueueEntryFilter()
        {

            public boolean accept(QueueEntry entry)
            {
                final long messageId = entry.getMessage().getMessageId();
                return messageId >= fromMessageId && messageId <= toMessageId;
            }

            public boolean filterComplete()
            {
                return false;
            }
        });
    }

    public QueueEntry getMessageOnTheQueue(final long messageId)
    {
        List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
        {
            private boolean _complete;

            public boolean accept(QueueEntry entry)
            {
                _complete = entry.getMessage().getMessageId() == messageId;
                return _complete;
            }

            public boolean filterComplete()
            {
                return _complete;
            }
        });
        return entries.isEmpty() ? null : entries.get(0);
    }

    public List<QueueEntry> getMessagesOnTheQueue(QueueEntryFilter filter)
    {
        ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>();
        QueueEntryIterator queueListIterator = _entries.iterator();
        while (queueListIterator.advance() && !filter.filterComplete())
        {
            QueueEntry node = queueListIterator.getNode();
            if (!node.isDeleted() && filter.accept(node))
            {
                entryList.add(node);
            }
        }
        return entryList;

    }

    public void moveMessagesToAnotherQueue(final long fromMessageId,
                                           final long toMessageId,
                                           String queueName,
                                           StoreContext storeContext)
    {

        AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
        MessageStore store = getVirtualHost().getMessageStore();

        List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
        {

            public boolean accept(QueueEntry entry)
            {
                final long messageId = entry.getMessage().getMessageId();
                return (messageId >= fromMessageId)
                       && (messageId <= toMessageId)
                       && entry.acquire();
            }

            public boolean filterComplete()
            {
                return false;
            }
        });

        try
        {
            store.beginTran(storeContext);

            // Move the messages in on the message store.
            for (QueueEntry entry : entries)
            {
                AMQMessage message = entry.getMessage();

                if (message.isPersistent() && toQueue.isDurable())
                {
                    store.enqueueMessage(storeContext, toQueue, message.getMessageId());
                }
                // dequeue does not decrement the refence count
                entry.dequeue(storeContext);
            }

            // Commit and flush the move transcations.
            try
            {
                store.commitTran(storeContext);
            }
            catch (AMQException e)
            {
                throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
            }
        }
        catch (AMQException e)
        {
            try
            {
                store.abortTran(storeContext);
            }
            catch (AMQException rollbackEx)
            {
                _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx);
            }
            throw new RuntimeException(e);
        }

        try
        {
            for (QueueEntry entry : entries)
            {
                toQueue.enqueue(storeContext, entry.getMessage());

            }
        }
        catch (MessageCleanupException e)
        {
            throw new RuntimeException(e);
        }
        catch (AMQException e)
        {
            throw new RuntimeException(e);
        }

    }

    public void copyMessagesToAnotherQueue(final long fromMessageId,
                                           final long toMessageId,
                                           String queueName,
                                           final StoreContext storeContext)
    {
        AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName));
        MessageStore store = getVirtualHost().getMessageStore();

        List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter()
        {

            public boolean accept(QueueEntry entry)
            {
                final long messageId = entry.getMessage().getMessageId();
                if ((messageId >= fromMessageId)
                    && (messageId <= toMessageId))
                {
                    if (!entry.isDeleted())
                    {
                        return entry.getMessage().incrementReference();
                    }
                }

                return false;
            }

            public boolean filterComplete()
            {
                return false;
            }
        });

        try
        {
            store.beginTran(storeContext);

            // Move the messages in on the message store.
            for (QueueEntry entry : entries)
            {
                AMQMessage message = entry.getMessage();

                if (message.isReferenced() && message.isPersistent() && toQueue.isDurable())
                {
                    store.enqueueMessage(storeContext, toQueue, message.getMessageId());
                }
            }

            // Commit and flush the move transcations.
            try
            {
                store.commitTran(storeContext);
            }
            catch (AMQException e)
            {
                throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e);
            }
        }
        catch (AMQException e)
        {
            try
            {
                store.abortTran(storeContext);
            }
            catch (AMQException rollbackEx)
            {
                _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx);
            }
            throw new RuntimeException(e);
        }

        try
        {
            for (QueueEntry entry : entries)
            {
                if (entry.getMessage().isReferenced())
                {
                    toQueue.enqueue(storeContext, entry.getMessage());
                }
            }
        }
        catch (MessageCleanupException e)
        {
            throw new RuntimeException(e);
        }
        catch (AMQException e)
        {
            throw new RuntimeException(e);
        }

    }

    public void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext)
    {

        try
        {
            QueueEntryIterator queueListIterator = _entries.iterator();

            while (queueListIterator.advance())
            {
                QueueEntry node = queueListIterator.getNode();

                final long messageId = node.getMessage().getMessageId();

                if ((messageId >= fromMessageId)
                    && (messageId <= toMessageId)
                    && !node.isDeleted()
                    && node.acquire())
                {
                    node.discard(storeContext);
                }

            }
        }
        catch (AMQException e)
        {
            throw new RuntimeException(e);
        }

    }

    // ------ Management functions

    public void deleteMessageFromTop(StoreContext storeContext) throws AMQException
    {
        QueueEntryIterator queueListIterator = _entries.iterator();
        boolean noDeletes = true;

        while (noDeletes && queueListIterator.advance())
        {
            QueueEntry node = queueListIterator.getNode();
            if (!node.isDeleted() && node.acquire())
            {
                node.discard(storeContext);
                noDeletes = false;
            }

        }
    }

    public long clearQueue(StoreContext storeContext) throws AMQException
    {

        QueueEntryIterator queueListIterator = _entries.iterator();
        long count = 0;

        while (queueListIterator.advance())
        {
            QueueEntry node = queueListIterator.getNode();
            if (!node.isDeleted() && node.acquire())
            {
                node.discard(storeContext);
                count++;
            }

        }
        return count;

    }

    public void addQueueDeleteTask(final Task task)
    {
        _deleteTaskList.add(task);
    }

    public int delete() throws AMQException
    {
        if (!_deleted.getAndSet(true))
        {

            SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator();

            while (subscriptionIter.advance())
            {
                Subscription s = subscriptionIter.getNode().getSubscription();
                if (s != null)
                {
                    s.queueDeleted(this);
                }
            }

            _bindings.deregister();
            _virtualHost.getQueueRegistry().unregisterQueue(_name);

            _managedObject.unregister();
            for (Task task : _deleteTaskList)
            {
                task.doTask(this);
            }

            _deleteTaskList.clear();
            stop();
        }
        return getMessageCount();

    }

    public void stop()
    {
        if (!_stopped.getAndSet(true))
        {
            ReferenceCountingExecutorService.getInstance().releaseExecutorService();
        }
    }

    public void deliverAsync()
    {
        _stateChangeCount.incrementAndGet();

        Runner runner = new Runner();

        if (_asynchronousRunner.compareAndSet(null, runner))
        {
            _asyncDelivery.execute(runner);
        }
    }

    public void deliverAsync(Subscription sub)
    {
        _asyncDelivery.execute(new SubFlushRunner(sub));
    }

    private class Runner implements ReadWriteRunnable
    {
        public void run()
        {
            try
            {
                processQueue(this);
            }
            catch (AMQException e)
            {
                _logger.error(e);
            }

        }

        public boolean isRead()
        {
            return false;
        }

        public boolean isWrite()
        {
            return true;
        }
    }

    private class SubFlushRunner implements ReadWriteRunnable
    {
        private final Subscription _sub;

        public SubFlushRunner(Subscription sub)
        {
            _sub = sub;
        }

        public void run()
        {
            boolean complete = false;
            try
            {
                complete = flushSubscription(_sub, new Long(MAX_ASYNC_DELIVERIES));

            }
            catch (AMQException e)
            {
                _logger.error(e);
            }
            if (!complete && !_sub.isSuspended())
            {
                _asyncDelivery.execute(this);
            }

        }

        public boolean isRead()
        {
            return false;
        }

        public boolean isWrite()
        {
            return true;
        }
    }

    public void flushSubscription(Subscription sub) throws AMQException
    {
        flushSubscription(sub, Long.MAX_VALUE);
    }

    public boolean flushSubscription(Subscription sub, Long iterations) throws AMQException
    {
        boolean atTail = false;

        while (!sub.isSuspended() && !atTail && iterations != 0)
        {
            try
            {
                sub.getSendLock();
                atTail =  attemptDelivery(sub);
                if (atTail && sub.isAutoClose())
                {
                    unregisterSubscription(sub);

                    ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter();
                    converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag());
                }
                else if (!atTail)
                {
                    iterations--;
                }
            }
            finally
            {
                sub.releaseSendLock();
            }
        }

        // if there's (potentially) more than one subscription the others will potentially not have been advanced to the
        // next entry they are interested in yet.  This would lead to holding on to references to expired messages, etc
        // which would give us memory "leak".

        if (!isExclusiveSubscriber())
        {
            advanceAllSubscriptions();
        }
        return atTail;
    }

    private boolean attemptDelivery(Subscription sub) throws AMQException
    {
        boolean atTail = false;
        boolean advanced = false;
        boolean subActive = sub.isActive();
        if (subActive)
        {
            QueueEntry node = moveSubscriptionToNextNode(sub);
            if (!(node.isAcquired() || node.isDeleted()))
            {
                if (!sub.isSuspended())
                {
                    if (sub.hasInterest(node))
                    {
                        if (!sub.wouldSuspend(node))
                        {
                            if (!sub.isBrowser() && !node.acquire(sub))
                            {
                                sub.restoreCredit(node);
                            }
                            else
                            {
                                deliverMessage(sub, node);

                                if (sub.isBrowser())
                                {
                                    QueueEntry newNode = _entries.next(node);

                                    if (newNode != null)
                                    {
                                        advanced = true;
                                        sub.setLastSeenEntry(node, newNode);
                                        node = sub.getLastSeenEntry();
                                    }
                                }
                            }

                        }
                        else // Not enough Credit for message and wouldSuspend
                        {
                            //QPID-1187 - Treat the subscription as suspended for this message
                            // and wait for the message to be removed to continue delivery.
                            subActive = false;
                            node.addStateChangeListener(new QueueEntryListener(sub, node));
                        }
                    }
                    else
                    {
                        // this subscription is not interested in this node so we can skip over it
                        QueueEntry newNode = _entries.next(node);
                        if (newNode != null)
                        {
                            sub.setLastSeenEntry(node, newNode);
                        }
                    }
                }

            }
            atTail = (_entries.next(node) == null) && !advanced;
        }
        return atTail || !subActive;
    }

    protected void advanceAllSubscriptions() throws AMQException
    {
        SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator();
        while (subscriberIter.advance())
        {
            SubscriptionList.SubscriptionNode subNode = subscriberIter.getNode();
            Subscription sub = subNode.getSubscription();
            moveSubscriptionToNextNode(sub);
        }
    }

    private QueueEntry moveSubscriptionToNextNode(final Subscription sub)
            throws AMQException
    {
        QueueEntry node = sub.getLastSeenEntry();

        while (node != null && (node.isAcquired() || node.isDeleted() || node.expired()))
        {
            if (!node.isAcquired() && !node.isDeleted() && node.expired())
            {
                if (node.acquire())
                {
                    final StoreContext reapingStoreContext = new StoreContext();
                    node.discard(reapingStoreContext);
                }
            }
            QueueEntry newNode = _entries.next(node);
            if (newNode != null)
            {
                sub.setLastSeenEntry(node, newNode);
                node = sub.getLastSeenEntry();
            }
            else
            {
                break;
            }

        }
        return node;
    }

    private void processQueue(Runnable runner) throws AMQException
    {
        long stateChangeCount;
        long previousStateChangeCount = Long.MIN_VALUE;
        boolean deliveryIncomplete = true;

        int extraLoops = 1;
        Long iterations = new Long(MAX_ASYNC_DELIVERIES);

        _asynchronousRunner.compareAndSet(runner, null);

        while (iterations != 0 && ((previousStateChangeCount != (stateChangeCount = _stateChangeCount.get())) || deliveryIncomplete) && _asynchronousRunner.compareAndSet(null, runner))
        {
            // we want to have one extra loop after every subscription has reached the point where it cannot move
            // further, just in case the advance of one subscription in the last loop allows a different subscription to
            // move forward in the next iteration

            if (previousStateChangeCount != stateChangeCount)
            {
                extraLoops = 1;
            }

            previousStateChangeCount = stateChangeCount;
            deliveryIncomplete = _subscriptionList.size() != 0;
            boolean done = true;

            SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator();
            //iterate over the subscribers and try to advance their pointer
            while (subscriptionIter.advance())
            {
                boolean closeConsumer = false;
                Subscription sub = subscriptionIter.getNode().getSubscription();
                sub.getSendLock();
                try
                {
                    if (sub != null)
                    {

                        QueueEntry node = moveSubscriptionToNextNode(sub);
                        if (node != null)
                        {
                            done = attemptDelivery(sub);
                        }
                    }
                    if (done)
                    {
                        if (extraLoops == 0)
                        {
                            deliveryIncomplete = false;
                            if (sub.isAutoClose())
                            {
                                unregisterSubscription(sub);

                                ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter();
                                converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag());
                            }
                        }
                        else
                        {
                            extraLoops--;
                        }
                    }
                    else
                    {
                        iterations--;
                        extraLoops = 1;
                    }
                }
                finally
                {
                    sub.releaseSendLock();
                }
            }
            _asynchronousRunner.set(null);
        }

        // If deliveries == 0 then the limitting factor was the time-slicing rather than available messages or credit
        // therefore we should schedule this runner again (unless someone beats us to it :-) ).
        if (iterations == 0 && _asynchronousRunner.compareAndSet(null, runner))
        {
            _asyncDelivery.execute(runner);
        }
    }

    public void removeExpiredIfNoSubscribers() throws AMQException
    {

        final StoreContext storeContext = new StoreContext();

        QueueEntryIterator queueListIterator = _entries.iterator();

        while (queueListIterator.advance())
        {
            QueueEntry node = queueListIterator.getNode();
            if (!node.isDeleted() && node.expired() && node.acquire())
            {

                node.discard(storeContext);
            }

        }

    }

    public long getMinimumAlertRepeatGap()
    {
        return _minimumAlertRepeatGap;
    }

    public void setMinimumAlertRepeatGap(long minimumAlertRepeatGap)
    {
        _minimumAlertRepeatGap = minimumAlertRepeatGap;
    }

    public long getMaximumMessageAge()
    {
        return _maximumMessageAge;
    }

    public void setMaximumMessageAge(long maximumMessageAge)
    {
        _maximumMessageAge = maximumMessageAge;
        if (maximumMessageAge == 0L)
        {
            _notificationChecks.remove(NotificationCheck.MESSAGE_AGE_ALERT);
        }
        else
        {
            _notificationChecks.add(NotificationCheck.MESSAGE_AGE_ALERT);
        }
    }

    public long getMaximumMessageCount()
    {
        return _maximumMessageCount;
    }

    public void setMaximumMessageCount(final long maximumMessageCount)
    {
        _maximumMessageCount = maximumMessageCount;
        if (maximumMessageCount == 0L)
        {
            _notificationChecks.remove(NotificationCheck.MESSAGE_COUNT_ALERT);
        }
        else
        {
            _notificationChecks.add(NotificationCheck.MESSAGE_COUNT_ALERT);
        }

    }

    public long getMaximumQueueDepth()
    {
        return _maximumQueueDepth;
    }

    // Sets the queue depth, the max queue size
    public void setMaximumQueueDepth(final long maximumQueueDepth)
    {
        _maximumQueueDepth = maximumQueueDepth;
        if (maximumQueueDepth == 0L)
        {
            _notificationChecks.remove(NotificationCheck.QUEUE_DEPTH_ALERT);
        }
        else
        {
            _notificationChecks.add(NotificationCheck.QUEUE_DEPTH_ALERT);
        }

    }

    public long getMaximumMessageSize()
    {
        return _maximumMessageSize;
    }

    public void setMaximumMessageSize(final long maximumMessageSize)
    {
        _maximumMessageSize = maximumMessageSize;
        if (maximumMessageSize == 0L)
        {
            _notificationChecks.remove(NotificationCheck.MESSAGE_SIZE_ALERT);
        }
        else
        {
            _notificationChecks.add(NotificationCheck.MESSAGE_SIZE_ALERT);
        }
    }

    public Set<NotificationCheck> getNotificationChecks()
    {
        return _notificationChecks;
    }

    public ManagedObject getManagedObject()
    {
        return _managedObject;
    }

    private final class QueueEntryListener implements QueueEntry.StateChangeListener
    {
        private final QueueEntry _entry;
        private final Subscription _sub;

        public QueueEntryListener(final Subscription sub, final QueueEntry entry)
        {
            _entry = entry;
            _sub = sub;
        }

        public boolean equals(Object o)
        {
            return _entry == ((QueueEntryListener) o)._entry && _sub == ((QueueEntryListener) o)._sub;
        }

        public int hashCode()
        {
            return System.identityHashCode(_entry) ^ System.identityHashCode(_sub);
        }

        public void stateChanged(QueueEntry entry, QueueEntry.State oldSate, QueueEntry.State newState)
        {
            entry.removeStateChangeListener(this);
            deliverAsync(_sub);
        }
    }

    public List<Long> getMessagesOnTheQueue(int num)
    {
        return getMessagesOnTheQueue(num, 0);
    }

    public List<Long> getMessagesOnTheQueue(int num, int offset)
    {
        ArrayList<Long> ids = new ArrayList<Long>(num);
        QueueEntryIterator it = _entries.iterator();
        for (int i = 0; i < offset; i++)
        {
            it.advance();
        }

        for (int i = 0; i < num && !it.atTail(); i++)
        {
            it.advance();
            ids.add(it.getNode().getMessage().getMessageId());
        }
        return ids;
    }

    public void configure(Configuration queueConfiguration)
    {
        Configurator.configure(this, queueConfiguration);
        resetNotifications();
    }
}
TOP

Related Classes of org.apache.qpid.server.queue.SimpleAMQQueue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.