INT-330:
- Polished some javadoc - removed unnecessary dependency on MessageStore from MessageGroup - added locking in pruner INT-971: disabled test from CronTriggerTests to avoid build breakage pending fix.
This commit is contained in:
@@ -34,9 +34,16 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* MessageHandler that holds a buffer of messages in a MessageStore. This class takes care of
|
||||
* correlated groups of messages that can be completed in batches. It is useful for aggregating,
|
||||
* resequencing, or custom implementations requiring correlation.
|
||||
* MessageHandler that holds a buffer of correlated messages in a MessageStore. This class takes care of correlated
|
||||
* groups of messages that can be completed in batches. It is useful for aggregating, resequencing, or custom
|
||||
* implementations requiring correlation.
|
||||
* <p/>
|
||||
* To customize this handler inject {@link org.springframework.integration.aggregator.CorrelationStrategy}, {@link
|
||||
* org.springframework.integration.aggregator.CompletionStrategy} and {@link org.springframework.integration.aggregator.MessageGroupProcessor}
|
||||
* implementations as you require.
|
||||
* <p/>
|
||||
* By default the CorrelationStrategy will be a HeaderAttributeCorrelationStrategy and the CompletionStrategy will be a
|
||||
* SequenceSizeCompletionStrategy.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
@@ -115,9 +122,10 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
Object correlationKey = correlationStrategy.getCorrelationKey(message);
|
||||
try {
|
||||
if (tracker.aquireLockFor(correlationKey)) {
|
||||
if (tracker.waitForLockIfNotTracked(correlationKey)) {
|
||||
MessageGroup group =
|
||||
new MessageGroup(store, completionStrategy, correlationKey, deleteOrTrackCallback());
|
||||
new MessageGroup(store.list(correlationKey),
|
||||
completionStrategy, correlationKey, deleteOrTrackCallback());
|
||||
if (group.hasNoMessageSuperseding(message)) {
|
||||
store(message, correlationKey);
|
||||
group.add(message);
|
||||
@@ -146,7 +154,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
}
|
||||
|
||||
public void onCompletionOf(Object correlationKey) {
|
||||
tracker.pushCorrellationId(correlationKey);
|
||||
tracker.pushCorrelationId(correlationKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -195,7 +203,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(this + "'s PrunerTask is processing " + key);
|
||||
}
|
||||
forceComplete(key);
|
||||
if (!forceComplete(key)) {
|
||||
keysInBuffer.offer(delayedKey);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -204,34 +214,42 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
}
|
||||
}
|
||||
|
||||
protected final void forceComplete(Object key) {
|
||||
MessageGroup group = new MessageGroup(store, completionStrategy, key, deleteOrTrackCallback());
|
||||
List<Message<?>> all = store.list(key);
|
||||
if (all.size() > 0) {
|
||||
//last chance for normal completion
|
||||
MessageChannel outputChannel = resolveReplyChannel(all.get(0), this.outputChannel, this.channelResolver);
|
||||
boolean processed = false;
|
||||
if (completionStrategy.isComplete(all)) {
|
||||
outputProcessor.processAndSend(group, outputChannel);
|
||||
processed = true;
|
||||
}
|
||||
boolean fullyCompleted = processed;
|
||||
if (!fullyCompleted) {
|
||||
if (sendPartialResultOnTimeout) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Processing partially complete messages for key [" + key + "] to: " + outputChannel);
|
||||
protected final boolean forceComplete(Object key) {
|
||||
try {
|
||||
if (tracker.tryLockFor(key)) {
|
||||
List<Message<?>> all = store.list(key);
|
||||
MessageGroup group = new MessageGroup(all, completionStrategy, key, deleteOrTrackCallback());
|
||||
if (all.size() > 0) {
|
||||
//last chance for normal completion
|
||||
MessageChannel outputChannel = resolveReplyChannel(all.get(0), this.outputChannel, this.channelResolver);
|
||||
boolean processed = false;
|
||||
if (group.isComplete()) {
|
||||
outputProcessor.processAndSend(group, outputChannel);
|
||||
processed = true;
|
||||
}
|
||||
outputProcessor.processAndSend(group, outputChannel);
|
||||
} else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discarding partially complete messages for key [" + key + "] to: " + discardChannel);
|
||||
}
|
||||
for (Message<?> message : all) {
|
||||
discardChannel.send(message);
|
||||
store.delete(message.getHeaders().getId());
|
||||
if (!processed) {
|
||||
if (sendPartialResultOnTimeout) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Processing partially complete messages for key [" + key + "] to: " + outputChannel);
|
||||
}
|
||||
outputProcessor.processAndSend(group, outputChannel);
|
||||
} else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discarding partially complete messages for key [" + key + "] to: " + discardChannel);
|
||||
}
|
||||
for (Message<?> message : all) {
|
||||
discardChannel.send(message);
|
||||
store.delete(message.getHeaders().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
tracker.unlock(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,20 +280,27 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
|
||||
private final class IdTracker {
|
||||
private ConcurrentMap<Object, ReentrantLock> trackerLocks = new ConcurrentHashMap<Object, ReentrantLock>();
|
||||
private final Queue<Object> trackedCorrellationIds = new LinkedBlockingQueue<Object>();
|
||||
private final Queue<Object> trackedCorrelationIds = new LinkedBlockingQueue<Object>();
|
||||
|
||||
private void pushCorrellationId(Object correlationKey) {
|
||||
while (!trackedCorrellationIds.offer(correlationKey)) {
|
||||
private void pushCorrelationId(Object correlationKey) {
|
||||
while (!trackedCorrelationIds.offer(correlationKey)) {
|
||||
//make room in the queue
|
||||
trackedCorrellationIds.poll();
|
||||
trackedCorrelationIds.poll();
|
||||
}
|
||||
trackerLocks.remove(correlationKey);
|
||||
}
|
||||
|
||||
private boolean aquireLockFor(Object correlationKey) {
|
||||
/**
|
||||
* Call this method to check if an id is tracked and obtain a lock for it. Don't forget to finally unlock
|
||||
* afterwards.
|
||||
*
|
||||
* @return false if the key was tracked, true after obtaining the lock otherwise.
|
||||
*/
|
||||
private boolean waitForLockIfNotTracked(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock == null) {
|
||||
if (trackedCorrellationIds.contains(correlationKey)) {
|
||||
if (trackedCorrelationIds.contains(correlationKey)) {
|
||||
//this correlation key is already processed in the near past: disallow processing
|
||||
return false;
|
||||
}
|
||||
lock = new ReentrantLock();
|
||||
@@ -286,6 +311,16 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tryLockFor(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock == null) {
|
||||
lock = new ReentrantLock();
|
||||
ReentrantLock original = trackerLocks.putIfAbsent(correlationKey, lock);
|
||||
lock = original == null ? lock : original;
|
||||
}
|
||||
return lock.tryLock();
|
||||
}
|
||||
|
||||
private void unlock(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock != null && lock.isHeldByCurrentThread()) {
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.store.MessageStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents a group of correlated messages that is bound to a certain
|
||||
* {@link org.springframework.integration.store.MessageStore} and correlation key.
|
||||
* The group can grow during its lifetime, if messages are <code>add</code>ed to it.
|
||||
* Represents an mutable group of correlated messages that is bound to a certain {@link org.springframework.integration.store.MessageStore}
|
||||
* and correlation key. The group will grow during its lifetime, when messages are <code>add</code>ed to it. <strong>
|
||||
* This is not thread safe and should not be used for long running aggregations</strong>.
|
||||
* <p/>
|
||||
* According to its {@link org.springframework.integration.aggregator.CompletionStrategy} it
|
||||
* can be <i>complete</i> depending on the messages in the group.
|
||||
* According to its {@link org.springframework.integration.aggregator.CompletionStrategy} it can be <i>complete</i>
|
||||
* depending on the messages in the group.
|
||||
* <p/>
|
||||
* Listeners can be configured to get callbacks when (parts of) the group are processed or the whole group is completed.
|
||||
* Optionally MessageGroupListeners can be added to get callbacks when (parts of) the group are processed or the whole
|
||||
* group is completed.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class MessageGroup {
|
||||
private final MessageStore store;
|
||||
private final CompletionStrategy completionStrategy;
|
||||
private final Object correlationKey;
|
||||
private final ArrayList<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
private final List<MessageGroupListener> listeners;
|
||||
|
||||
|
||||
public MessageGroup(MessageStore store, CompletionStrategy completionStrategy, Object correlationKey, MessageGroupListener... listeners) {
|
||||
this.store = store;
|
||||
public MessageGroup(Collection<? extends Message<?>> originalMessages, CompletionStrategy completionStrategy, Object correlationKey,
|
||||
MessageGroupListener... listeners) {
|
||||
this.completionStrategy = completionStrategy;
|
||||
this.correlationKey = correlationKey;
|
||||
this.messages.addAll(store.list(correlationKey));
|
||||
this.messages.addAll(originalMessages);
|
||||
this.listeners = Collections.unmodifiableList(Arrays.asList(listeners));
|
||||
}
|
||||
|
||||
@@ -53,10 +51,6 @@ public class MessageGroup {
|
||||
return completionStrategy.isComplete(messages);
|
||||
}
|
||||
|
||||
public static MessageGroupBuilder builder() {
|
||||
return new MessageGroupBuilder();
|
||||
}
|
||||
|
||||
public List<Message<?>> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
@@ -76,35 +70,4 @@ public class MessageGroup {
|
||||
listener.onCompletionOf(correlationKey);
|
||||
}
|
||||
}
|
||||
|
||||
final static class MessageGroupBuilder {
|
||||
private MessageStore store;
|
||||
private Object correlationKey;
|
||||
private MessageGroupListener[] listeners;
|
||||
private CompletionStrategy completionStrategy;
|
||||
|
||||
public MessageGroupBuilder withCorrelationKey(Object key) {
|
||||
this.correlationKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageGroupBuilder withStore(MessageStore store) {
|
||||
this.store = store;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageGroupBuilder observedBy(MessageGroupListener... listeners) {
|
||||
this.listeners = listeners;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageGroupBuilder completedBy(CompletionStrategy completionStrategy) {
|
||||
this.completionStrategy = completionStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageGroup build() {
|
||||
return new MessageGroup(store, completionStrategy, correlationKey, listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user