Package org.apache.activemq.broker.region

Source Code of org.apache.activemq.broker.region.Queue

/**
*
* 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.
*/
package org.apache.activemq.broker.region;

import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;

import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.cursors.PendingMessageCursor;
import org.apache.activemq.broker.region.cursors.VMPendingMessageCursor;
import org.apache.activemq.broker.region.group.MessageGroupHashBucketFactory;
import org.apache.activemq.broker.region.group.MessageGroupMap;
import org.apache.activemq.broker.region.group.MessageGroupMapFactory;
import org.apache.activemq.broker.region.group.MessageGroupSet;
import org.apache.activemq.broker.region.policy.DeadLetterStrategy;
import org.apache.activemq.broker.region.policy.DispatchPolicy;
import org.apache.activemq.broker.region.policy.PendingQueueMessageStoragePolicy;
import org.apache.activemq.broker.region.policy.RoundRobinDispatchPolicy;
import org.apache.activemq.broker.region.policy.SharedDeadLetterStrategy;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ConsumerId;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.filter.BooleanExpression;
import org.apache.activemq.filter.MessageEvaluationContext;
import org.apache.activemq.memory.UsageManager;
import org.apache.activemq.selector.SelectorParser;
import org.apache.activemq.store.MessageRecoveryListener;
import org.apache.activemq.store.MessageStore;
import org.apache.activemq.thread.TaskRunnerFactory;
import org.apache.activemq.thread.Valve;
import org.apache.activemq.transaction.Synchronization;
import org.apache.activemq.util.BrokerSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.jms.InvalidSelectorException;
import javax.jms.JMSException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
* The Queue is a List of MessageEntry objects that are dispatched to matching
* subscriptions.
*
* @version $Revision: 1.28 $
*/
public class Queue implements Destination {

    private final Log log;

    protected final ActiveMQDestination destination;
    protected final List consumers = new CopyOnWriteArrayList();
    protected final Valve dispatchValve = new Valve(true);
    protected final UsageManager usageManager;
    protected final DestinationStatistics destinationStatistics = new DestinationStatistics();
    protected  PendingMessageCursor messages = new VMPendingMessageCursor();

    private LockOwner exclusiveOwner;
    private MessageGroupMap messageGroupOwners;

    protected long garbageSize = 0;
    protected long garbageSizeBeforeCollection = 1000;
    private DispatchPolicy dispatchPolicy = new RoundRobinDispatchPolicy();
    protected final MessageStore store;
    protected int highestSubscriptionPriority;
    private DeadLetterStrategy deadLetterStrategy = new SharedDeadLetterStrategy();
    private MessageGroupMapFactory messageGroupMapFactory = new MessageGroupHashBucketFactory();

    public Queue(ActiveMQDestination destination, final UsageManager memoryManager, MessageStore store, DestinationStatistics parentStats,
            TaskRunnerFactory taskFactory) throws Exception {
        this.destination = destination;
        this.usageManager = new UsageManager(memoryManager);
        this.usageManager.setLimit(Long.MAX_VALUE);
        this.store = store;

        // Let the store know what usage manager we are using so that he can
        // flush messages to disk
        // when usage gets high.
        if (store != null) {
            store.setUsageManager(usageManager);
        }

        destinationStatistics.setParent(parentStats);
        this.log = LogFactory.getLog(getClass().getName() + "." + destination.getPhysicalName());

       
    }
   
    public void initialize() throws Exception {
        if (store != null) {
            // Restore the persistent messages.
            store.recover(new MessageRecoveryListener() {
                public void recoverMessage(Message message) {
                    message.setRegionDestination(Queue.this);
                    MessageReference reference = createMessageReference(message);
                    synchronized (messages) {
                        try{
                            messages.addMessageLast(reference);
                        }catch(Exception e){
                           log.fatal("Failed to add message to cursor",e);
                        }
                    }
                    reference.decrementReferenceCount();
                    destinationStatistics.getMessages().increment();
                }

                public void recoverMessageReference(String messageReference) throws Exception {
                    throw new RuntimeException("Should not be called.");
                }

                public void finished() {
                }
            });
        }
    }

    public synchronized boolean lock(MessageReference node, LockOwner lockOwner) {
        if (exclusiveOwner == lockOwner)
            return true;
        if (exclusiveOwner != null)
            return false;
        if (lockOwner.getLockPriority() < highestSubscriptionPriority)
            return false;
        if (lockOwner.isLockExclusive()) {
            exclusiveOwner = lockOwner;
        }
        return true;
    }

    public void addSubscription(ConnectionContext context, Subscription sub) throws Exception {
        sub.add(context, this);
        destinationStatistics.getConsumers().increment();

        // synchronize with dispatch method so that no new messages are sent
        // while
        // setting up a subscription. avoid out of order messages, duplicates
        // etc.
        dispatchValve.turnOff();

        MessageEvaluationContext msgContext = context.getMessageEvaluationContext();
        try {
            synchronized (consumers) {
                consumers.add(sub);
            }

            highestSubscriptionPriority = calcHighestSubscriptionPriority();
            msgContext.setDestination(destination);

            synchronized (messages) {
                // Add all the matching messages in the queue to the
                // subscription.
                messages.reset();
                while(messages.hasNext()) {

                    QueueMessageReference node = (QueueMessageReference) messages.next();
                    if (node.isDropped()) {
                        continue;
                    }

                    try {
                        msgContext.setMessageReference(node);
                        if (sub.matches(node, msgContext)) {
                            sub.add(node);
                        }
                    }
                    catch (IOException e) {
                        log.warn("Could not load message: " + e, e);
                    }
                }
            }

        }
        finally {
            msgContext.clear();
            dispatchValve.turnOn();
        }
    }

    public void removeSubscription(ConnectionContext context, Subscription sub) throws Exception {

        destinationStatistics.getConsumers().decrement();

        // synchronize with dispatch method so that no new messages are sent
        // while
        // removing up a subscription.
        dispatchValve.turnOff();

        try {

            synchronized (consumers) {
                consumers.remove(sub);
            }
            sub.remove(context, this);

            highestSubscriptionPriority = calcHighestSubscriptionPriority();

            boolean wasExclusiveOwner = false;
            if (exclusiveOwner == sub) {
                exclusiveOwner = null;
                wasExclusiveOwner = true;
            }

            ConsumerId consumerId = sub.getConsumerInfo().getConsumerId();
            MessageGroupSet ownedGroups = getMessageGroupOwners().removeConsumer(consumerId);

            if (!sub.getConsumerInfo().isBrowser()) {
                MessageEvaluationContext msgContext = context.getMessageEvaluationContext();
                try {
                    msgContext.setDestination(destination);

                    // lets copy the messages to dispatch to avoid deadlock
                    List messagesToDispatch = new ArrayList();
                    synchronized (messages) {
                        messages.reset();
                        while(messages.hasNext()) {
                            QueueMessageReference node = (QueueMessageReference) messages.next();
                            if (node.isDropped()) {
                                continue;
                            }

                            String groupID = node.getGroupID();

                            // Re-deliver all messages that the sub locked
                            if (node.getLockOwner() == sub || wasExclusiveOwner || (groupID != null && ownedGroups.contains(groupID))) {
                                messagesToDispatch.add(node);
                            }
                        }
                    }

                    // now lets dispatch from the copy of the collection to
                    // avoid deadlocks
                    for (Iterator iter = messagesToDispatch.iterator(); iter.hasNext();) {
                        QueueMessageReference node = (QueueMessageReference) iter.next();
                        node.incrementRedeliveryCounter();
                        node.unlock();
                        msgContext.setMessageReference(node);
                        dispatchPolicy.dispatch(context, node, msgContext, consumers);
                    }
                }
                finally {
                    msgContext.clear();
                }
            }
        }
        finally {
            dispatchValve.turnOn();
        }

    }

    public void send(final ConnectionContext context, final Message message) throws Exception {

        if (context.isProducerFlowControl()) {
            if (usageManager.isSendFailIfNoSpace() && usageManager.isFull()) {
                throw new javax.jms.ResourceAllocationException("Usage Manager memory limit reached");
            }
            else {
                usageManager.waitForSpace();
            }
        }

        message.setRegionDestination(this);

        if (store != null && message.isPersistent())
            store.addMessage(context, message);

        final MessageReference node = createMessageReference(message);
        try {

            if (context.isInTransaction()) {
                context.getTransaction().addSynchronization(new Synchronization() {
                    public void afterCommit() throws Exception {
                        dispatch(context, node, message);
                    }
                });
            }
            else {
                dispatch(context, node, message);
            }
        }
        finally {
            node.decrementReferenceCount();
        }
    }

    public void dispose(ConnectionContext context) throws IOException {
        if (store != null) {
            store.removeAllMessages(context);
        }
        destinationStatistics.setParent(null);
    }

    public void dropEvent() {
        dropEvent(false);
    }

    public void dropEvent(boolean skipGc) {
        // TODO: need to also decrement when messages expire.
        destinationStatistics.getMessages().decrement();
        synchronized (messages) {
            garbageSize++;
            if (!skipGc && garbageSize > garbageSizeBeforeCollection) {
                gc();
            }
        }
    }

    public void gc() {
        synchronized (messages) {
            messages.resetForGC();
            while(messages.hasNext()) {
                // Remove dropped messages from the queue.
                QueueMessageReference node = (QueueMessageReference) messages.next();
                if (node.isDropped()) {
                    garbageSize--;
                    messages.remove();
                    continue;
                }
            }
        }
    }

    public void acknowledge(ConnectionContext context, Subscription sub, MessageAck ack, MessageReference node) throws IOException {
        if (store != null && node.isPersistent()) {
            // the original ack may be a ranged ack, but we are trying to delete
            // a specific
            // message store here so we need to convert to a non ranged ack.
            if (ack.getMessageCount() > 0) {
                // Dup the ack
                MessageAck a = new MessageAck();
                ack.copy(a);
                ack = a;
                // Convert to non-ranged.
                ack.setFirstMessageId(node.getMessageId());
                ack.setLastMessageId(node.getMessageId());
                ack.setMessageCount(1);
            }
            store.removeMessage(context, ack);
        }
    }

    Message loadMessage(MessageId messageId) throws IOException {
        Message msg = store.getMessage(messageId);
        if (msg != null) {
            msg.setRegionDestination(this);
        }
        return msg;
    }

    public String toString() {
        int size = 0;
        synchronized (messages) {
            size = messages.size();
        }
        return "Queue: destination=" + destination.getPhysicalName() + ", subscriptions=" + consumers.size() + ", memory=" + usageManager.getPercentUsage()
                + "%, size=" + size + ", in flight groups=" + messageGroupOwners;
    }

    public void start() throws Exception {
    }

    public void stop() throws Exception {
    }

    // Properties
    // -------------------------------------------------------------------------
    public ActiveMQDestination getActiveMQDestination() {
        return destination;
    }

    public String getDestination() {
        return destination.getPhysicalName();
    }

    public UsageManager getUsageManager() {
        return usageManager;
    }

    public DestinationStatistics getDestinationStatistics() {
        return destinationStatistics;
    }

    public MessageGroupMap getMessageGroupOwners() {
        if (messageGroupOwners == null) {
            messageGroupOwners = getMessageGroupMapFactory().createMessageGroupMap();
        }
        return messageGroupOwners;
    }

    public DispatchPolicy getDispatchPolicy() {
        return dispatchPolicy;
    }

    public void setDispatchPolicy(DispatchPolicy dispatchPolicy) {
        this.dispatchPolicy = dispatchPolicy;
    }

    public DeadLetterStrategy getDeadLetterStrategy() {
        return deadLetterStrategy;
    }

    public void setDeadLetterStrategy(DeadLetterStrategy deadLetterStrategy) {
        this.deadLetterStrategy = deadLetterStrategy;
    }

    public MessageGroupMapFactory getMessageGroupMapFactory() {
        return messageGroupMapFactory;
    }

    public void setMessageGroupMapFactory(MessageGroupMapFactory messageGroupMapFactory) {
        this.messageGroupMapFactory = messageGroupMapFactory;
    }

    public String getName() {
        return getActiveMQDestination().getPhysicalName();
    }

    public PendingMessageCursor getMessages(){
        return this.messages;
    }
    public void setMessages(PendingMessageCursor messages){
        this.messages=messages;
    }

    // Implementation methods
    // -------------------------------------------------------------------------
    private MessageReference createMessageReference(Message message) {
        return new IndirectMessageReference(this, store, message);
    }

    private void dispatch(ConnectionContext context, MessageReference node, Message message) throws Exception {

        dispatchValve.increment();
        MessageEvaluationContext msgContext = context.getMessageEvaluationContext();
        try {
            destinationStatistics.getEnqueues().increment();
      destinationStatistics.getMessages().increment();
            synchronized (messages) {
                messages.addMessageLast(node);
            }

            synchronized (consumers) {
                if (consumers.isEmpty()) {
                    log.debug("No subscriptions registered, will not dispatch message at this time.");
                    return;
                }
            }

            msgContext.setDestination(destination);
            msgContext.setMessageReference(node);

            dispatchPolicy.dispatch(context, node, msgContext, consumers);
        }
        finally {
            msgContext.clear();
            dispatchValve.decrement();
        }
    }

    private int calcHighestSubscriptionPriority() {
        int rc = Integer.MIN_VALUE;
        synchronized (consumers) {
            for (Iterator iter = consumers.iterator(); iter.hasNext();) {
                Subscription sub = (Subscription) iter.next();
                if (sub.getConsumerInfo().getPriority() > rc) {
                    rc = sub.getConsumerInfo().getPriority();
                }
            }
        }
        return rc;
    }

    MessageStore getMessageStore() {
        return store;
    }

    public Message[] browse() {
        ArrayList l = new ArrayList();
        synchronized (messages) {
            messages.reset();
            while(messages.hasNext()) {
                try {
                    MessageReference r = (MessageReference) messages.next();
                    r.incrementReferenceCount();
                    try {
                        Message m = r.getMessage();
                        if (m != null) {
                            l.add(m);
                        }
                    }
                    finally {
                        r.decrementReferenceCount();
                    }
                }
                catch (IOException e) {
                }
            }
        }

        return (Message[]) l.toArray(new Message[l.size()]);
    }

    public Message getMessage(String messageId) {
        synchronized (messages) {
            messages.reset();
            while(messages.hasNext()) {
                try {
                    MessageReference r = (MessageReference) messages.next();
                    if (messageId.equals(r.getMessageId().toString())) {
                        r.incrementReferenceCount();
                        try {
                            Message m = r.getMessage();
                            if (m != null) {
                                return m;
                            }
                        }
                        finally {
                            r.decrementReferenceCount();
                        }
                        break;
                    }
                }
                catch (IOException e) {
                }
            }
        }
        return null;
    }

    public void purge() {
        synchronized (messages) {
            ConnectionContext c = createConnectionContext();
            messages.reset();
            while(messages.hasNext()) {
                try {
                    QueueMessageReference r = (QueueMessageReference) messages.next();

                    // We should only delete messages that can be locked.
                    if (r.lock(LockOwner.HIGH_PRIORITY_LOCK_OWNER)) {
                        MessageAck ack = new MessageAck();
                        ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
                        ack.setDestination(destination);
                        ack.setMessageID(r.getMessageId());
                        acknowledge(c, null, ack, r);
                        r.drop();
                        dropEvent(true);
                    }
                }
                catch (IOException e) {
                }
            }

            // Run gc() by hand. Had we run it in the loop it could be
            // quite expensive.
            gc();
        }
    }
   

    /**
     * Removes the message matching the given messageId
     */
    public boolean removeMessage(String messageId) throws Exception {
        return removeMatchingMessages(createMessageIdFilter(messageId), 1) > 0;
    }

    /**
     * Removes the messages matching the given selector
     *
     * @return the number of messages removed
     */
    public int removeMatchingMessages(String selector) throws Exception {
        return removeMatchingMessages(selector, -1);
    }
   
    /**
     * Removes the messages matching the given selector up to the maximum number of matched messages
     *
     * @return the number of messages removed
     */
    public int removeMatchingMessages(String selector, int maximumMessages) throws Exception {
        return removeMatchingMessages(createSelectorFilter(selector), maximumMessages);
    }

    /**
     * Removes the messages matching the given filter up to the maximum number of matched messages
     *
     * @return the number of messages removed
     */
    public int removeMatchingMessages(MessageReferenceFilter filter, int maximumMessages) throws Exception {
        int counter = 0;
        synchronized (messages) {
            ConnectionContext c = createConnectionContext();
            messages.reset();
            while(messages.hasNext()) {
                IndirectMessageReference r = (IndirectMessageReference) messages.next();
                if (filter.evaluate(c, r)) {
                    // We should only delete messages that can be locked.
                    if (lockMessage(r)) {
                        removeMessage(c, r);
                        if (++counter >= maximumMessages && maximumMessages > 0) {
                            break;
                        }
                    }
                }
            }
        }
        return counter;
    }

    /**
     * Copies the message matching the given messageId
     */
    public boolean copyMessageTo(ConnectionContext context, String messageId, ActiveMQDestination dest) throws Exception {
        return copyMatchingMessages(context, createMessageIdFilter(messageId), dest, 1) > 0;
    }
   
    /**
     * Copies the messages matching the given selector
     *
     * @return the number of messages copied
     */
    public int copyMatchingMessagesTo(ConnectionContext context, String selector, ActiveMQDestination dest) throws Exception {
        return copyMatchingMessagesTo(context, selector, dest, -1);
    }
   
    /**
     * Copies the messages matching the given selector up to the maximum number of matched messages
     *
     * @return the number of messages copied
     */
    public int copyMatchingMessagesTo(ConnectionContext context, String selector, ActiveMQDestination dest, int maximumMessages) throws Exception {
        return copyMatchingMessages(context, createSelectorFilter(selector), dest, maximumMessages);
    }

    /**
     * Copies the messages matching the given filter up to the maximum number of matched messages
     *
     * @return the number of messages copied
     */
    public int copyMatchingMessages(ConnectionContext context, MessageReferenceFilter filter, ActiveMQDestination dest, int maximumMessages) throws Exception {
        int counter = 0;
        synchronized (messages) {
            messages.reset();
            while(messages.hasNext()) {
                MessageReference r = (MessageReference) messages.next();
                if (filter.evaluate(context, r)) {
                    r.incrementReferenceCount();
                    try {
                        Message m = r.getMessage();
                        BrokerSupport.resend(context, m, dest);
                        if (++counter >= maximumMessages && maximumMessages > 0) {
                            break;
                        }
                    }
                    finally {
                        r.decrementReferenceCount();
                    }
                }
            }
        }
        return counter;
    }

    /**
     * Moves the message matching the given messageId
     */
    public boolean moveMessageTo(ConnectionContext context, String messageId, ActiveMQDestination dest) throws Exception {
        return moveMatchingMessagesTo(context, createMessageIdFilter(messageId), dest, 1) > 0;
    }
   
    /**
     * Moves the messages matching the given selector
     *
     * @return the number of messages removed
     */
    public int moveMatchingMessagesTo(ConnectionContext context, String selector, ActiveMQDestination dest) throws Exception {
        return moveMatchingMessagesTo(context, selector, dest, -1);
    }
   
    /**
     * Moves the messages matching the given selector up to the maximum number of matched messages
     */
    public int moveMatchingMessagesTo(ConnectionContext context, String selector, ActiveMQDestination dest, int maximumMessages) throws Exception {
        return moveMatchingMessagesTo(context, createSelectorFilter(selector), dest, maximumMessages);
    }

    /**
     * Moves the messages matching the given filter up to the maximum number of matched messages
     */
    public int moveMatchingMessagesTo(ConnectionContext context, MessageReferenceFilter filter, ActiveMQDestination dest, int maximumMessages) throws Exception {
        int counter = 0;
        synchronized (messages) {
            messages.reset();
            while(messages.hasNext()) {
                IndirectMessageReference r = (IndirectMessageReference) messages.next();
                if (filter.evaluate(context, r)) {
                    // We should only move messages that can be locked.
                    if (lockMessage(r)) {
                        r.incrementReferenceCount();
                        try {
                            Message m = r.getMessage();
                            BrokerSupport.resend(context, m, dest);
                            removeMessage(context, r);
                            if (++counter >= maximumMessages && maximumMessages > 0) {
                                break;
                            }
                        }
                        finally {
                            r.decrementReferenceCount();
                        }
                    }
                }
            }
        }
        return counter;
    }

    protected MessageReferenceFilter createMessageIdFilter(final String messageId) {
        return new MessageReferenceFilter() {
            public boolean evaluate(ConnectionContext context, MessageReference r) {
                return messageId.equals(r.getMessageId().toString());
            }
        };
    }
   
    protected MessageReferenceFilter createSelectorFilter(String selector) throws InvalidSelectorException {
        final BooleanExpression selectorExpression = new SelectorParser().parse(selector);

        return new MessageReferenceFilter() {
            public boolean evaluate(ConnectionContext context, MessageReference r) throws JMSException {
                MessageEvaluationContext messageEvaluationContext = context.getMessageEvaluationContext();
               
                messageEvaluationContext.setMessageReference(r);
                if (messageEvaluationContext.getDestination() == null) {
                    messageEvaluationContext.setDestination(getActiveMQDestination());
                }
               
                return selectorExpression.matches(messageEvaluationContext);
            }
        };
    }

    protected void removeMessage(ConnectionContext c, IndirectMessageReference r) throws IOException {
        MessageAck ack = new MessageAck();
        ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
        ack.setDestination(destination);
        ack.setMessageID(r.getMessageId());
        acknowledge(c, null, ack, r);
        r.drop();
        dropEvent();
    }

    protected boolean lockMessage(IndirectMessageReference r) {
        return r.lock(LockOwner.HIGH_PRIORITY_LOCK_OWNER);
    }

    protected ConnectionContext createConnectionContext() {
        ConnectionContext answer = new ConnectionContext();
        answer.getMessageEvaluationContext().setDestination(getActiveMQDestination());
        return answer;
    }
}
TOP

Related Classes of org.apache.activemq.broker.region.Queue

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.