/** * 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.cursors; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.activemq.broker.region.MessageReference; import org.apache.activemq.command.MessageId; /** * An abstraction that keeps the correct order of messages that need to be dispatched * to consumers, but also hides the fact that there might be redelivered messages that * should be dispatched ahead of any other paged in messages. * * Direct usage of this class is recommended as you can control when redeliveries need * to be added vs regular pending messages (the next set of messages that can be dispatched) * * Created by ceposta * <a href="http://christianposta.com/blog>http://christianposta.com/blog</a>. */ public class QueueDispatchPendingList implements PendingList { private PendingList pagedInPendingDispatch = new OrderedPendingList(); private PendingList redeliveredWaitingDispatch = new OrderedPendingList(); private boolean prioritized = false; @Override public boolean isEmpty() { return pagedInPendingDispatch.isEmpty() && redeliveredWaitingDispatch.isEmpty(); } @Override public void clear() { pagedInPendingDispatch.clear(); redeliveredWaitingDispatch.clear(); } /** * Messages added are added directly to the pagedInPendingDispatch set of messages. If * you're trying to add a message that is marked redelivered add it using addMessageForRedelivery() * method * @param message * The MessageReference that is to be added to this list. * * @return the pending node. */ @Override public PendingNode addMessageFirst(MessageReference message) { return pagedInPendingDispatch.addMessageFirst(message); } /** * Messages added are added directly to the pagedInPendingDispatch set of messages. If * you're trying to add a message that is marked redelivered add it using addMessageForRedelivery() * method * @param message * The MessageReference that is to be added to this list. * * @return the pending node. */ @Override public PendingNode addMessageLast(MessageReference message) { return pagedInPendingDispatch.addMessageLast(message); } @Override public PendingNode remove(MessageReference message) { if (pagedInPendingDispatch.contains(message)) { return pagedInPendingDispatch.remove(message); } else if (redeliveredWaitingDispatch.contains(message)) { return redeliveredWaitingDispatch.remove(message); } return null; } @Override public int size() { return pagedInPendingDispatch.size() + redeliveredWaitingDispatch.size(); } @Override public long messageSize() { return pagedInPendingDispatch.messageSize() + redeliveredWaitingDispatch.messageSize(); } @Override public Iterator<MessageReference> iterator() { if (prioritized && hasRedeliveries()) { final QueueDispatchPendingList delegate = this; final PrioritizedPendingList priorityOrderedRedeliveredAndPending = new PrioritizedPendingList(); priorityOrderedRedeliveredAndPending.addAll(redeliveredWaitingDispatch); priorityOrderedRedeliveredAndPending.addAll(pagedInPendingDispatch); return new Iterator<MessageReference>() { Iterator<MessageReference> combinedIterator = priorityOrderedRedeliveredAndPending.iterator(); MessageReference current = null; @Override public boolean hasNext() { return combinedIterator.hasNext(); } @Override public MessageReference next() { current = combinedIterator.next(); return current; } @Override public void remove() { if (current!=null) { delegate.remove(current); } } }; } else { return new Iterator<MessageReference>() { Iterator<MessageReference> redeliveries = redeliveredWaitingDispatch.iterator(); Iterator<MessageReference> pendingDispatch = pagedInPendingDispatch.iterator(); Iterator<MessageReference> current = redeliveries; @Override public boolean hasNext() { if (!redeliveries.hasNext() && (current == redeliveries)) { current = pendingDispatch; } return current.hasNext(); } @Override public MessageReference next() { return current.next(); } @Override public void remove() { current.remove(); } }; } } @Override public boolean contains(MessageReference message) { return pagedInPendingDispatch.contains(message) || redeliveredWaitingDispatch.contains(message); } @Override public Collection<MessageReference> values() { List<MessageReference> messageReferences = new ArrayList<MessageReference>(); Iterator<MessageReference> iterator = iterator(); while (iterator.hasNext()) { messageReferences.add(iterator.next()); } return messageReferences; } @Override public void addAll(PendingList pendingList) { pagedInPendingDispatch.addAll(pendingList); } @Override public MessageReference get(MessageId messageId) { MessageReference rc = pagedInPendingDispatch.get(messageId); if (rc == null) { return redeliveredWaitingDispatch.get(messageId); } return rc; } public void setPrioritizedMessages(boolean prioritizedMessages) { prioritized = prioritizedMessages; if (prioritizedMessages && this.pagedInPendingDispatch instanceof OrderedPendingList) { pagedInPendingDispatch = new PrioritizedPendingList(); redeliveredWaitingDispatch = new PrioritizedPendingList(); } else if(pagedInPendingDispatch instanceof PrioritizedPendingList) { pagedInPendingDispatch = new OrderedPendingList(); redeliveredWaitingDispatch = new OrderedPendingList(); } } public boolean hasRedeliveries(){ return !redeliveredWaitingDispatch.isEmpty(); } public void addForRedelivery(List<MessageReference> list, boolean noConsumers) { if (noConsumers && redeliveredWaitingDispatch instanceof OrderedPendingList && willBeInOrder(list)) { // a single consumer can expect repeatable redelivery order irrespective // of transaction or prefetch boundaries ((OrderedPendingList)redeliveredWaitingDispatch).insertAtHead(list); } else { for (MessageReference ref : list) { redeliveredWaitingDispatch.addMessageLast(ref); } } } private boolean willBeInOrder(List<MessageReference> list) { // for a single consumer inserting at head will be in order w.r.t brokerSequence but // will not be if there were multiple consumers in the mix even if this is the last // consumer to close (noConsumers==true) return !redeliveredWaitingDispatch.isEmpty() && list != null && !list.isEmpty() && redeliveredWaitingDispatch.iterator().next().getMessageId().getBrokerSequenceId() > list.get(list.size() - 1).getMessageId().getBrokerSequenceId(); } }