/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed 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.springframework.integration.channel;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.store.MessageGroupQueue;
import org.springframework.integration.store.PriorityCapableChannelMessageStore;
import org.springframework.integration.util.UpperBound;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
/**
* A message channel that prioritizes messages based on a {@link Comparator}.
* The default comparator is based upon the message header's 'priority'.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class PriorityChannel extends QueueChannel {
private final UpperBound upperBound;
private final AtomicLong sequenceCounter = new AtomicLong();
private final boolean useMessageStore;
/**
* Create a channel with an unbounded queue. Message priority will be
* based on the value of {@link IntegrationMessageHeaderAccessor#getPriority()}.
*/
public PriorityChannel() {
this(0, null);
}
/**
* Create a channel with the specified queue capacity. Message priority
* will be based upon the value of {@link IntegrationMessageHeaderAccessor#getPriority()}.
*
* @param capacity The queue capacity.
*/
public PriorityChannel(int capacity) {
this(capacity, null);
}
/**
* Create a channel with an unbounded queue. Message priority will be
* determined by the provided {@link Comparator}. If the comparator
* is <code>null</code>, the priority will be based upon the value of
* {@link IntegrationMessageHeaderAccessor#getPriority()}.
*
* @param comparator The comparator.
*/
public PriorityChannel(Comparator<Message<?>> comparator) {
this(0, comparator);
}
/**
* Create a channel with the specified queue capacity. If the capacity
* is a non-positive value, the queue will be unbounded. Message priority
* will be determined by the provided {@link Comparator}. If the comparator
* is <code>null</code>, the priority will be based upon the value of
* {@link IntegrationMessageHeaderAccessor#getPriority()}.
*
* @param capacity The capacity.
* @param comparator The comparator.
*/
public PriorityChannel(int capacity, Comparator<Message<?>> comparator) {
super(new PriorityBlockingQueue<>(11, new SequenceFallbackComparator(comparator)));
this.upperBound = new UpperBound(capacity);
this.useMessageStore = false;
}
/**
* Create a channel based on the provided {@link PriorityCapableChannelMessageStore}
* and group id for message store operations.
* @param messageGroupStore the {@link PriorityCapableChannelMessageStore} to use.
* @param groupId to group message for this channel in the message store.
* @since 5.0
*/
public PriorityChannel(PriorityCapableChannelMessageStore messageGroupStore, Object groupId) {
this(new MessageGroupQueue(messageGroupStore, groupId));
}
/**
* Create a channel based on the provided {@link MessageGroupQueue}.
* @param messageGroupQueue the {@link MessageGroupQueue} to use.
* @since 5.0
*/
public PriorityChannel(MessageGroupQueue messageGroupQueue) {
super(messageGroupQueue);
this.upperBound = new UpperBound(0);
this.useMessageStore = true;
}
@Override
public int getRemainingCapacity() {
return this.upperBound.availablePermits();
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (!this.upperBound.tryAcquire(timeout)) {
return false;
}
if (!this.useMessageStore) {
message = new MessageWrapper(message);
}
return super.doSend(message, 0);
}
@Override
protected Message<?> doReceive(long timeout) {
Message<?> message = super.doReceive(timeout);
if (message != null) {
if (!this.useMessageStore) {
message = ((MessageWrapper) message).getRootMessage();
}
this.upperBound.release();
}
return message;
}
private static final class SequenceFallbackComparator implements Comparator<Message<?>> {
private final Comparator<Message<?>> targetComparator;
SequenceFallbackComparator(Comparator<Message<?>> targetComparator) {
this.targetComparator = targetComparator;
}
@Override
public int compare(Message<?> message1, Message<?> message2) {
int compareResult = 0;
if (this.targetComparator != null) {
compareResult = this.targetComparator.compare(message1, message2);
}
else {
Integer priority1 = new IntegrationMessageHeaderAccessor(message1).getPriority();
Integer priority2 = new IntegrationMessageHeaderAccessor(message2).getPriority();
priority1 = priority1 != null ? priority1 : 0;
priority2 = priority2 != null ? priority2 : 0;
compareResult = priority2.compareTo(priority1);
}
if (compareResult == 0) {
Long sequence1 = ((MessageWrapper) message1).getSequence();
Long sequence2 = ((MessageWrapper) message2).getSequence();
compareResult = sequence1.compareTo(sequence2);
}
return compareResult;
}
}
//we need this because of INT-2508
private final class MessageWrapper implements Message<Object> {
private final Message<?> rootMessage;
private final long sequence;
MessageWrapper(Message<?> rootMessage) {
this.rootMessage = rootMessage;
this.sequence = PriorityChannel.this.sequenceCounter.incrementAndGet();
}
public Message<?> getRootMessage() {
return this.rootMessage;
}
@Override
public MessageHeaders getHeaders() {
return this.rootMessage.getHeaders();
}
@Override
public Object getPayload() {
return this.rootMessage.getPayload();
}
long getSequence() {
return this.sequence;
}
}
}