INT-1105, INT-1063: Big merge. Removing old aggregation and correlation stuff, and reaping the reaper.

This commit is contained in:
David Syer
2010-05-02 04:31:05 +00:00
parent ec76ea497a
commit d04c7008b6
58 changed files with 1658 additions and 3797 deletions

View File

@@ -64,7 +64,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
protected Map<String, Object> aggregateHeaders(MessageGroup group) {
Map<String, Object> aggregatedHeaders = new HashMap<String, Object>();
Set<String> conflictKeys = new HashSet<String>();
for (Message<?> message : group.getMessages()) {
for (Message<?> message : group.getUnmarked()) {
MessageHeaders currentHeaders = message.getHeaders();
for (String key : currentHeaders.keySet()) {
if (MessageHeaders.ID.equals(key) || MessageHeaders.TIMESTAMP.equals(key)

View File

@@ -1,95 +0,0 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.ArrayList;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A base class for aggregating a group of Messages into a single Message.
* Extends {@link AbstractMessageBarrierHandler} and waits for a
* <em>complete</em> group of {@link Message Messages} to arrive. Subclasses
* must provide the implementation of the {@link #aggregateMessages(List)}
* method to combine the group of Messages into a single {@link Message}.
*
* <p>
* The default strategy for determining whether a group is complete is based on
* the '<code>sequenceSize</code>' property of the header. Alternatively, a
* custom implementation of the {@link CompletionStrategy} may be provided.
*
* <p>
* All considerations regarding <code>timeout</code> and grouping by
* <code>correlationId</code> from {@link AbstractMessageBarrierHandler} apply
* here as well.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public abstract class AbstractMessageAggregator extends
AbstractMessageBarrierHandler<List<Message<?>>> {
private volatile CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
/**
* Strategy to determine whether the group of messages is complete.
*/
public void setCompletionStrategy(CompletionStrategy completionStrategy) {
Assert.notNull(completionStrategy,
"'completionStrategy' must not be null");
this.completionStrategy = completionStrategy;
}
@Override
public String getComponentType() {
return "aggregator";
}
@Override
protected MessageBarrier<List<Message<?>>> createMessageBarrier(Object correlationKey) {
return new MessageBarrier<List<Message<?>>>(new ArrayList<Message<?>>(), correlationKey);
}
@Override
protected void processBarrier(MessageBarrier<List<Message<?>>> barrier) {
if (!barrier.isComplete() && !CollectionUtils.isEmpty(barrier.getMessages())) {
if (this.completionStrategy.isComplete(barrier.getMessages())) {
barrier.setComplete();
}
}
if (barrier.isComplete()) {
this.removeBarrier(barrier.getCorrelationKey());
Message<?> result = this.aggregateMessages(barrier.getMessages());
if (result != null) {
if (result.getHeaders().getCorrelationId() == null) {
result = MessageBuilder.fromMessage(result)
.setCorrelationId(barrier.getCorrelationKey())
.build();
}
this.sendReply(result, this.resolveReplyChannelFromMessage(barrier.getMessages().get(0)));
}
}
}
protected abstract Message<?> aggregateMessages(List<Message<?>> messages);
}

View File

@@ -1,397 +0,0 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* Base class for {@link MessageBarrier}-based Message Handlers. A
* {@link MessageHandler} implementation that waits for a group of
* {@link Message Messages} to arrive and processes them together. Uses a
* {@link MessageBarrier} to store messages and to decide how the messages
* should be released.
* <p>
* Each {@link Message} that is received by this handler will be associated
* with a group based upon the '<code>correlationId</code>' property of its
* header. If no such property is available, a {@link MessageHandlingException}
* will be thrown.
* <p>
* The '<code>timeout</code>' value determines how long to wait for the complete
* group after the arrival of the first {@link Message} of the group. The
* default value is 1 minute. If the timeout elapses prior to completion, then
* Messages with that timed-out 'correlationId' will be sent to the
* 'discardChannel' if provided unless 'sendPartialResultsOnTimeout' is set to
* true in which case the incomplete group will be sent to the output channel.
* <p>
* Subclasses must decide what kind of a Collection they want to use. The semantics
* of adding a Message to the MessageBarrier will be decided by the Collection type.
* <p>
* Note: this class is not part of the Spring Integration API, but
* an internal class, used for implementing components that need to keep
* a list of messages until they are ready to be released or processed
* (e.g. Resequencer or Aggregator). As such it is subject to change in future
* versions.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public abstract class AbstractMessageBarrierHandler<T extends Collection<? extends Message<?>>>
extends AbstractMessageHandler implements MessageProducer, BeanFactoryAware, InitializingBean {
public final static long DEFAULT_SEND_TIMEOUT = 1000;
public final static long DEFAULT_TIMEOUT = 60000;
public final static long DEFAULT_REAPER_INTERVAL = 1000;
public final static int DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY = 1000;
protected final Log logger = LogFactory.getLog(this.getClass());
private volatile MessageChannel outputChannel;
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
private volatile MessageChannel discardChannel;
protected final ConcurrentMap<Object, MessageBarrier<T>> barriers = new ConcurrentHashMap<Object, MessageBarrier<T>>();
private volatile long timeout = DEFAULT_TIMEOUT;
private volatile boolean sendPartialResultOnTimeout = false;
private volatile long reaperInterval = DEFAULT_REAPER_INTERVAL;
private volatile int trackedCorrelationIdCapacity = DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY;
protected volatile BlockingQueue<Object> trackedCorrelationIds;
private volatile boolean autoStartup = true;
private volatile ScheduledFuture<?> reaperFutureTask;
private volatile boolean initialized;
private final Object lifecycleMonitor = new Object();
private volatile CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID);
public AbstractMessageBarrierHandler() {
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
/**
* Specify a channel for sending Messages that arrive after their
* aggregation group has either completed or timed-out.
*/
public void setDiscardChannel(MessageChannel discardChannel) {
this.discardChannel = discardChannel;
}
/**
* Specify whether to aggregate and send the resulting Message when the
* timeout elapses prior to the CompletionStrategy returning true.
*/
public void setSendPartialResultOnTimeout(boolean sendPartialResultOnTimeout) {
this.sendPartialResultOnTimeout = sendPartialResultOnTimeout;
}
/**
* Set the interval in milliseconds for the reaper thread. Default is 1000.
*/
public void setReaperInterval(long reaperInterval) {
Assert.isTrue(reaperInterval > 0, "'reaperInterval' must be a positive value");
this.reaperInterval = reaperInterval;
}
/**
* Set the number of completed correlationIds to track. Default is 1000.
*/
public void setTrackedCorrelationIdCapacity(int trackedCorrelationIdCapacity) {
this.trackedCorrelationIdCapacity = trackedCorrelationIdCapacity;
}
/**
* Maximum time to wait (in milliseconds) for the completion strategy to
* become true. The default is 60000 (1 minute).
*/
public void setTimeout(long timeout) {
Assert.isTrue(timeout >= 0, "'timeout' must be a positive value");
this.timeout = timeout;
}
public void setSendTimeout(long sendTimeout) {
this.channelTemplate.setSendTimeout(sendTimeout);
}
public void setTaskScheduler(TaskScheduler taskScheduler) {
super.setTaskScheduler(taskScheduler);
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) {
this.correlationStrategy = correlationStrategy;
}
@Override
public final void onInit() {
synchronized (this.lifecycleMonitor) {
if (!this.initialized) {
if (this.trackedCorrelationIdCapacity > 0) {
this.trackedCorrelationIds = new ArrayBlockingQueue<Object>(this.trackedCorrelationIdCapacity);
}
if (this.autoStartup) {
this.start();
}
this.initialized = true;
}
}
}
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.reaperFutureTask != null;
}
}
public void start() {
synchronized (this.lifecycleMonitor) {
if (this.isRunning()) {
return;
}
Assert.state(this.getTaskScheduler() != null, "TaskScheduler must not be null");
this.reaperFutureTask = this.getTaskScheduler().scheduleWithFixedDelay(
new PrunerTask(), this.reaperInterval);
}
}
public void stop() {
synchronized (this.lifecycleMonitor) {
if (this.isRunning()) {
this.reaperFutureTask.cancel(true);
}
}
}
@Override
protected final void handleMessageInternal(Message<?> message) {
if (!this.initialized) {
this.afterPropertiesSet();
}
Object correlationKey = this.correlationStrategy.getCorrelationKey(message);
if (correlationKey == null) {
throw new MessageHandlingException(message, this.getClass().getSimpleName()
+ " requires the 'correlationKey' property");
}
if (this.trackedCorrelationIds != null && this.trackedCorrelationIds.contains(correlationKey)) {
if (logger.isDebugEnabled()) {
logger.debug("Handling of Message group with correlationKey '" + correlationKey
+ "' has already completed or timed out.");
}
this.discardMessage(message);
}
else {
this.processMessage(message, correlationKey);
}
}
private void discardMessage(Message<?> message) {
if (this.discardChannel != null) {
boolean sent = this.channelTemplate.send(message, this.discardChannel);
if (!sent && logger.isWarnEnabled()) {
logger.warn("unable to send to 'discardChannel', message: " + message);
}
}
}
@SuppressWarnings("unchecked")
private void processMessage(Message<?> message, Object correlationKey) {
MessageBarrier<T> barrier = barriers.putIfAbsent(correlationKey, createMessageBarrier(correlationKey));
if (barrier == null) {
barrier = barriers.get(correlationKey);
}
synchronized (barrier) {
if (canAddMessage(message, barrier)) {
((MessageBarrier)barrier).getMessages().add(message);
}
processBarrier(barrier);
}
}
protected final void sendReplies(Collection<Message<?>> messages, MessageChannel defaultReplyChannel) {
if (messages.isEmpty()) {
return;
}
for (Message<?> result : messages) {
sendReply(result, defaultReplyChannel);
}
}
protected final void sendReply(Message<?> message, MessageChannel defaultReplyChannel) {
MessageChannel replyChannel = this.outputChannel;
if (replyChannel == null) {
replyChannel = this.resolveReplyChannelFromMessage(message);
if (replyChannel == null) {
replyChannel = defaultReplyChannel;
}
}
if (replyChannel != null) {
if (defaultReplyChannel != null && !defaultReplyChannel.equals(replyChannel)) {
message = MessageBuilder.fromMessage(message)
.setHeaderIfAbsent(MessageHeaders.REPLY_CHANNEL, defaultReplyChannel)
.build();
}
if (!this.channelTemplate.send(message, replyChannel)) {
throw new MessageDeliveryException(message, "failed to send reply Message");
}
}
else if (logger.isWarnEnabled()) {
logger.warn("unable to determine reply target for aggregation result: " + message);
}
}
protected final MessageChannel resolveReplyChannelFromMessage(Message<?> message) {
Object replyChannel = message.getHeaders().getReplyChannel();
if (replyChannel != null) {
if (replyChannel instanceof MessageChannel) {
return (MessageChannel) replyChannel;
}
if (logger.isWarnEnabled()) {
logger.warn("Aggregator can only reply to a 'replyChannel' of type MessageChannel.");
}
}
return null;
}
protected final void removeBarrier(Object correlationId) {
if (this.barriers.remove(correlationId) != null
&& this.trackedCorrelationIds != null) {
synchronized (this.trackedCorrelationIds) {
boolean added = this.trackedCorrelationIds.offer(correlationId);
if (!added) {
this.trackedCorrelationIds.poll();
this.trackedCorrelationIds.offer(correlationId);
}
}
}
}
/**
* Verifies that a message can be added to the barrier. To be overridden by subclasses, which may add
* their own verifications. Subclasses overriding this method must call the method from the superclass.
*/
protected boolean canAddMessage(Message<?> message, MessageBarrier<T> barrier) {
if (barrier.isComplete()) {
if (logger.isDebugEnabled()) {
logger.debug("Message received after aggregation has already completed: " + message);
}
return false;
}
return true;
}
/**
* Factory method for creating a MessageBarrier implementation.
*/
protected abstract MessageBarrier<T> createMessageBarrier(Object correlationKey);
/**
* A method for processing the information in the message barrier after a message has been added or on pruning.
* The decision as to whether the messages from the {@link MessageBarrier}
* can be released normally belongs here, although calling code may forcibly set the MessageBarrier's 'complete'
* flag to true before invoking the method.
* @param barrier the {@link MessageBarrier} to be processed
*/
protected abstract void processBarrier(MessageBarrier<T> barrier);
/**
* A method for discarding the content of the message barrier.
* Can be overridden by subclasses.
* @param entry
* @param barrier
*/
protected void discardBarrier(MessageBarrier<T> barrier) {
for (Message<?> message : barrier.getMessages()) {
if (logger.isDebugEnabled()) {
logger.debug("Handling of Message group with correlation key '" + barrier.getCorrelationKey()+ "' has timed out.");
}
discardMessage(message);
}
}
/**
* A task that runs periodically, pruning the timed-out message barriers.
*/
private class PrunerTask implements Runnable {
public void run() {
long currentTime = System.currentTimeMillis();
for (Map.Entry<Object, MessageBarrier<T>> entry : barriers.entrySet()) {
if (currentTime - entry.getValue().getTimestamp() >= timeout) {
MessageBarrier<T> barrier = entry.getValue();
synchronized (barrier) {
removeBarrier(entry.getKey());
if (sendPartialResultOnTimeout) {
barrier.setComplete();
processBarrier(barrier);
}
else {
discardBarrier(barrier);
}
}
}
}
}
}
}

View File

@@ -1,36 +1,24 @@
/*
* Copyright 2002-2010 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.
*
* 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.aggregator;
import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.channel.NullChannel;
@@ -46,33 +34,29 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* 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
* 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.
* To customize this handler inject {@link CorrelationStrategy}, {@link ReleaseStrategy}, and
* {@link MessageGroupProcessor} implementations as you require.
* <p/>
* By default the CorrelationStrategy will be a
* HeaderAttributeCorrelationStrategy and the CompletionStrategy will be a
* SequenceSizeCompletionStrategy.
* By default the CorrelationStrategy will be a HeaderAttributeCorrelationStrategy and the ReleaseStrategy will be a
* SequenceSizeReleaseStrategy.
*
* @author Iwein Fuld
* @author Dave Syer
* @since 2.0
*/
public class CorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer, Lifecycle {
public class CorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer {
private static final Log logger = LogFactory.getLog(CorrelatingMessageHandler.class);
private static final long DEFAULT_SEND_TIMEOUT = 1000L;
public static final long DEFAULT_SEND_TIMEOUT = 1000L;
private static final long DEFAULT_REAPER_INTERVAL = 1000L;
public static final long DEFAULT_REAPER_INTERVAL = 1000L;
private static final long DEFAULT_TIMEOUT = 60000L;
public static final long DEFAULT_TIMEOUT = 60000L;
private final MessageStore store;
@@ -81,7 +65,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
private volatile CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(
MessageHeaders.CORRELATION_ID);
private volatile CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
private volatile ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
private MessageChannel outputChannel;
@@ -89,41 +73,29 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
private volatile MessageChannel discardChannel = new NullChannel();
private final IdTracker tracker = new IdTracker();
private boolean sendPartialResultOnTimeout = false;
private final BlockingQueue<DelayedKey> keysInBuffer = new DelayQueue<DelayedKey>();
private volatile ScheduledFuture<?> reaperFutureTask;
private volatile long reaperInterval = DEFAULT_REAPER_INTERVAL;
private volatile long timeout = DEFAULT_TIMEOUT;
private volatile boolean sendPartialResultOnTimeout;
private final Object lifecycleMonitor = new Object();
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
public CorrelatingMessageHandler(MessageStore store, CorrelationStrategy correlationStrategy,
CompletionStrategy completionStrategy, MessageGroupProcessor processor) {
ReleaseStrategy ReleaseStrategy, MessageGroupProcessor processor) {
Assert.notNull(store);
Assert.notNull(processor);
Assert.notNull(correlationStrategy);
Assert.notNull(completionStrategy);
this.store = store;
this.outputProcessor = processor;
this.correlationStrategy = correlationStrategy;
this.completionStrategy = completionStrategy;
this.correlationStrategy = correlationStrategy == null ? new HeaderAttributeCorrelationStrategy(
MessageHeaders.CORRELATION_ID) : correlationStrategy;
this.ReleaseStrategy = ReleaseStrategy == null ? new SequenceSizeReleaseStrategy() : ReleaseStrategy;
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public CorrelatingMessageHandler(MessageStore store, MessageGroupProcessor processor) {
this(store, new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
new SequenceSizeCompletionStrategy(), processor);
this(store, null, null, processor);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor) {
this(new SimpleMessageStore(0), new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
new SequenceSizeCompletionStrategy(), processor);
new SequenceSizeReleaseStrategy(), processor);
}
public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) {
@@ -131,9 +103,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
this.correlationStrategy = correlationStrategy;
}
public void setCompletionStrategy(CompletionStrategy completionStrategy) {
Assert.notNull(completionStrategy);
this.completionStrategy = completionStrategy;
public void setReleaseStrategy(ReleaseStrategy ReleaseStrategy) {
Assert.notNull(ReleaseStrategy);
this.ReleaseStrategy = ReleaseStrategy;
}
public void setTaskScheduler(TaskScheduler taskScheduler) {
@@ -141,11 +113,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void setReaperInterval(long reaperInterval) {
this.reaperInterval = reaperInterval;
}
public void setOutputChannel(MessageChannel outputChannel) {
@@ -176,232 +146,115 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Object correlationKey = correlationStrategy.getCorrelationKey(message);
if (logger.isDebugEnabled()) {
logger.debug("Handling message with correlationKey [" + correlationKey + "]: " + message);
}
try {
if (tracker.waitForLockIfNotTracked(correlationKey)) {
Collection<Message<?>> messages = store.list(correlationKey);
MessageGroup group = new MessageGroup(messages, correlationKey);
if (group.hasNoMessageSuperseding(message)) {
store(message, correlationKey);
group.add(message);
if (completionStrategy.isComplete(group.getMessages())) {
if (logger.isDebugEnabled()) {
logger.debug("Completing group with correlationKey [" + correlationKey + "]");
}
outputProcessor.processAndSend(group, channelTemplate, this.resolveReplyChannel(message,
this.outputChannel));
if (!correlationKey.equals(message.getHeaders().getCorrelationId())) {
// TODO: strategise the treatment of overwritten correlation
message = MessageBuilder.fromMessage(message).setCorrelationId(correlationKey).build();
}
Object lock = getLock(correlationKey);
synchronized (lock) {
Collection<Message<?>> messages = store.list(correlationKey);
MessageGroup group = new MessageGroup(messages, correlationKey);
if (group.add(message)) {
// TODO: use try/catch to detect problem in group.add() and use
// that to decide on discard?
store(message, correlationKey);
if (ReleaseStrategy.canRelease(group)) {
if (logger.isDebugEnabled()) {
logger.debug("Completing group with correlationKey [" + correlationKey + "]");
}
outputProcessor.processAndSend(group, channelTemplate, this.resolveReplyChannel(message,
this.outputChannel));
if (group.isComplete()) {
complete(group);
}
}
else {
discardChannel.send(message);
else {
partialComplete(group);
}
} // If not releasing any messages the group might still be complete
else if (group.isComplete()) {
for (Message<?> discard : group.getUnmarked()) {
discardChannel.send(discard);
}
complete(group);
}
}
else {
discardChannel.send(message);
}
}
finally {
tracker.unlock(correlationKey);
}
// TODO: arrange for this to be called if user desires, e.g. periodically
public final boolean forceComplete(Object correlationKey) {
Object lock = getLock(correlationKey);
synchronized (lock) {
Collection<Message<?>> all = store.list(correlationKey);
MessageGroup group = new MessageGroup(all, correlationKey);
if (all.size() > 0) {
// last chance for normal completion
if (ReleaseStrategy.canRelease(group)) {
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator().next(),
this.outputChannel));
complete(group);
}
else {
if (sendPartialResultOnTimeout) {
if (logger.isInfoEnabled()) {
logger.info("Processing partially complete messages for key [" + correlationKey + "] to: "
+ outputChannel);
}
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator()
.next(), this.outputChannel));
}
else {
if (logger.isInfoEnabled()) {
logger.info("Discarding partially complete messages for key [" + correlationKey + "] to: "
+ discardChannel);
}
for (Message<?> message : all) {
discardChannel.send(message);
}
}
complete(group);
}
return true;
}
return false;
}
}
private Object getLock(Object correlationKey) {
locks.putIfAbsent(correlationKey, correlationKey);
return locks.get(correlationKey);
}
private void partialComplete(MessageGroup group) {
for (Message<?> message : group.getMessages()) {
store.delete(group.getCorrelationKey(), message.getHeaders().getId());
for (Message<?> message : group.getUnmarked()) {
store.mark(group.getCorrelationKey(), message.getHeaders().getId());
}
}
private void complete(MessageGroup group) {
tracker.pushCorrelationId(group.getCorrelationKey());
store.deleteAll(group.getCorrelationKey());
Object correlationKey = group.getCorrelationKey();
store.deleteAll(correlationKey);
locks.remove(correlationKey);
}
@SuppressWarnings("unchecked")
private void store(Message<?> message, Object correlationKey) {
Message toStore = message;
if (!correlationKey.equals(message.getHeaders().getCorrelationId())) {
toStore = MessageBuilder.fromMessage(message).setCorrelationId(correlationKey).build();
}
store.put(correlationKey, toStore);
if (!keysInBuffer.contains(correlationKey)) {
keysInBuffer.add(new DelayedKey(correlationKey, timeout));
}
}
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.reaperFutureTask != null;
}
}
public void start() {
synchronized (this.lifecycleMonitor) {
if (this.isRunning()) {
return;
}
Assert.state(this.getTaskScheduler() != null, "'taskScheduler' must not be null");
this.reaperFutureTask = this.getTaskScheduler().scheduleWithFixedDelay(new PrunerTask(),
this.reaperInterval);
}
}
public void stop() {
synchronized (this.lifecycleMonitor) {
if (this.isRunning()) {
this.reaperFutureTask.cancel(true);
}
}
}
private class PrunerTask implements Runnable {
public void run() {
if (logger.isTraceEnabled()) {
logger.trace("PrunerTask is running");
}
DelayedKey delayedKey;
try {
while ((delayedKey = keysInBuffer.poll(reaperInterval, TimeUnit.MILLISECONDS)) != null) {
Object key = delayedKey.getKey();
if (logger.isDebugEnabled()) {
logger.debug(this + "'s PrunerTask is processing " + key);
}
if (!forceComplete(key)) {
keysInBuffer.offer(delayedKey);
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
protected final boolean forceComplete(Object key) {
try {
if (tracker.tryLockFor(key)) {
Collection<Message<?>> all = store.list(key);
MessageGroup group = new MessageGroup(all, key);
if (all.size() > 0) {
// last chance for normal completion
MessageChannel outputChannel = resolveReplyChannel(all.iterator().next(), this.outputChannel);
if (completionStrategy.isComplete(all)) {
outputProcessor.processAndSend(group, channelTemplate, outputChannel);
complete(group);
} else {
if (sendPartialResultOnTimeout) {
if (logger.isInfoEnabled()) {
logger.info("Processing partially complete messages for key [" + key + "] to: "
+ outputChannel);
}
outputProcessor.processAndSend(group, channelTemplate, outputChannel);
}
else {
if (logger.isInfoEnabled()) {
logger.info("Discarding partially complete messages for key [" + key + "] to: "
+ discardChannel);
}
for (Message<?> message : all) {
discardChannel.send(message);
}
}
partialComplete(group);
}
}
return true;
}
else {
return false;
}
}
finally {
tracker.unlock(key);
}
}
private final class DelayedKey implements Delayed {
private final Object key;
private final Long releaseTime;
private final TimeUnit unit = TimeUnit.MILLISECONDS;
public DelayedKey(Object correlationKey, long delay) {
Assert.notNull(correlationKey, "'correlationKey' must not be null");
this.key = correlationKey;
this.releaseTime = System.currentTimeMillis() + delay;
}
public long getDelay(TimeUnit unit) {
return unit.convert(this.releaseTime - System.currentTimeMillis(), this.unit);
}
public int compareTo(Delayed o) {
return ((Long) this.getDelay(this.unit)).compareTo(o.getDelay(this.unit));
}
public Object getKey() {
return key;
}
}
private final class IdTracker {
private final ConcurrentMap<Object, ReentrantLock> trackerLocks = new ConcurrentHashMap<Object, ReentrantLock>();
private final Queue<Object> trackedCorrelationIds = new LinkedBlockingQueue<Object>();
private void pushCorrelationId(Object correlationKey) {
while (!trackedCorrelationIds.offer(correlationKey)) {
// make room in the queue
trackedCorrelationIds.poll();
}
trackerLocks.remove(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 (trackedCorrelationIds.contains(correlationKey)) {
// this correlation key is already processed
// in the near past: disallow processing
return false;
}
lock = new ReentrantLock();
ReentrantLock original = trackerLocks.putIfAbsent(correlationKey, lock);
lock = original == null ? lock : original;
}
lock.lock();
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()) {
lock.unlock();
}
}
store.put(correlationKey, message);
}
}

View File

@@ -36,7 +36,7 @@ public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregating
@Override
protected final Object aggregatePayloads(MessageGroup group) {
Collection<Message<?>> messages = group.getMessages();
Collection<Message<?>> messages = group.getUnmarked();
Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups");
List<Object> payloads = new ArrayList<Object>(messages.size());
for (Message<?> message : messages) {

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2002-2009 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.aggregator;
import java.util.ArrayList;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
/**
* The Default Message Aggregator implementation that combines a group of
* messages into a single message containing a {@link List} of all payloads. The
* elements of the List are in order of their receiving. Any MessageHeader value
* is ignored except the <code>correlationId</code>.
*
* <p>
* The default strategy for determining whether a group is complete is based
* on the '<code>sequenceSize</code>' property of the header. Alternatively, a
* custom implementation of the {@link CompletionStrategy} may be provided.
* </p>
*
* <p>
* All considerations regarding <code>timeout</code> and grouping by
* <code>correlationId</code> from {@link AbstractMessageBarrierHandler} apply
* here as well.
* </p>
*
* @author Alex Peters
* @since 1.0.3
*/
public class DefaultMessageAggregator extends AbstractMessageAggregator {
/**
* {@inheritDoc}
*/
@Override
protected Message<?> aggregateMessages(List<Message<?>> messages) {
List<Object> payloads = new ArrayList<Object>(messages.size());
for (Message<?> message : messages) {
payloads.add(message.getPayload());
}
return MessageBuilder.withPayload(payloads).build();
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
/**
* This class implements all the strategy interfaces needed for a default
* resequencer.
*
* @author Iwein Fuld
* @since 2.0
*/
public class DefaultResequencerStrategies implements CorrelationStrategy, CompletionStrategy, MessageGroupProcessor {
private final ConcurrentMap<Object, AtomicInteger> nextMessagesToPass = new ConcurrentHashMap<Object, AtomicInteger>();
private final ConcurrentMap<Object, AtomicInteger> lastMessagesToPass = new ConcurrentHashMap<Object, AtomicInteger>();
private volatile SequenceNumberComparator sequenceNumberComparator = new SequenceNumberComparator();
private volatile boolean releasePartialSequences;
public Object getCorrelationKey(Message<?> message) {
Object key = message.getHeaders().getCorrelationId();
nextMessagesToPass.putIfAbsent(key, new AtomicInteger(1));
return key;
}
public boolean isComplete(Collection<? extends Message<?>> messages) {
return releasePartialSequences
|| (!messages.isEmpty() && messages.iterator().next().getHeaders().getSequenceSize() == messages.size());
}
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
Collection<Message<?>> all = group.getMessages();
Object correlationKey = group.getCorrelationKey();
if (all.size() > 0) {
List<Message<?>> sorted = new ArrayList<Message<?>>(all);
Collections.sort(sorted, sequenceNumberComparator);
AtomicInteger nextSequence = nextMessagesToPass.get(correlationKey);
for (Message<?> message : sorted) {
final int sequenceNumber = message.getHeaders().getSequenceNumber();
if (sequenceNumber <= nextSequence.get()) {
channelTemplate.send(message, outputChannel);
nextSequence.compareAndSet(sequenceNumber, sequenceNumber + 1);
}
}
MessageHeaders headers = sorted.get(0).getHeaders();
if (all.size() == headers.getSequenceSize()) {
// TODO: it's only complete if this is true...
}
}
}
public void setReleasePartialSequences(boolean releasePartialSequences) {
this.releasePartialSequences = releasePartialSequences;
}
private static class SequenceNumberComparator implements Comparator<Message<?>> {
public int compare(Message<?> o1, Message<?> o2) {
return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber());
}
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import java.util.HashMap;
import java.util.Map;
import java.util.Collection;
import org.springframework.integration.core.Message;
/**
* Utility class for AbstractMessageBarrierHandler and its subclasses for
* storing objects while in transit. It is a wrapper around a {@link java.util.Collection},
* providing special properties for recording the complete status, the creation
* time (for determining if a group of messages has timed out), and the
* correlation id for a group of messages (available after the first message has
* been added to it). This is a parameterized type, allowing different different
* client classes to use different types of Collections and their respective features.
*
* Can store/retrieve attributes through its setAttribute() and getAttribute() methods.
*
* This class is not thread-safe and will be synchronized by the calling code.
*
* @author Marius Bogoevici
*/
public class MessageBarrier<T extends Collection<? extends Message<?>>> {
protected final T messages;
private volatile boolean complete = false;
private Object correlationKey;
private final long timestamp = System.currentTimeMillis();
private final Map<String, Object> attributes = new HashMap<String, Object>();
public MessageBarrier(T messages, Object correlationKey) {
this.messages = messages;
this.correlationKey = correlationKey;
}
public Object getCorrelationKey() {
return this.correlationKey;
}
/**
* Returns the creation time of this barrier as the number of milliseconds
* since January 1, 1970.
*
* @see System#currentTimeMillis()
*/
public long getTimestamp() {
return this.timestamp;
}
/**
* Marks the barrier as complete.
*/
public void setComplete() {
this.complete = true;
}
/**
* True if the barrier has received all the messages and can proceed to
* release them.
*/
public boolean isComplete() {
return this.complete;
}
public T getMessages() {
return this.messages;
}
/**
* Sets a the value of a given attribute on the MessageBarrier.
* @param attributeName
* @param value
*/
public void setAttribute(String attributeName, Object value) {
this.attributes.put(attributeName, value);
}
/**
* Gets the value of a given attribute from the MessageBarrier.
* @param attributeName
*/
public <V> V getAttribute(String attributeName) {
return (V)this.attributes.get(attributeName);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2010 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.aggregator;
/**
* A {@link ReleaseStrategy} that releases only the first <code>n</code> messages, where <code>n</code> is a threshold.
*
* @author Dave Syer
*
*/
public class MessageCountReleaseStrategy implements ReleaseStrategy {
private final int threshold;
/**
* @param threshold the number of messages to accept before releasing
*/
public MessageCountReleaseStrategy(int threshold) {
super();
this.threshold = threshold;
}
/**
* Convenient constructor is only one message is required (threshold=1).
*/
public MessageCountReleaseStrategy() {
this(1);
}
/**
* Release the group if it has more messages than the threshold and has not previously been released. Previous
* releases leave an imprint on the group in the form of marked messages. It is possible that more messages than the
* threshold could be released, but only if multiple consumers receive messages from the same group concurrently.
*/
public boolean canRelease(MessageGroup group) {
return group.size() >= threshold && group.getMarked().size() == 0;
}
}

View File

@@ -1,97 +1,145 @@
/*
* Copyright 2002-2010 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.
*
* 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.aggregator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageStore;
/**
* Represents a 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.
* <p/>
* Optionally MessageGroupListeners can be added to get callbacks when (parts
* of) the group are processed or the whole group is completed.
* Represents a mutable group of correlated messages that is bound to a certain {@link 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>.
*
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Dave Syer
*
* @since 2.0
*/
public class MessageGroup {
private final Object correlationKey;
private final Collection<Message<?>> messages = new ArrayList<Message<?>>();
private final Collection<Message<?>> marked = new HashSet<Message<?>>();
private final Collection<Message<?>> unmarked = new HashSet<Message<?>>();
public MessageGroup(Object correlationKey) {
this.correlationKey = correlationKey;
}
public MessageGroup(Collection<? extends Message<?>> originalMessages, Object correlationKey) {
this.correlationKey = correlationKey;
this.messages.addAll(originalMessages);
this(correlationKey);
for (Message<?> message : originalMessages) {
add(message);
}
}
/**
* This method determines whether messages have been added to this group
* that supersede the given message based on its sequence id. This can be
* helpful to avoid ending up with sequences larger than their required
* sequence size or sequences that are missing certain sequence numbers.
* Add a message to the internal list. This is needed to avoid hitting the underlying store or copying the internal
* list. Use with care.
*/
public boolean hasNoMessageSuperseding(Message<?> message) {
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
for (Message<?> member : messages) {
Integer memberSequenceNumber = member.getHeaders().getSequenceNumber();
if (messageSequenceNumber.equals(memberSequenceNumber)) {
return false;
}
}
public boolean add(Message<?> message) {
if (isMember(message)) {
return false;
}
if (message.getHeaders().containsKey(MessageStore.PROCESSED)
&& message.getHeaders().get(MessageStore.PROCESSED, Boolean.class)) {
this.marked.add(message);
} else {
this.unmarked.add(message);
}
return true;
}
/**
* Add a message to the internal list. This is needed to avoid hitting the
* underlying store or copying the internal list. Use with care.
* @return internal message list, modification is allowed, but not recommended
*/
protected void add(Message<?> message) {
messages.add(message);
public Collection<Message<?>> getUnmarked() {
return unmarked;
}
/**
* @return internal message list, modification is allowed, but not
* recommended
* @return internal message list, modification is allowed, but not recommended
*/
public Collection<Message<?>> getMessages() {
return messages;
public Collection<Message<?>> getMarked() {
return marked;
}
/**
* @return the correlation key that links these messages together according
* to a particular CorrelationStrategy
* @return the correlation key that links these messages together according to a particular CorrelationStrategy
*/
public Object getCorrelationKey() {
return correlationKey;
}
public boolean isComplete() {
if (size() == 0) {
return true;
}
int sequenceSize = getSequenceSize();
// If there is no sequence then it must be complete....
return sequenceSize == 0 || sequenceSize == size();
}
public int getSequenceSize() {
if (size() == 0) {
return 0;
}
return getOne().getHeaders().getSequenceSize();
}
private Message<?> getOne() {
return unmarked.isEmpty() ? (marked.isEmpty() ? null : marked.iterator().next()) : unmarked.iterator().next();
}
public int size() {
return marked.size() + unmarked.size();
}
/**
* This method determines whether messages have been added to this group that supersede the given message based on
* its sequence id. This can be helpful to avoid ending up with sequences larger than their required sequence size
* or sequences that are missing certain sequence numbers.
*/
private boolean isMember(Message<?> message) {
if (size() == 0) {
return false;
}
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
Integer messageSequenceSize = message.getHeaders().getSequenceSize();
if (!messageSequenceSize.equals(getSequenceSize())
|| containsSequenceNumber(unmarked, messageSequenceNumber)
|| containsSequenceNumber(marked, messageSequenceNumber)) {
return true;
}
}
return false;
}
private boolean containsSequenceNumber(Collection<Message<?>> messages, Integer messageSequenceNumber) {
for (Message<?> member : messages) {
Integer memberSequenceNumber = member.getHeaders().getSequenceNumber();
if (messageSequenceNumber.equals(memberSequenceNumber)) {
return true;
}
}
return false;
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2002-2009 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.aggregator;
import org.springframework.integration.core.Message;
/**
* Listener that can be configured with a MessageGroup to receive notifications on processing of (parts of) the group
* and exactly one notification when the whole group completes.
*
* @author Iwein Fuld
*/
public interface MessageGroupListener {
void onProcessingOf(Message<?>... processedMessages);
void onCompletionOf(Object correlationKey);
}

View File

@@ -1,119 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link AbstractMessageAggregator} adapter for methods annotated with
* {@link Aggregator @Aggregator} annotation and for <code>aggregator</code>
* elements (e.g. &lt;aggregator ref="beanReference" method="methodName"/&gt;).
*
* @author Marius Bogoevici
* @author Mark Fisher
*/
public class MethodInvokingAggregator extends AbstractMessageAggregator {
private final MessageListMethodAdapter methodInvoker;
public MethodInvokingAggregator(Object object, Method method) {
this.methodInvoker = new MessageListMethodAdapter(object, method);
}
public MethodInvokingAggregator(Object object, String methodName) {
this.methodInvoker = new MessageListMethodAdapter(object, methodName);
}
public MethodInvokingAggregator(Object object) {
Assert.notNull(object, "object must not be null");
Method method = this.findAggregatorMethod(object);
Assert.notNull(method, "unable to resolve Aggregator method on target class ["
+ object.getClass() + "]");
this.methodInvoker = new MessageListMethodAdapter(object, method);
}
public Message<?> aggregateMessages(List<Message<?>> messages) {
if (CollectionUtils.isEmpty(messages)) {
return null;
}
Object returnedValue = this.methodInvoker.executeMethod(messages);
if (returnedValue == null) {
return null;
}
if (returnedValue instanceof Message) {
return (Message<?>) returnedValue;
}
return new GenericMessage<Object>(returnedValue);
}
private Method findAggregatorMethod(Object candidate) {
Class<?> targetClass = AopUtils.getTargetClass(candidate);
if (targetClass == null) {
targetClass = candidate.getClass();
}
Method method = this.findAnnotatedMethod(targetClass);
if (method == null) {
method = this.findSinglePublicMethod(targetClass);
}
return method;
}
private Method findAnnotatedMethod(final Class<?> targetClass) {
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, Aggregator.class);
if (annotation != null) {
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
+ targetClass + "] with the annotation type [" + Aggregator.class.getName() + "]");
annotatedMethod.set(method);
}
}
});
return annotatedMethod.get();
}
private Method findSinglePublicMethod(Class<?> targetClass) {
Method result = null;
for (Method method : targetClass.getMethods()) {
if (!method.getDeclaringClass().equals(Object.class)) {
if (result != null) {
throw new IllegalArgumentException(
"Class [" + targetClass + "] contains more than one public Method.");
}
result = method;
}
}
return result;
}
}

View File

@@ -1,47 +0,0 @@
package org.springframework.integration.aggregator;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
public class MethodInvokingAggregatorFactoryBean implements
FactoryBean<CorrelatingMessageHandler> , InitializingBean{
private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE;
private MessageStore store = new SimpleMessageStore(DEFAULT_CAPACITY);
private CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(
MessageHeaders.CORRELATION_ID);
private CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
private MessageGroupProcessor processor;
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public void afterPropertiesSet() throws Exception {
// build correllation strategy
// build completion strategy
// build processor
}
public CorrelatingMessageHandler getObject() throws Exception {
return new CorrelatingMessageHandler(store, correlationStrategy,
completionStrategy, processor);
}
public Class<? extends CorrelatingMessageHandler> getObjectType() {
return CorrelatingMessageHandler.class;
}
public boolean isSingleton() {
return false;
}
}

View File

@@ -18,17 +18,19 @@ package org.springframework.integration.aggregator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.core.Message;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* MessageGroupProcessor that serves as an adapter for the invocation of a POJO method.
@@ -41,23 +43,20 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
private final MessageListMethodAdapter adapter;
/**
* Creates a wrapper around the target passed in. This constructor will
* choose the best fitting method and throw an exception when methods are
* ambiguous or no fitting methods can be found.
* Creates a wrapper around the target passed in. This constructor will choose the best fitting method and throw an
* exception when methods are ambiguous or no fitting methods can be found.
*
* @param target the object to wrap
* @throws IllegalStateException when no single method can be found unambiguously
*/
public MethodInvokingMessageGroupProcessor(Object target) {
this.adapter = new MessageListMethodAdapter(target, this.selectMethodFrom(target));
this.adapter = new MessageListMethodAdapter(target, this.findAggregatorMethod(target));
}
/**
* Creates a wrapper around the object passed in. This constructor will look
* for a named method specifically and fail when it cannot find a method
* with the given name.
* Creates a wrapper around the object passed in. This constructor will look for a named method specifically and
* fail when it cannot find a method with the given name.
*
* @param target the object to wrap
* @param method the name of the method to look for
@@ -66,34 +65,54 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
this.adapter = new MessageListMethodAdapter(target, method);
}
@Override
protected final Object aggregatePayloads(MessageGroup group) {
final Collection<Message<?>> messagesUpForProcessing = group.getMessages();
final Collection<Message<?>> messagesUpForProcessing = group.getUnmarked();
Object result = this.adapter.executeMethod(messagesUpForProcessing);
return result;
}
private Method selectMethodFrom(Object target) {
Method[] methods = target.getClass().getMethods();
Set<Method> candidates = new HashSet<Method>(Arrays.asList(methods));
removeObjectMethodsFrom(candidates);
removeVoidMethodsFrom(candidates);
removeListIncompatibleMethodsFrom(candidates);
Set<Method> notAnnotatedCandidates = new HashSet<Method>();
if (candidates.size() > 1) {
notAnnotatedCandidates.addAll(removeNotAnnotatedFrom(candidates));
private Method findAggregatorMethod(Object candidate) {
Class<?> targetClass = AopUtils.getTargetClass(candidate);
if (targetClass == null) {
targetClass = candidate.getClass();
}
// if no methods are annotated we need to look in more detail in the unannotated methods
if (candidates.size() < 1) {
candidates = notAnnotatedCandidates;
removeUnfittingFrom(candidates);
Method method = this.findAnnotatedMethod(targetClass);
if (method == null) {
method = this.findSinglePublicMethod(targetClass);
}
Assert.state(candidates.size() == 1,
"Method selection failed, there should be exactly one candidate, found [" + candidates + "]");
return candidates.iterator().next();
return method;
}
private Method findAnnotatedMethod(final Class<?> targetClass) {
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, Aggregator.class);
if (annotation != null) {
Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + targetClass
+ "] with the annotation type [" + Aggregator.class.getName() + "]");
annotatedMethod.set(method);
}
}
});
return annotatedMethod.get();
}
private Method findSinglePublicMethod(Class<?> targetClass) {
Set<Method> methods = new HashSet<Method>();
for (Method method : targetClass.getMethods()) {
if (!method.getDeclaringClass().equals(Object.class)) {
methods.add(method);
}
}
removeListIncompatibleMethodsFrom(methods);
removeVoidMethodsFrom(methods);
removeUnfittingFrom(methods);
if (methods.size() > 1) {
throw new IllegalArgumentException("Class [" + targetClass + "] contains more than one public Method.");
}
return methods.isEmpty() ? null : methods.iterator().next();
}
private void removeListIncompatibleMethodsFrom(Set<Method> candidates) {
@@ -101,7 +120,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
public boolean select(Method method) {
int found = 0;
for (Class<?> parameterClass : method.getParameterTypes()) {
if (parameterClass.isAssignableFrom(List.class)) {
if (Collection.class.isAssignableFrom(parameterClass)) {
found++;
}
}
@@ -118,15 +137,6 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
});
}
private Set<Method> removeNotAnnotatedFrom(Set<Method> candidates) {
return removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
Aggregator annotation = method.getAnnotation(Aggregator.class);
return (annotation == null);
}
});
}
private Set<Method> removeUnfittingFrom(Set<Method> candidates) {
return removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
@@ -141,7 +151,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
int candidateParametersFound = 0;
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if (parameterType.isAssignableFrom(List.class)) {
if (Collection.class.isAssignableFrom(parameterType)) {
boolean headerAnnotationFound = false;
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation instanceof Header) {
@@ -156,14 +166,6 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
return candidateParametersFound == 1;
}
private void removeObjectMethodsFrom(Set<Method> candidates) {
removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
return method.getDeclaringClass().equals(Object.class);
}
});
}
private Set<Method> removeMethodsMatchingSelector(Set<Method> candidates, MethodSelector selector) {
Set<Method> removed = new HashSet<Method>();
Iterator<Method> iterator = candidates.iterator();

View File

@@ -7,7 +7,7 @@ import org.springframework.integration.core.MessageChannel;
/**
* This implementation of MessageGroupProcessor will forward all messages inside the group to the given output channel.
* This is useful if there is no requirement to process the messages, but they should just be blocked as a group until
* their CompletionStrategy lets them pass through.
* their ReleaseStrategy lets them pass through.
*
* @author Iwein Fuld
* @since 2.0.0
@@ -15,7 +15,7 @@ import org.springframework.integration.core.MessageChannel;
public class PassThroughMessageGroupProcessor implements MessageGroupProcessor {
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
for (Message<?> message : group.getMessages()) {
for (Message<?> message : group.getUnmarked()) {
channelTemplate.send(message, outputChannel);
}
}

View File

@@ -16,19 +16,17 @@
package org.springframework.integration.aggregator;
import java.util.Collection;
import org.springframework.integration.core.Message;
/**
* Strategy for determining when a group of messages reaches a state of
* completion (i.e. can trip a barrier).
*
* @author Mark Fisher
* @see AbstractMessageAggregator
* @author Dave Syer
*/
public interface CompletionStrategy {
public interface ReleaseStrategy {
boolean isComplete(Collection<? extends Message<?>> messages);
boolean canRelease(MessageGroup group);
}

View File

@@ -17,36 +17,34 @@
package org.springframework.integration.aggregator;
import java.lang.reflect.Method;
import java.util.Collection;
import org.springframework.integration.core.Message;
import org.springframework.util.Assert;
/**
* Adapter for methods annotated with
* {@link org.springframework.integration.annotation.CompletionStrategy @CompletionStrategy}
* and for '<code>completion-strategy</code>' elements that include a '<code>method</code>'
* attribute (e.g. &lt;completion-strategy ref="beanReference" method="methodName"/&gt;).
* {@link org.springframework.integration.annotation.ReleaseStrategy @ReleaseStrategy}
* and for '<code>release-strategy</code>' elements that include a '<code>method</code>'
* attribute (e.g. &lt;release-strategy ref="beanReference" method="methodName"/&gt;).
*
* @author Marius Bogoevici
*/
public class CompletionStrategyAdapter extends MessageListMethodAdapter implements CompletionStrategy {
public class ReleaseStrategyAdapter extends MessageListMethodAdapter implements ReleaseStrategy {
public CompletionStrategyAdapter(Object object, Method method) {
public ReleaseStrategyAdapter(Object object, Method method) {
super(object, method);
this.assertMethodReturnsBoolean();
}
public CompletionStrategyAdapter(Object object, String methodName) {
public ReleaseStrategyAdapter(Object object, String methodName) {
super(object, methodName);
this.assertMethodReturnsBoolean();
}
public boolean isComplete(Collection<? extends Message<?>> messages) {
return ((Boolean) executeMethod(messages)).booleanValue();
public boolean canRelease(MessageGroup messages) {
return ((Boolean) executeMethod(messages.getUnmarked())).booleanValue() && messages.getMarked().isEmpty();
}
private void assertMethodReturnsBoolean() {
Assert.isTrue(Boolean.class.equals(this.getMethod().getReturnType())
|| boolean.class.equals(this.getMethod().getReturnType()),

View File

@@ -17,135 +17,65 @@
package org.springframework.integration.aggregator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.util.CollectionUtils;
import org.springframework.integration.core.MessageChannel;
/**
* An {@link AbstractMessageBarrierHandler} that waits for a group of
* {@link Message Messages} to arrive and re-sends them in order, sorted
* by their <code>sequenceNumber</code>.
* <p>
* This handler can either release partial sequences of messages or can
* wait for the whole sequence to arrive before re-sending them.
* <p>
* All considerations regarding <code>timeout</code> and grouping by
* '<code>correlationId</code>' from {@link AbstractMessageBarrierHandler}
* apply here as well.
* This class implements all the strategy interfaces needed for a default
* resequencer.
*
* It is assumed that all messages have the same <code>sequence_size</code> header attribute
* and that the sequence numbers of the messages are successive, starting with
* 1 up to <code>sequenceSize</code>. Messages that do not satisfy this condition are
* considered out-of-sequence and thus rejected.
*
*
* Note: messages with the same sequence number will be treated as equivalent
* by this class (i.e. after a message with a given sequence number is received,
* further messages from within the same group, that have the same sequence number,
* will be ignored.
*
* @author Marius Bogoevici
* @author Alex Peters
* @author Iwein Fuld
* @author Dave Syer
* @since 2.0
*/
public class Resequencer extends AbstractMessageBarrierHandler<SortedSet<Message<?>>> {
public class Resequencer implements CorrelationStrategy, ReleaseStrategy, MessageGroupProcessor {
private volatile boolean releasePartialSequences = true;
private static final String LAST_RELEASED_SEQUENCE_NUMBER = "last.released.sequence.number";
private volatile SequenceNumberComparator sequenceNumberComparator = new SequenceNumberComparator();
private volatile boolean releasePartialSequences;
public Object getCorrelationKey(Message<?> message) {
// TODO: remove this (as its duplicating the default)
Object correlationKey = message.getHeaders().getCorrelationId();
return correlationKey;
}
public boolean canRelease(MessageGroup messages) {
if (releasePartialSequences) {
List<Message<?>> sorted = new ArrayList<Message<?>>(messages.getUnmarked());
Collections.sort(sorted, sequenceNumberComparator);
int head = sorted.get(sorted.size() - 1).getHeaders().getSequenceNumber();
int tail = sorted.get(0).getHeaders().getSequenceNumber() - 1;
return tail == messages.getMarked().size() && head - tail == sorted.size();
}
return messages.isComplete();
}
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
Collection<Message<?>> messages = group.getUnmarked();
if (messages.size() > 0) {
List<Message<?>> sorted = new ArrayList<Message<?>>(messages);
Collections.sort(sorted, sequenceNumberComparator);
for (Message<?> message : sorted) {
channelTemplate.send(message, outputChannel);
}
}
}
public void setReleasePartialSequences(boolean releasePartialSequences) {
this.releasePartialSequences = releasePartialSequences;
}
@Override
public String getComponentType() {
return "resequencer";
}
@Override
protected MessageBarrier<SortedSet<Message<?>>> createMessageBarrier(Object correlationKey) {
MessageBarrier<SortedSet<Message<?>>> messageBarrier
= new MessageBarrier<SortedSet<Message<?>>>(new TreeSet<Message<?>>(new MessageSequenceComparator()), correlationKey);
messageBarrier.setAttribute(LAST_RELEASED_SEQUENCE_NUMBER, 0);
return messageBarrier;
private static class SequenceNumberComparator implements Comparator<Message<?>> {
public int compare(Message<?> o1, Message<?> o2) {
return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber());
}
}
@Override
protected void processBarrier(MessageBarrier<SortedSet<Message<?>>> barrier) {
if (hasReceivedAllMessages(barrier)) {
barrier.setComplete();
}
List<Message<?>> releasedMessages = releaseAvailableMessages(barrier);
if (!CollectionUtils.isEmpty(releasedMessages)) {
Message<?> lastMessage = releasedMessages.get(releasedMessages.size()-1);
if (lastMessage.getHeaders().getSequenceNumber().equals(lastMessage.getHeaders().getSequenceSize())) {
this.removeBarrier(barrier.getCorrelationKey());
}
this.sendReplies(releasedMessages, this.resolveReplyChannelFromMessage(releasedMessages.get(0)));
}
}
private boolean hasReceivedAllMessages(MessageBarrier<SortedSet<Message<?>>> barrier) {
if(barrier.getMessages().isEmpty()) {
return false;
}
int sequenceSize = barrier.getMessages().first().getHeaders().getSequenceSize();
int messagesCurrentlyInBarrier = barrier.getMessages().size();
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
return (lastReleasedSequenceNumber + messagesCurrentlyInBarrier == sequenceSize);
}
private List<Message<?>> releaseAvailableMessages(MessageBarrier<SortedSet<Message<?>>> barrier) {
if (this.releasePartialSequences || barrier.isComplete()) {
ArrayList<Message<?>> releasedMessages = new ArrayList<Message<?>>();
Iterator<Message<?>> it = barrier.getMessages().iterator();
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
while (it.hasNext()) {
Message<?> currentMessage = it.next();
if (lastReleasedSequenceNumber == currentMessage.getHeaders().getSequenceNumber() - 1) {
releasedMessages.add(currentMessage);
lastReleasedSequenceNumber = currentMessage.getHeaders().getSequenceNumber();
it.remove();
}
else {
break;
}
}
barrier.setAttribute(LAST_RELEASED_SEQUENCE_NUMBER, lastReleasedSequenceNumber);
return releasedMessages;
}
else {
return new ArrayList<Message<?>>();
}
}
@Override
protected boolean canAddMessage(Message<?> message, MessageBarrier<SortedSet<Message<?>>> barrier) {
if (!super.canAddMessage(message, barrier)) {
return false;
}
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
if (barrier.messages.contains(message)
|| lastReleasedSequenceNumber >= message.getHeaders().getSequenceNumber()) {
logger.debug("A message with the same sequence number has been already received: " + message);
return false;
}
if (message.getHeaders().getSequenceSize() < message.getHeaders().getSequenceNumber()) {
logger.debug("The message has a sequence number which is larger than the sequence size: "+ message);
return false;
}
if (!barrier.getMessages().isEmpty() &&
! message.getHeaders().getSequenceSize().equals(barrier.getMessages().first().getHeaders().getSequenceSize())) {
logger.debug("The message has a sequence size which is different from other messages handled so far: " + message
+ ", expected value is " + barrier.getMessages().first().getHeaders().getSequenceNumber());
return false;
}
return true;
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import java.util.Collection;
import org.springframework.integration.core.Message;
import org.springframework.util.CollectionUtils;
/**
* An implementation of {@link CompletionStrategy} that simply compares the
* current size of the message list to the expected 'sequenceSize' according to
* the first {@link Message} in the list.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class SequenceSizeCompletionStrategy implements CompletionStrategy {
public boolean isComplete(Collection<? extends Message<?>> messages) {
if (CollectionUtils.isEmpty(messages)) {
return false;
}
return messages.size() != 0 && (messages.size() >= messages.iterator().next().getHeaders().getSequenceSize());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2008 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.aggregator;
/**
* An implementation of {@link ReleaseStrategy} that simply compares the
* current size of the message list to the expected 'sequenceSize'.
*
* @author Mark Fisher
* @author Marius Bogoevici
* @author Dave Syer
*/
public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
public boolean canRelease(MessageGroup messages) {
return messages.isComplete();
}
}

View File

@@ -22,7 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.integration.aggregator.AbstractMessageAggregator;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
/**
* Indicates that a method is capable of aggregating messages.
@@ -56,12 +56,12 @@ public @interface Aggregator {
/**
* timeout for sending results to the reply target (in milliseconds)
*/
long sendTimeout() default AbstractMessageAggregator.DEFAULT_SEND_TIMEOUT;
long sendTimeout() default CorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT;
/**
* maximum time to wait for completion (in milliseconds)
*/
long timeout() default AbstractMessageAggregator.DEFAULT_TIMEOUT;
long timeout() default CorrelatingMessageHandler.DEFAULT_TIMEOUT;
/**
* indicates whether to send an incomplete aggregate on timeout
@@ -71,13 +71,14 @@ public @interface Aggregator {
/**
* interval for the task that checks for timed-out aggregates
*/
long reaperInterval() default AbstractMessageAggregator.DEFAULT_REAPER_INTERVAL;
long reaperInterval() default CorrelatingMessageHandler.DEFAULT_REAPER_INTERVAL;
/**
* maximum number of correlation IDs to maintain so that received messages
* may be recognized as belonging to an aggregate that has already completed
* or timed out
*/
int trackedCorrelationIdCapacity() default AbstractMessageAggregator.DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY;
// TODO: remove / deal with tracked id capacity
int trackedCorrelationIdCapacity() default 42;
}

View File

@@ -31,6 +31,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CompletionStrategy {
public @interface ReleaseStrategy {
}

View File

@@ -18,18 +18,20 @@ package org.springframework.integration.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.aggregator.AbstractMessageAggregator;
import org.springframework.integration.aggregator.CompletionStrategyAdapter;
import org.springframework.integration.aggregator.MethodInvokingAggregator;
import org.springframework.integration.aggregator.ReleaseStrategyAdapter;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.CorrelationStrategyAdapter;
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CompletionStrategy;
import org.springframework.integration.annotation.ReleaseStrategy;
import org.springframework.integration.annotation.CorrelationStrategy;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -48,49 +50,54 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
@Override
protected MessageHandler createHandler(Object bean, Method method, Aggregator annotation) {
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean, method);
this.configureCompletionStrategy(bean, aggregator);
this.configureCorrelationStrategy(bean, aggregator);
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method.getName());
ReleaseStrategyAdapter ReleaseStrategy = getReleaseStrategy(bean);
CorrelationStrategyAdapter correlationStrategy = getCorrelationStrategy(bean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(new SimpleMessageStore(), correlationStrategy, ReleaseStrategy, processor);
String discardChannelName = annotation.discardChannel();
if (StringUtils.hasText(discardChannelName)) {
MessageChannel discardChannel = this.channelResolver.resolveChannelName(discardChannelName);
Assert.notNull(discardChannel, "failed to resolve discardChannel '" + discardChannelName + "'");
aggregator.setDiscardChannel(discardChannel);
handler.setDiscardChannel(discardChannel);
}
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
aggregator.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
handler.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
aggregator.setSendTimeout(annotation.sendTimeout());
aggregator.setSendPartialResultOnTimeout(annotation.sendPartialResultsOnTimeout());
aggregator.setReaperInterval(annotation.reaperInterval());
aggregator.setTimeout(annotation.timeout());
aggregator.setTrackedCorrelationIdCapacity(annotation.trackedCorrelationIdCapacity());
aggregator.setBeanFactory(this.beanFactory);
aggregator.afterPropertiesSet();
return aggregator;
handler.setSendTimeout(annotation.sendTimeout());
handler.setSendPartialResultOnTimeout(annotation.sendPartialResultsOnTimeout());
handler.setReaperInterval(annotation.reaperInterval());
handler.setTimeout(annotation.timeout());
// handler.setTrackedCorrelationIdCapacity(annotation.trackedCorrelationIdCapacity());
handler.setBeanFactory(this.beanFactory);
handler.afterPropertiesSet();
return handler;
}
private void configureCompletionStrategy(final Object bean, final AbstractMessageAggregator aggregator) {
private ReleaseStrategyAdapter getReleaseStrategy(final Object bean) {
final AtomicReference<ReleaseStrategyAdapter> reference = new AtomicReference<ReleaseStrategyAdapter>();
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, CompletionStrategy.class);
Annotation annotation = AnnotationUtils.getAnnotation(method, ReleaseStrategy.class);
if (annotation != null) {
aggregator.setCompletionStrategy(new CompletionStrategyAdapter(bean, method));
reference.set(new ReleaseStrategyAdapter(bean, method));
}
}
});
return reference.get();
}
private void configureCorrelationStrategy(final Object bean, final AbstractMessageAggregator aggregator) {
private CorrelationStrategyAdapter getCorrelationStrategy(final Object bean) {
final AtomicReference<CorrelationStrategyAdapter> reference = new AtomicReference<CorrelationStrategyAdapter>();
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, CorrelationStrategy.class);
if (annotation != null) {
aggregator.setCorrelationStrategy(new CorrelationStrategyAdapter(bean, method));
reference.set(new CorrelationStrategyAdapter(bean, method));
}
}
});
return reference.get();
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
@@ -25,116 +26,117 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Parser for the <em>aggregator</em> element of the integration namespace.
* Registers the annotation-driven post-processors.
*
* Parser for the <em>aggregator</em> element of the integration namespace. Registers the annotation-driven
* post-processors.
*
* @author Marius Bogoevici
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
public class AggregatorParser extends AbstractConsumerEndpointParser {
private static final String COMPLETION_STRATEGY_REF_ATTRIBUTE = "completion-strategy";
private static final String RELEASE_STRATEGY_REF_ATTRIBUTE = "release-strategy";
private static final String COMPLETION_STRATEGY_METHOD_ATTRIBUTE = "completion-strategy-method";
private static final String RELEASE_STRATEGY_METHOD_ATTRIBUTE = "release-strategy-method";
private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy";
private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy";
private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method";
private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method";
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
private static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE = "send-partial-result-on-timeout";
private static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE = "send-partial-result-on-timeout";
private static final String REAPER_INTERVAL_ATTRIBUTE = "reaper-interval";
private static final String REAPER_INTERVAL_ATTRIBUTE = "reaper-interval";
//private static final String TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE = "tracked-correlation-id-capacity";
private static final String TIMEOUT_ATTRIBUTE = "timeout";
private static final String TIMEOUT_ATTRIBUTE = "timeout";
private static final String RELEASE_STRATEGY_PROPERTY = "releaseStrategy";
private static final String AUTO_STARTUP_ATTRIBUTE = "auto-startup";
private static final String COMPLETION_STRATEGY_PROPERTY = "completionStrategy";
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(
element, parserContext);
String ref = element.getAttribute(REF_ATTRIBUTE);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
String ref = element.getAttribute(REF_ATTRIBUTE);
BeanDefinitionBuilder builder;
BeanDefinitionBuilder processorBuilder = null;
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.MethodInvokingMessageGroupProcessor");
if (innerHandlerDefinition != null) {
processorBuilder.addConstructorArgValue(innerHandlerDefinition);
}
else {
processorBuilder.addConstructorArgReference(ref);
}
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
}
else {
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.DefaultAggregatingMessageGroupProcessor")
.getBeanDefinition());
}
builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
BeanDefinitionBuilder processorBuilder = null;
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
String method = element.getAttribute(METHOD_ATTRIBUTE);
processorBuilder.getRawBeanDefinition().getConstructorArgumentValues()
.addGenericArgumentValue(method, "java.lang.String");
}
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.MethodInvokingMessageGroupProcessor");
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
} else {
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.DefaultAggregatingMessageGroupProcessor").getBeanDefinition());
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, DISCARD_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, OUTPUT_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(
builder, element, SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REAPER_INTERVAL_ATTRIBUTE);
// IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,
// element, TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, AUTO_STARTUP_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TIMEOUT_ATTRIBUTE);
this.injectPropertyWithBean(COMPLETION_STRATEGY_REF_ATTRIBUTE, COMPLETION_STRATEGY_METHOD_ATTRIBUTE,
COMPLETION_STRATEGY_PROPERTY, "CompletionStrategyAdapter", element, builder, parserContext);
this.injectPropertyWithBean(CORRELATION_STRATEGY_REF_ATTRIBUTE, CORRELATION_STRATEGY_METHOD_ATTRIBUTE,
CORRELATION_STRATEGY_PROPERTY, "CorrelationStrategyAdapter", element, builder, parserContext);
return builder;
}
if (innerHandlerDefinition != null) {
processorBuilder.addConstructorArgValue(innerHandlerDefinition);
} else {
if (StringUtils.hasText(ref)) {
processorBuilder.addConstructorArgReference(ref);
}
}
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
String method = element.getAttribute(METHOD_ATTRIBUTE);
processorBuilder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
}
private void injectPropertyWithBean(String beanRefAttribute, String methodRefAttribute, String beanProperty,
String adapterClass, Element element, BeanDefinitionBuilder builder, ParserContext parserContext) {
final String beanRef = element.getAttribute(beanRefAttribute);
final String beanMethod = element.getAttribute(methodRefAttribute);
if (StringUtils.hasText(beanRef)) {
if (StringUtils.hasText(beanMethod)) {
String adapterBeanName = this.createAdapter(beanRef, beanMethod, adapterClass, parserContext);
builder.addPropertyReference(beanProperty, adapterBeanName);
}
else {
builder.addPropertyReference(beanProperty, beanRef);
}
}
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
DISCARD_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
OUTPUT_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
REAPER_INTERVAL_ATTRIBUTE);
// IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TIMEOUT_ATTRIBUTE);
this.injectPropertyWithBean(RELEASE_STRATEGY_REF_ATTRIBUTE,
RELEASE_STRATEGY_METHOD_ATTRIBUTE, RELEASE_STRATEGY_PROPERTY,
"ReleaseStrategyAdapter", element, builder, parserContext);
this.injectPropertyWithBean(CORRELATION_STRATEGY_REF_ATTRIBUTE,
CORRELATION_STRATEGY_METHOD_ATTRIBUTE, CORRELATION_STRATEGY_PROPERTY,
"CorrelationStrategyAdapter", element, builder, parserContext);
return builder;
}
private String createAdapter(String ref, String method, String unqualifiedClassName, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator." + unqualifiedClassName);
builder.addConstructorArgReference(ref);
builder.getRawBeanDefinition().getConstructorArgumentValues()
.addGenericArgumentValue(method, "java.lang.String");
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
}
private void injectPropertyWithBean(String beanRefAttribute, String methodRefAttribute,
String beanProperty, String adapterClass, Element element,
BeanDefinitionBuilder builder, ParserContext parserContext) {
final String beanRef = element.getAttribute(beanRefAttribute);
final String beanMethod = element.getAttribute(methodRefAttribute);
if (StringUtils.hasText(beanRef)) {
if (StringUtils.hasText(beanMethod)) {
String adapterBeanName = this.createAdapter(beanRef, beanMethod, adapterClass,
parserContext);
builder.addPropertyReference(beanProperty, adapterBeanName);
} else {
builder.addPropertyReference(beanProperty, beanRef);
}
}
}
private String createAdapter(String ref, String method, String unqualifiedClassName,
ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator." + unqualifiedClassName);
builder.addConstructorArgReference(ref);
builder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
parserContext.getRegistry());
}
}

View File

@@ -16,12 +16,11 @@
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;resequencer&gt; element.
@@ -32,38 +31,57 @@ public class ResequencerParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
BeanDefinitionBuilder processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.Resequencer");
IntegrationNamespaceUtils.setValueIfAttributeDefined(processorBuilder, element, "release-partial-sequences");
// TODO: expose message store as an XML attribute
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".store.SimpleMessageStore").getBeanDefinition());
String correlationStrategyRef = getCorrelationStrategyRef(element, parserContext);
String processorRef = BeanDefinitionReaderUtils.registerWithGeneratedName(processorBuilder
.getBeanDefinition(), parserContext.getRegistry());
if (correlationStrategyRef != null) {
builder.addConstructorArgReference(correlationStrategyRef);
}
else {
builder.addConstructorArgReference(processorRef);
}
// Completion strategy
builder.addConstructorArgReference(processorRef);
// Message group processor
builder.addConstructorArgReference(processorRef);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "discard-channel");
this.configureCorrelationStrategy(builder, element, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "release-partial-sequences");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-partial-result-on-timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reaper-interval");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "tracked-correlation-id-capacity");
// IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "tracked-correlation-id-capacity");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder;
}
private void configureCorrelationStrategy(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
private String getCorrelationStrategyRef(Element element, ParserContext parserContext) {
String ref = element.getAttribute("correlation-strategy");
String method = element.getAttribute("correlation-strategy-method");
String correlationStrategyProperty = "correlationStrategy";
if (StringUtils.hasText(ref)) {
if (StringUtils.hasText(method)) {
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelationStrategyAdapter");
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE
+ ".aggregator.CorrelationStrategyAdapter");
adapterBuilder.addConstructorArgReference(ref);
adapterBuilder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
String adapterBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
adapterBuilder.getBeanDefinition(), parserContext.getRegistry());
builder.addPropertyReference(correlationStrategyProperty, adapterBeanName);
adapterBuilder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method,
"java.lang.String");
String adapterBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(adapterBuilder
.getBeanDefinition(), parserContext.getRegistry());
return adapterBeanName;
}
else {
builder.addPropertyReference(correlationStrategyProperty, ref);
return ref;
}
}
return null;
}
}

View File

@@ -20,11 +20,11 @@ import java.util.Collection;
import java.util.UUID;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHeaders;
/**
* Strategy interface for storing and retrieving messages. The interface mimics
* the semantics for REST for the methods named after REST operations. This is
* helpful when mapping to a RESTful API.
* Strategy interface for storing and retrieving messages. The interface mimics the semantics for REST for the methods
* named after REST operations. This is helpful when mapping to a RESTful API.
*
* @author Mark Fisher
* @author Iwein Fuld
@@ -33,43 +33,40 @@ import org.springframework.integration.core.Message;
*/
public interface MessageStore {
String PROCESSED = MessageHeaders.PREFIX + "processed";
/**
* Return the Message with the given id, or <i>null</i> if no Message with
* that id exists in the MessageStore.
* Return the Message with the given id, or <i>null</i> if no Message with that id exists in the MessageStore.
*/
Message<?> get(UUID id);
/**
* Put the provided Message into the MessageStore. The store may need to
* mutate the message internally, and if it does then the return value can
* be different than the input. The id of the return value will be used as
* an index so that the {@link #get(UUID)} and {@link #delete(Object)}
* behave properly. Since messages are immutable, putting the same message
* more than once is a no-op.
* Put the provided Message into the MessageStore. The store may need to mutate the message internally, and if it
* does then the return value can be different than the input. The id of the return value will be used as an index
* so that the {@link #get(UUID)} and {@link #delete(Object)} behave properly. Since messages are immutable, putting
* the same message more than once is a no-op.
*
* @return the message that was stored
*/
<T> Message<T> put(Message<T> message);
/**
* Remove the Message with the given id from the MessageStore, if present,
* and return it. If no Message with that id is present in the store, this
* will return <i>null</i>.
* Remove the Message with the given id from the MessageStore, if present, and return it. If no Message with that id
* is present in the store, this will return <i>null</i>.
*/
Message<?> delete(UUID id);
/**
* Return all Messages currently in the MessageStore that were stored using
* {@link #put(Object, Message)} or {@link #put(Object, Collection)} with
* this correlation id.
* Return all Messages currently in the MessageStore that were stored using {@link #put(Object, Message)} or
* {@link #put(Object, Collection)} with this correlation id.
*
* @see org.springframework.integration.core.MessageHeaders#getCorrelationId()
*/
Collection<Message<?>> list(Object correlationId);
/**
* Store a message with an association to a correlation id. This can be used
* to group messages together instead of storing them just under their id.
* Store a message with an association to a correlation id. This can be used to group messages together instead of
* storing them just under their id.
*
* @param correlationId the correlation id to store the message under
* @param message a message
@@ -77,28 +74,17 @@ public interface MessageStore {
void put(Object correlationId, Message<?> message);
/**
* Store a group of message with an association to a correlation id.
* Mark a message from the association with this correlation id. If the message was previously added using
* {@link #put(Object, Message)} then it will be removed and re-inserted with the {@value #PROCESSED} flag set in
* the headers.
*
* @param correlationId the correlation id to store the message under
* @param messages a collection of messages
*
* @see MessageStore#put(UUID, Message)
* @param correlationId the correlation id to mark the message under
*/
void put(Object correlationId, Collection<Message<?>> messages);
Message<?> mark(Object correlationId, UUID messageId);
/**
* Delete a message from the association with this correlation id. If the
* message was stored under through {@link #put(Message)} as well, then it
* is still accessible via {@link #get(UUID)}.
*
* @param correlationId the correlation id to delete all messages under
*/
Message<?> delete(Object correlationId, UUID messageId);
/**
* Delete all the messages from the association with this correlation id. If
* the messages were stored under their id through {@link #put(Message)}
* they are still accessible via {@link #get(UUID)}.
* Delete all the messages from the association with this correlation id. If the messages were stored under their id
* through {@link #put(Message)} they are still accessible via {@link #get(UUID)}.
*
* @param correlationId the correlation id to delete all messages under
*/

View File

@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.util.UpperBound;
import org.springframework.util.Assert;
@@ -101,15 +102,11 @@ public class SimpleMessageStore implements MessageStore {
return new HashSet<Message<?>>(collection);
}
public void put(Object correlationId, Collection<Message<?>> messages) {
getMessagesInternal(correlationId).addAll(messages);
}
public void put(Object correlationId, Message<?> message) {
getMessagesInternal(correlationId).add(message);
}
public Message<?> delete(Object correlationId, UUID messageId) {
public Message<?> mark(Object correlationId, UUID messageId) {
if (!correlationToMessage.containsKey(correlationId)) {
return null;
}
@@ -119,9 +116,10 @@ public class SimpleMessageStore implements MessageStore {
Message<?> message = (Message<?>) iterator.next();
if (message.getHeaders().getId().equals(messageId)) {
iterator.remove();
result = message;
result = MessageBuilder.fromMessage(message).setHeader(PROCESSED, true).build();
}
}
messages.add(result);
return result;
}

View File

@@ -1422,7 +1422,7 @@
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="innerEndpointDefinitionAware">
<xsd:attribute name="completion-strategy" type="xsd:string">
<xsd:attribute name="release-strategy" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
@@ -1431,11 +1431,11 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="completion-strategy-method" type="xsd:string">
<xsd:attribute name="release-strategy-method" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-method type-ref="@completion-strategy"/>
<tool:expected-method type-ref="@release-strategy"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -1,352 +0,0 @@
/*
* Copyright 2002-2010 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.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.message.StringMessage;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class AggregatorEndpointTests {
private TaskExecutor taskExecutor;
private ThreadPoolTaskScheduler taskScheduler;
private AbstractMessageAggregator aggregator;
@Before
public void configureAggregator() {
this.taskExecutor = new SimpleAsyncTaskExecutor();
this.taskScheduler = new ThreadPoolTaskScheduler();
this.taskScheduler.afterPropertiesSet();
this.aggregator = new TestAggregator();
this.aggregator.setTaskScheduler(this.taskScheduler);
this.aggregator.start();
}
@Test
public void testCompleteGroupWithinTimeout() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}
@Test
public void testCompleteGroupWithinTimeoutWithSameId() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
UUID id = UUID.randomUUID();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, id);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, id);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, id);
CountDownLatch latch = new CountDownLatch(3);
//for testing the duplication scenario, the messages must be processed synchronously
new AggregatorTestTask(this.aggregator, message1, latch).run();
new AggregatorTestTask(this.aggregator, message2, latch).run();
new AggregatorTestTask(this.aggregator, message3, latch).run();
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}
@Test
public void testShouldNotSendPartialResultOnTimeoutByDefault() throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setTimeout(50);
this.aggregator.setReaperInterval(10);
this.aggregator.setDiscardChannel(discardChannel);
QueueChannel replyChannel = new QueueChannel();
Message<?> message = createMessage("123", "ABC", 2, 1, replyChannel, null);
CountDownLatch latch = new CountDownLatch(1);
AggregatorTestTask task = new AggregatorTestTask(this.aggregator, message, latch);
this.taskExecutor.execute(task);
latch.await(2000, TimeUnit.MILLISECONDS);
assertEquals("task should have completed within timeout", 0, latch.getCount());
Message<?> reply = replyChannel.receive(0);
assertNull(reply);
Message<?> discardedMessage = discardChannel.receive(2000);
assertNotNull(discardedMessage);
assertEquals(message, discardedMessage);
}
@Test
public void testShouldSendPartialResultOnTimeoutTrue() throws InterruptedException {
this.aggregator.setTimeout(500);
this.aggregator.setReaperInterval(10);
this.aggregator.setSendPartialResultOnTimeout(true);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
CountDownLatch latch = new CountDownLatch(2);
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator, message1, latch);
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator, message2, latch);
this.taskExecutor.execute(task1);
this.taskExecutor.execute(task2);
latch.await(3000, TimeUnit.MILLISECONDS);
assertEquals("handlers should have been invoked within time limit", 0, latch.getCount());
Message<?> reply = replyChannel.receive(3000);
assertNotNull("a reply message should have been received", reply);
assertEquals("123456", reply.getPayload());
assertNull(task1.getException());
assertNull(task2.getException());
}
@Test
public void testMultipleGroupsSimultaneously() throws InterruptedException {
QueueChannel replyChannel1 = new QueueChannel();
QueueChannel replyChannel2 = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel1, null);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel1, null);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel1, null);
Message<?> message4 = createMessage("abc", "XYZ", 3, 1, replyChannel2, null);
Message<?> message5 = createMessage("def", "XYZ", 3, 2, replyChannel2, null);
Message<?> message6 = createMessage("ghi", "XYZ", 3, 3, replyChannel2, null);
CountDownLatch latch = new CountDownLatch(6);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message6, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message5, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply1 = replyChannel1.receive(500);
assertNotNull(reply1);
assertEquals("123456789", reply1.getPayload());
Message<?> reply2 = replyChannel2.receive(500);
assertNotNull(reply2);
assertEquals("abcdefghi", reply2.getPayload());
}
@Test
public void testDiscardChannelForTrackedCorrelationId() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
assertEquals("test-1a", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
assertEquals("test-1b", discardChannel.receive(100).getPayload());
}
@Test
public void testTrackedCorrelationIdsCapacityAtLimit() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
assertEquals("test-1a", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-2", 2, 1, 1, replyChannel, null));
assertEquals("test-2", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-3", 3, 1, 1, replyChannel, null));
assertEquals("test-3", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
assertEquals("test-1b", discardChannel.receive(100).getPayload());
}
@Test
public void testTrackedCorrelationIdsCapacityPassesLimit() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
assertEquals("test-1a", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-2", 2, 1, 1, replyChannel, null));
assertEquals("test-2", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-3", 3, 1, 1, replyChannel, null));
assertEquals("test-3", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-4", 4, 1, 1, replyChannel, null));
assertEquals("test-4", replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
assertEquals("test-1b", replyChannel.receive(100).getPayload());
assertNull(discardChannel.receive(0));
}
@Test(expected = MessageHandlingException.class)
public void testExceptionThrownIfNoCorrelationId() throws InterruptedException {
Message<?> message = createMessage("123", null, 2, 1, new QueueChannel(), null);
this.aggregator.handleMessage(message);
}
@Test
public void testAdditionalMessageAfterCompletion() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
Message<?> message4 = createMessage("abc", "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(4);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789".length(), ((String)reply.getPayload()).length());
}
@Test
public void testNullReturningAggregator() throws InterruptedException {
this.aggregator = new NullReturningAggregator();
this.aggregator.setTaskScheduler(this.taskScheduler);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1, latch);
this.taskExecutor.execute(task1);
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2, latch);
this.taskExecutor.execute(task2);
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3, latch);
this.taskExecutor.execute(task3);
latch.await(1000, TimeUnit.MILLISECONDS);
assertNull(task1.getException());
assertNull(task2.getException());
assertNull(task3.getException());
Message<?> reply = replyChannel.receive(500);
assertNull(reply);
assertTrue(((NullReturningAggregator) this.aggregator).isAggregationComplete());
}
private static Message<?> createMessage(String payload, Object correlationId,
int sequenceSize, int sequenceNumber, MessageChannel replyChannel, UUID predefinedId) {
MessageBuilder<String> builder = MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId)
.setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)
.setReplyChannel(replyChannel);
if (predefinedId != null) {
builder.setHeader(MessageHeaders.ID, predefinedId);
}
return builder.build();
}
private static class TestAggregator extends AbstractMessageAggregator {
public Message<?> aggregateMessages(List<Message<?>> messages) {
List<Message<?>> sortableList = new ArrayList<Message<?>>(messages);
Collections.sort(sortableList, new MessageSequenceComparator());
StringBuffer buffer = new StringBuffer();
for (Message<?> message : sortableList) {
buffer.append(message.getPayload().toString());
}
return new StringMessage(buffer.toString());
}
}
private static class NullReturningAggregator extends AbstractMessageAggregator {
private boolean aggregationComplete;
public boolean isAggregationComplete() {
return aggregationComplete;
}
public Message<?> aggregateMessages(List<Message<?>> messages) {
this.aggregationComplete = true;
return null;
}
}
private static class AggregatorTestTask implements Runnable {
private AbstractMessageAggregator aggregator;
private Message<?> message;
private Exception exception;
private CountDownLatch latch;
AggregatorTestTask(AbstractMessageAggregator aggregator, Message<?> message, CountDownLatch latch) {
this.aggregator = aggregator;
this.message = message;
this.latch = latch;
}
public Exception getException() {
return this.exception;
}
public void run() {
try {
this.aggregator.handleMessage(message);
}
catch (Exception e) {
this.exception = e;
}
finally {
this.latch.countDown();
}
}
}
@After
public void stopTaskScheduler() {
this.taskScheduler.destroy();
this.aggregator.stop();
}
}

View File

@@ -1,207 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.message.MessageBuilder;
import java.lang.reflect.Method;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author Mark Fisher
*/
/*
* TODO This class needs to be removed with INT-1017. We need to ensure that the tests herein are superseded by
* MethodInvokingMessageGroupProcessorTests before deleting it entirely.
*/
public class AggregatorMethodResolutionTests {
@Test
public void singleAnnotation() throws Exception {
SingleAnnotationTestBean bean = new SingleAnnotationTestBean();
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean);
Method method = this.getMethod(aggregator);
Method expected = SingleAnnotationTestBean.class.getMethod("method1", new Class[]{List.class});
assertEquals(expected, method);
}
@Test(expected = IllegalArgumentException.class)
public void multipleAnnotations() {
MultipleAnnotationTestBean bean = new MultipleAnnotationTestBean();
new MethodInvokingAggregator(bean);
}
@Test
public void noAnnotations() throws Exception {
NoAnnotationTestBean bean = new NoAnnotationTestBean();
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean);
Method method = this.getMethod(aggregator);
Method expected = NoAnnotationTestBean.class.getMethod("method1", new Class[]{List.class});
assertEquals(expected, method);
}
@Test(expected = IllegalArgumentException.class)
public void multiplePublicMethods() {
MultiplePublicMethodTestBean bean = new MultiplePublicMethodTestBean();
new MethodInvokingAggregator(bean);
}
@Test(expected = IllegalArgumentException.class)
public void noPublicMethods() {
NoPublicMethodTestBean bean = new NoPublicMethodTestBean();
new MethodInvokingAggregator(bean);
}
@Test
public void jdkProxy() {
DirectChannel input = new DirectChannel();
QueueChannel output = new QueueChannel();
GreetingService testBean = new GreetingBean();
ProxyFactory proxyFactory = new ProxyFactory(testBean);
proxyFactory.setProxyTargetClass(false);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(testBean);
aggregator.setAutoStartup(false);
aggregator.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, aggregator);
endpoint.start();
Message<?> message = MessageBuilder.withPayload("proxy")
.setCorrelationId("abc")
.build();
input.send(message);
assertEquals("hello proxy", output.receive(0).getPayload());
}
@Test
public void cglibProxy() {
DirectChannel input = new DirectChannel();
QueueChannel output = new QueueChannel();
GreetingService testBean = new GreetingBean();
ProxyFactory proxyFactory = new ProxyFactory(testBean);
proxyFactory.setProxyTargetClass(true);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(testBean);
aggregator.setAutoStartup(false);
aggregator.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, aggregator);
endpoint.start();
Message<?> message = MessageBuilder.withPayload("proxy")
.setCorrelationId("abc")
.build();
input.send(message);
assertEquals("hello proxy", output.receive(0).getPayload());
}
private Method getMethod(MethodInvokingAggregator aggregator) {
Object invoker = new DirectFieldAccessor(aggregator).getPropertyValue("methodInvoker");
return (Method) new DirectFieldAccessor(invoker).getPropertyValue("method");
}
private static class SingleAnnotationTestBean {
@Aggregator
public String method1(List<String> input) {
return input.get(0);
}
public String method2(List<String> input) {
return input.get(0);
}
}
private static class MultipleAnnotationTestBean {
@Aggregator
public String method1(List<String> input) {
return input.get(0);
}
@Aggregator
public String method2(List<String> input) {
return input.get(0);
}
}
private static class NoAnnotationTestBean {
public String method1(List<String> input) {
return input.get(0);
}
String method2(List<String> input) {
return input.get(0);
}
}
private static class MultiplePublicMethodTestBean {
public String upperCase(String s) {
return s.toUpperCase();
}
public String lowerCase(String s) {
return s.toLowerCase();
}
}
private static class NoPublicMethodTestBean {
String lowerCase(String s) {
return s.toLowerCase();
}
}
public interface GreetingService {
String sayHello(List<String> names);
}
public static class GreetingBean implements GreetingService {
private String greeting = "hello";
public void setGreeting(String greeting) {
this.greeting = greeting;
}
@Aggregator
public String sayHello(List<String> names) {
return greeting + " " + names.get(0);
}
}
}

View File

@@ -22,7 +22,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -43,7 +42,7 @@ import org.springframework.integration.store.SimpleMessageStore;
* @author Marius Bogoevici
* @author Iwein Fuld
*/
public class NewAggregatorEndpointTests {
public class AggregatorTests {
private CorrelatingMessageHandler aggregator;
@@ -115,34 +114,20 @@ public class NewAggregatorEndpointTests {
aggregator.handleMessage(message6);
aggregator.handleMessage(message4);
aggregator.handleMessage(message2);
@SuppressWarnings("unchecked")
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
assertNotNull(reply1);
assertThat(reply1.getPayload(), is(105));
@SuppressWarnings("unchecked")
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
assertNotNull(reply2);
assertThat(reply2.getPayload(), is(2431));
}
@Test
public void testDiscardChannelForTrackedCorrelationId() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, "tracked", 1, 1, replyChannel, null));
Message<?> received1 = replyChannel.receive(0);
assertEquals(1, received1.getPayload());
assertNotNull("Expected aggregated message, but got null", received1);
this.aggregator.handleMessage(createMessage(2, "tracked", 1, 1, replyChannel, null));
Message<?> received2 = discardChannel.receive(0);
assertNotNull("Expected discarded message, but got null", received2);
assertEquals(2, received2.getPayload());
}
@Test
@Ignore
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityAtLimit() {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
//this.aggregator.setTrackedCorrelationIdCapacity(3);
@@ -162,7 +147,6 @@ public class NewAggregatorEndpointTests {
@Ignore
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityPassesLimit() {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
//this.aggregator.setTrackedCorrelationIdCapacity(3);
@@ -258,7 +242,7 @@ public class NewAggregatorEndpointTests {
MessageChannelTemplate channelTemplate, MessageChannel outputChannel
) {
Integer product = 1;
for (Message<?> message : group.getMessages()) {
for (Message<?> message : group.getUnmarked()) {
product *= (Integer) message.getPayload();
}
channelTemplate.send(MessageBuilder.withPayload(product).build(), outputChannel);

View File

@@ -0,0 +1,363 @@
/*
* Copyright 2002-2009 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.aggregator;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.store.SimpleMessageStore;
/**
* @author Mark Fisher
* @author Marius Bogoevici
* @author Iwein Fuld
*/
public class ConcurrentAggregatorTests {
private TaskExecutor taskExecutor;
private CorrelatingMessageHandler aggregator;
@Before
public void configureAggregator() {
this.taskExecutor = new SimpleAsyncTaskExecutor();
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(
50), new MultiplyingProcessor());
}
@Test
public void testCompleteGroupWithinTimeout() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message3, latch));
latch.await(10000, TimeUnit.MILLISECONDS);
assertThat(latch.getCount(), is(0l));
Message<?> reply = replyChannel.receive(2000);
assertNotNull(reply);
assertEquals(reply.getPayload(), 105);
}
@Test
@Ignore
// dropped backwards compatibility for duplicate ID's
public void testCompleteGroupWithinTimeoutWithSameId()
throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel,
"ID#1");
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel,
"ID#1");
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel,
"ID#1");
CountDownLatch latch = new CountDownLatch(3);
// for testing the duplication scenario, the messages must be processed
// synchronously
new AggregatorTestTask(this.aggregator, message1, latch).run();
new AggregatorTestTask(this.aggregator, message2, latch).run();
new AggregatorTestTask(this.aggregator, message3, latch).run();
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}
@Test
public void testShouldNotSendPartialResultOnTimeoutByDefault()
throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setDiscardChannel(discardChannel);
QueueChannel replyChannel = new QueueChannel();
Message<?> message = createMessage(3, "ABC", 2, 1, replyChannel, null);
CountDownLatch latch = new CountDownLatch(1);
AggregatorTestTask task = new AggregatorTestTask(this.aggregator,
message, latch);
this.taskExecutor.execute(task);
latch.await(200, TimeUnit.MILLISECONDS);
assertEquals("Task should have completed within timeout", 0, latch
.getCount());
Message<?> reply = replyChannel.receive(100);
assertNull("No message should have been sent normally", reply);
aggregator.forceComplete("ABC");
Message<?> discardedMessage = discardChannel.receive(100);
assertNotNull("A message should have been discarded", discardedMessage);
assertEquals(message, discardedMessage);
}
@Test
public void testShouldSendPartialResultOnTimeoutTrue()
throws InterruptedException {
this.aggregator.setSendPartialResultOnTimeout(true);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
CountDownLatch latch = new CountDownLatch(2);
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator,
message1, latch);
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator,
message2, latch);
this.taskExecutor.execute(task1);
this.taskExecutor.execute(task2);
latch.await(300, TimeUnit.MILLISECONDS);
assertEquals("handlers should have been invoked within time limit", 0,
latch.getCount());
this.aggregator.forceComplete("ABC");
Message<?> reply = replyChannel.receive(100);
assertNotNull("A reply message should have been received", reply);
assertEquals(15, reply.getPayload());
assertNull(task1.getException());
assertNull(task2.getException());
}
@Test
public void testMultipleGroupsSimultaneously() throws InterruptedException {
QueueChannel replyChannel1 = new QueueChannel();
QueueChannel replyChannel2 = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel1, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel1, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel1, null);
Message<?> message4 = createMessage(11, "XYZ", 3, 1, replyChannel2,
null);
Message<?> message5 = createMessage(13, "XYZ", 3, 2, replyChannel2,
null);
Message<?> message6 = createMessage(17, "XYZ", 3, 3, replyChannel2,
null);
CountDownLatch latch = new CountDownLatch(6);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message6, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message5, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
@SuppressWarnings("unchecked")
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
assertNotNull(reply1);
assertThat(reply1.getPayload(), is(105));
@SuppressWarnings("unchecked")
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
assertNotNull(reply2);
assertThat(reply2.getPayload(), is(2431));
}
@Test
@Ignore
// dropped backwards compatibility for setting capacity limit (it's always
// Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityAtLimit() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
// this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
null));
assertEquals(1, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel,
null));
assertEquals(3, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel,
null));
assertEquals(4, replyChannel.receive(100).getPayload());
// next message with same correlation ID is discarded
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel,
null));
assertEquals(2, discardChannel.receive(100).getPayload());
}
@Test
@Ignore
// dropped backwards compatibility for setting capacity limit (it's always
// Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityPassesLimit() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
// this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
null));
assertEquals(1, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel,
null));
assertEquals(2, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel,
null));
assertEquals(3, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel,
null));
assertEquals(4, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel,
null));
assertEquals(5, replyChannel.receive(100).getPayload());
assertNull(discardChannel.receive(0));
}
@Test(expected = MessageHandlingException.class)
public void testExceptionThrownIfNoCorrelationId()
throws InterruptedException {
Message<?> message = createMessage(3, null, 2, 1, new QueueChannel(),
null);
this.aggregator.handleMessage(message);
}
@Test
public void testAdditionalMessageAfterCompletion()
throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(4);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(100);
assertNotNull("A message should be aggregated", reply);
assertThat(((Integer) reply.getPayload()), is(105));
}
@Test
public void testNullReturningAggregator() throws InterruptedException {
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(
50), new NullReturningMessageProcessor());
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1,
latch);
this.taskExecutor.execute(task1);
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2,
latch);
this.taskExecutor.execute(task2);
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3,
latch);
this.taskExecutor.execute(task3);
latch.await(1000, TimeUnit.MILLISECONDS);
assertNull(task1.getException());
assertNull(task2.getException());
assertNull(task3.getException());
Message<?> reply = replyChannel.receive(500);
assertNull(reply);
}
private static Message<?> createMessage(Object payload,
Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel replyChannel, String predefinedId) {
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId).setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)
.setReplyChannel(replyChannel);
if (predefinedId != null) {
builder.setHeader(MessageHeaders.ID, predefinedId);
}
return builder.build();
}
private static class AggregatorTestTask implements Runnable {
private MessageHandler aggregator;
private Message<?> message;
private Exception exception;
private CountDownLatch latch;
AggregatorTestTask(MessageHandler aggregator, Message<?> message,
CountDownLatch latch) {
this.aggregator = aggregator;
this.message = message;
this.latch = latch;
}
public Exception getException() {
return this.exception;
}
public void run() {
try {
this.aggregator.handleMessage(message);
} catch (Exception e) {
e.printStackTrace();
this.exception = e;
} finally {
this.latch.countDown();
}
}
}
private class MultiplyingProcessor implements MessageGroupProcessor {
public void processAndSend(MessageGroup group,
MessageChannelTemplate channelTemplate,
MessageChannel outputChannel) {
Integer product = 1;
for (Message<?> message : group.getUnmarked()) {
product *= (Integer) message.getPayload();
}
channelTemplate.send(MessageBuilder.withPayload(product).build(),
outputChannel);
}
}
private class NullReturningMessageProcessor implements
MessageGroupProcessor {
public void processAndSend(MessageGroup group,
MessageChannelTemplate channelTemplate,
MessageChannel outputChannel) {
// noop
}
}
}

View File

@@ -36,7 +36,6 @@ public class CorrelatingMessageHandlerIntegrationTest {
private CorrelatingMessageHandler defaultHandler = new CorrelatingMessageHandler(store, processor);
@Before
public void setupHandler() {
defaultHandler.setOutputChannel(outputChannel);
@@ -52,14 +51,17 @@ public class CorrelatingMessageHandlerIntegrationTest {
}
@Test
public void completesAfterSequenceComplete() throws Exception {
public void completesAfterThreshold() throws Exception {
defaultHandler.setReleaseStrategy(new MessageCountReleaseStrategy());
MessageChannel discardChannel = mock(MessageChannel.class);
defaultHandler.setDiscardChannel(discardChannel);
Message<?> message1 = correlatedMessage(1, 2, 1);
Message<?> message2 = correlatedMessage(1, 2, 2);
defaultHandler.handleMessage(message1);
verify(outputChannel, never()).send(message1);
defaultHandler.handleMessage(message2);
verify(outputChannel).send(message1);
verify(outputChannel).send(message2);
defaultHandler.handleMessage(message2);
verify(outputChannel, never()).send(message2);
verify(discardChannel).send(message2);
}
@Test
@@ -82,6 +84,17 @@ public class CorrelatingMessageHandlerIntegrationTest {
verify(outputChannel).send(message2a);
}
@Test
public void completesAfterSequenceComplete() throws Exception {
Message<?> message1 = correlatedMessage(1, 2, 1);
Message<?> message2 = correlatedMessage(1, 2, 2);
defaultHandler.handleMessage(message1);
verify(outputChannel, never()).send(message1);
defaultHandler.handleMessage(message2);
verify(outputChannel).send(message1);
verify(outputChannel).send(message2);
}
private Message<?> correlatedMessage(Object correlationId, Integer sequenceSize, Integer sequenceNumber) {
return MessageBuilder.withPayload("test")

View File

@@ -16,17 +16,17 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@@ -34,137 +34,107 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.internal.stubbing.answers.DoesNothing;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Iwein Fuld
* @author Dave Syer
*/
@RunWith(MockitoJUnitRunner.class)
public class CorrelatingMessageHandlerTests {
private CorrelatingMessageHandler handler;
private CorrelatingMessageHandler handler;
@Mock
private MessageStore store;
@Mock
private CorrelationStrategy correlationStrategy;
@Mock
private CorrelationStrategy correlationStrategy;
private ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
@Mock
private CompletionStrategy completionStrategy;
@Mock
private MessageGroupProcessor processor;
@Mock
private MessageGroupProcessor processor;
@Mock
private MessageChannel outputChannel;
@Mock
private MessageChannel outputChannel;
@Before
public void initializeSubject() {
handler = new CorrelatingMessageHandler(new SimpleMessageStore(), correlationStrategy, ReleaseStrategy,
processor);
handler.setOutputChannel(outputChannel);
doAnswer(new DoesNothing()).when(processor).processAndSend(isA(MessageGroup.class),
isA(MessageChannelTemplate.class), eq(outputChannel));
}
@Before
public void initializeSubject() {
handler = new CorrelatingMessageHandler(
store, correlationStrategy, completionStrategy, processor);
handler.setOutputChannel(outputChannel);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock invocation) throws Throwable {
MessageGroup messageGroup = (MessageGroup) invocation.getArguments()[0];
// TODO: remove this?
return null;
}
}).when(processor).processAndSend(isA(MessageGroup.class),
isA(MessageChannelTemplate.class), eq(outputChannel));
}
@Test
public void bufferCompletesNormally() throws Exception {
String correlationKey = "key";
Message<?> message1 = testMessage(correlationKey, 1, 2);
Message<?> message2 = testMessage(correlationKey, 2, 2);
List<Message<?>> storedMessages = new ArrayList<Message<?>>();
@Test
public void bufferCompletesNormally() throws Exception {
String correlationKey = "key";
Message<?> message1 = testMessage(correlationKey, 1);
Message<?> message2 = testMessage(correlationKey, 2);
List<Message<?>> storedMessages = new ArrayList<Message<?>>();
when(correlationStrategy.getCorrelationKey(isA(Message.class))).thenReturn(correlationKey);
when(store.list(correlationKey)).thenReturn(storedMessages);
handler.handleMessage(message1);
storedMessages.add(message1);
verifyLocks(handler, 1);
when(correlationStrategy.getCorrelationKey(isA(Message.class)))
.thenReturn(correlationKey);
handler.handleMessage(message2);
storedMessages.add(message2);
verifyLocks(handler, 0); // lock is removed when group is complete
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1))).thenReturn(false);
verify(correlationStrategy).getCorrelationKey(message1);
verify(correlationStrategy).getCorrelationKey(message2);
verify(processor).processAndSend(isA(MessageGroup.class), isA(MessageChannelTemplate.class), eq(outputChannel));
}
handler.handleMessage(message1);
storedMessages.add(message1);
private void verifyLocks(CorrelatingMessageHandler handler, int lockCount) {
assertEquals(lockCount, ((Map<?, ?>) ReflectionTestUtils.getField(handler, "locks")).size());
}
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1, message2))).thenReturn(true);
handler.handleMessage(message2);
storedMessages.add(message2);
/*
* The next test verifies that when pruning happens after the completing message arrived, but before the group was
* processed locking prevents forced completion and the group completes normally.
*/
verify(store).put(correlationKey, message1);
verify(store).put(correlationKey, message2);
verify(store, times(2)).list(correlationKey);
verify(correlationStrategy).getCorrelationKey(message1);
verify(correlationStrategy).getCorrelationKey(message2);
verify(completionStrategy).isComplete(Arrays.<Message<?>>asList(message1));
verify(completionStrategy).isComplete(Arrays.<Message<?>>asList(message1, message2));
verify(processor).processAndSend(isA(MessageGroup.class),
isA(MessageChannelTemplate.class), eq(outputChannel)
);
}
@Test
public void shouldNotPruneWhileCompleting() throws Exception {
String correlationKey = "key";
final Message<?> message1 = testMessage(correlationKey, 1, 2);
final Message<?> message2 = testMessage(correlationKey, 2, 2);
final List<Message<?>> storedMessages = new ArrayList<Message<?>>();
/*
The next test verifies that when pruning happens after the completing message arrived, but before the group was
processed locking prevents forced completion and the group completes normally.
*/
final CountDownLatch bothMessagesHandled = new CountDownLatch(2);
@Test
public void shouldNotPruneWhileCompleting() throws Exception {
String correlationKey = "key";
final Message<?> message1 = testMessage(correlationKey, 1);
final Message<?> message2 = testMessage(correlationKey, 2);
final List<Message<?>> storedMessages = new ArrayList<Message<?>>();
when(correlationStrategy.getCorrelationKey(isA(Message.class))).thenReturn(correlationKey);
final CountDownLatch bothMessagesHandled = new CountDownLatch(2);
handler.handleMessage(message1);
bothMessagesHandled.countDown();
storedMessages.add(message1);
Executors.newSingleThreadExecutor().submit(new Runnable() {
public void run() {
handler.handleMessage(message2);
storedMessages.add(message2);
bothMessagesHandled.countDown();
}
});
when(store.list(correlationKey)).thenReturn(storedMessages);
Thread.sleep(20);
assertFalse(handler.forceComplete("key"));
when(correlationStrategy.getCorrelationKey(isA(Message.class)))
.thenReturn(correlationKey);
bothMessagesHandled.await();
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1, message2)))
.thenAnswer(new Answer<Boolean>() {
public Boolean answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(50);
return true;
}
}).thenReturn(true);
}
handler.handleMessage(message1);
bothMessagesHandled.countDown();
storedMessages.add(message1);
Executors.newSingleThreadExecutor().submit(new Runnable() {
public void run() {
handler.handleMessage(message2);
storedMessages.add(message2);
bothMessagesHandled.countDown();
}
});
Thread.sleep(20);
assertFalse(handler.forceComplete("key"));
bothMessagesHandled.await();
verify(store).put(correlationKey, message1);
verify(store).put(correlationKey, message2);
verify(store).deleteAll(correlationKey);
}
private Message<?> testMessage(String correlationKey, int sequenceNumber) {
return MessageBuilder.withPayload("test" + sequenceNumber)
.setCorrelationId(correlationKey)
.setSequenceNumber(sequenceNumber).build();
}
private Message<?> testMessage(String correlationKey, int sequenceNumber, int sequenceSize) {
return MessageBuilder.withPayload("test" + sequenceNumber).setCorrelationId(correlationKey).setSequenceNumber(
sequenceNumber).setSequenceSize(sequenceSize).build();
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2002-2009 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.aggregator;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
/**
* @author Alex Peters
* @author Iwein Fuld
*/
public class DefaultMessageAggregatorTests {
DefaultMessageAggregator aggregator = new DefaultMessageAggregator();
@SuppressWarnings("unchecked")
@Test
public void aggregateMessages_withMultiplePayloads_allAsListInResultMsg() {
List<Object> anyPayloads = Arrays.asList("foo", "bar", 123L, new Object());
List<Message<?>> messageGroup = new ArrayList<Message<?>>(anyPayloads.size());
for (Object payload : anyPayloads) {
messageGroup.add(MessageBuilder.withPayload(payload).build());
}
Message<?> result = aggregator.aggregateMessages(messageGroup);
assertThat((List<Object>) result.getPayload(), is(anyPayloads));
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.LinkedHashSet;
import org.junit.Test;
import org.springframework.integration.message.StringMessage;
/**
* @author Mark Fisher
*/
@SuppressWarnings("unchecked")
public class MessageBarrierTests {
@Test
public void testMessageRetrieval() {
MessageBarrier barrier = new MessageBarrier(new LinkedHashSet(), null);
barrier.getMessages().add(new StringMessage("test1"));
assertEquals(1, barrier.getMessages().size());
barrier.getMessages().add(new StringMessage("test2"));
assertEquals(2, barrier.getMessages().size());
}
@Test
public void testTimestamp() {
long before = System.currentTimeMillis();
MessageBarrier barrier = new MessageBarrier(new LinkedHashSet(), null);
long timestamp = barrier.getTimestamp();
assertTrue(before <= timestamp);
long after = System.currentTimeMillis();
assertTrue(after >= timestamp);
}
@Test
public void testEmptyMessageList() {
MessageBarrier barrier = new MessageBarrier(new LinkedHashSet(), null);
assertEquals(0, barrier.getMessages().size());
}
}

View File

@@ -7,9 +7,6 @@ import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
@@ -33,17 +30,17 @@ public class MessageGroupTests {
public void shouldFindSupersedingMessages() {
final Message<?> message1 = MessageBuilder.withPayload("test").setSequenceNumber(1).build();
final Message<?> message2 = MessageBuilder.fromMessage(message1).setSequenceNumber(1).build();
assertThat(group.hasNoMessageSuperseding(message1), is(true));
assertThat(group.add(message1), is(true));
group.add(message2);
assertThat(group.hasNoMessageSuperseding(message1), is(false));
assertThat(group.add(message1), is(false));
}
@Test
public void shouldIgnoreMessagesWithZeroSequenceNumber() {
final Message<?> message1 = MessageBuilder.withPayload("test").build();
final Message<?> message2 = MessageBuilder.fromMessage(message1).build();
assertThat(group.hasNoMessageSuperseding(message1), is(true));
assertThat(group.add(message1), is(true));
group.add(message2);
assertThat(group.hasNoMessageSuperseding(message1), is(true));
assertThat(group.add(message1), is(true));
}
}

View File

@@ -1,202 +0,0 @@
/*
* Copyright 2002-2008 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.aggregator;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.GenericMessage;
import static org.easymock.EasyMock.*;
/**
* @author Marius Bogoevici
* @author Mark Fisher
* @author Iwein Fuld
*/
@SuppressWarnings("unchecked")
public class MethodInvokingAggregatorTests {
private TestAggregator mockAggregator = createMock(TestAggregator.class);
private List<Message<?>> messages = new ArrayList<Message<?>>();
@Test
public void adapterWithNonParameterizedMessageListBasedMethod() {
expect(mockAggregator.doAggregationOnNonParameterizedListOfMessages(isA(List.class))).andStubReturn(
new GenericMessage<String>(""));
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnNonParameterizedListOfMessages");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithWildcardParameterizedMessageBasedMethod() {
expect(mockAggregator.doAggregationOnListOfMessagesParametrizedWithWildcard(isA(List.class))).andStubReturn(
new GenericMessage<String>(""));
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnListOfMessagesParametrizedWithWildcard");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithTypeParameterizedMessageBasedMethod() {
expect(mockAggregator.doAggregationOnListOfMessagesParametrizedWithString(isA(List.class))).andStubReturn(
new GenericMessage<String>(""));
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnListOfMessagesParametrizedWithString");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithPojoBasedMethod() {
expect(mockAggregator.doAggregationOnListOfStrings(isA(List.class))).andStubReturn(
new GenericMessage<String>(""));
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnListOfStrings");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithPojoBasedMethodReturningObject() {
expect(mockAggregator.doAggregationOnListOfStringsReturningLong(isA(List.class))).andStubReturn(6l);
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnListOfStringsReturningLong");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithVoidReturnType() {
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator, "doAggregationWithNoReturn");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test
public void adapterWithNullReturn() {
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
"doAggregationOnListOfStrings");
replay(mockAggregator);
aggregator.aggregateMessages(messages);
verify(mockAggregator);
}
@Test(expected = IllegalArgumentException.class)
public void adapterWithWrongMethodName() {
new MethodInvokingAggregator(mockAggregator, "methodThatDoesNotExist");
}
@Test(expected = IllegalArgumentException.class)
public void invalidParameterTypeUsingMethodName() {
new MethodInvokingAggregator(mockAggregator, "invalidParameterType");
}
@Test(expected = IllegalArgumentException.class)
public void tooManyParametersUsingMethodName() {
new MethodInvokingAggregator(mockAggregator, "tooManyParameters");
}
@Test(expected = IllegalArgumentException.class)
public void notEnoughParametersUsingMethodName() {
new MethodInvokingAggregator(mockAggregator, "notEnoughParameters");
}
@Test(expected = IllegalArgumentException.class)
public void listSubclassParameterUsingMethodName() {
new MethodInvokingAggregator(mockAggregator, "ListSubclassParameter");
}
@Test(expected = IllegalArgumentException.class)
public void invalidParameterTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("invalidParameterType",
String.class));
}
@Test(expected = IllegalArgumentException.class)
public void tooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("tooManyParameters",
List.class, List.class));
}
@Test(expected = IllegalArgumentException.class)
public void notEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("notEnoughParameters",
new Class[] {}));
}
@Test(expected = IllegalArgumentException.class)
public void listSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException {
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("listSubclassParameter",
new Class[] { LinkedList.class }));
}
@Test(expected = IllegalArgumentException.class)
public void nullObject() {
new MethodInvokingAggregator(null, "doesNotMatter");
}
@Test(expected = IllegalArgumentException.class)
public void nullMethodName() {
String methodName = null;
new MethodInvokingAggregator(mockAggregator, methodName);
}
@Test(expected = IllegalArgumentException.class)
public void nullMethodObject() {
Method method = null;
new MethodInvokingAggregator(mockAggregator, method);
}
private interface TestAggregator {
public Message<?> doAggregationOnNonParameterizedListOfMessages(List<Message> messages);
public Message<?> doAggregationOnListOfMessagesParametrizedWithWildcard(List<Message<?>> messages);
public Message<?> doAggregationOnListOfMessagesParametrizedWithString(List<Message<String>> messages);
public Message<?> doAggregationOnListOfStrings(List<String> messages);
public Long doAggregationOnListOfStringsReturningLong(List<String> messages);
public void doAggregationWithNoReturn(List<String> message);
public Message<?> invalidParameterType(String invalid);
public Message<?> tooManyParameters(List<?> c1, List<?> c2);
public Message<?> notEnoughParameters();
public Message<?> listSubclassParameter(LinkedList<?> l1);
}
}

View File

@@ -1,22 +1,7 @@
package org.springframework.integration.aggregator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageBuilder;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
@@ -24,154 +9,324 @@ import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.message.MessageBuilder;
@RunWith(MockitoJUnitRunner.class)
public class MethodInvokingMessageGroupProcessorTests {
@Mock
private MessageGroupListener processedCallback;
@Mock
private MessageChannel outputChannel;
@Mock
private MessageChannel outputChannel;
private List<Message<?>> messagesUpForProcessing = new ArrayList<Message<?>>(3);
private List<Message<?>> messagesUpForProcessing = new ArrayList<Message<?>>(
3);
@Mock
private MessageGroup messageGroupMock;
@Mock
private MessageGroup messageGroupMock;
@Mock
private MessageChannelTemplate channelTemplate;
@Mock
private MessageChannelTemplate channelTemplate;
@Before
public void initializeMessagesUpForProcessing() {
messagesUpForProcessing.add(MessageBuilder.withPayload(1).build());
messagesUpForProcessing.add(MessageBuilder.withPayload(2).build());
messagesUpForProcessing.add(MessageBuilder.withPayload(4).build());
}
@Before
public void initializeMessagesUpForProcessing() {
messagesUpForProcessing.add(MessageBuilder.withPayload(1).build());
messagesUpForProcessing.add(MessageBuilder.withPayload(2).build());
messagesUpForProcessing.add(MessageBuilder.withPayload(4).build());
}
private class AnnotatedAggregatorMethod {
@SuppressWarnings("unused")
private class AnnotatedAggregatorMethod {
@Aggregator
@SuppressWarnings("unused")
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
@Aggregator
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
public String know(List<Integer> flags) {
return "I'm not the one ";
}
}
public String know(List<Integer> flags) {
return "I'm not the one ";
}
}
@Test
public void shouldFindAnnotatedAggregatorMethod() throws Exception {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
new AnnotatedAggregatorMethod());
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
.forClass(Message.class);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
@Test
public void shouldFindAnnotatedAggregatorMethod() throws Exception {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedAggregatorMethod());
@SuppressWarnings("unchecked")
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
@SuppressWarnings("unused")
private class SimpleAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
}
private class SimpleAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
}
@Test
public void shouldFindSimpleAggregatorMethod() throws Exception {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator());
@SuppressWarnings("unchecked")
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
@Test
public void shouldFindSimpleAggregatorMethod() throws Exception {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
new SimpleAggregator());
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
.forClass(Message.class);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
@SuppressWarnings("unused")
private class UnnanotatedAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
public void voidMethodShouldBeIgnored(List<Integer> flags) {
fail("this method should not be invoked");
}
private class UnnanotatedAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
public String methodAcceptingNoCollectionShouldBeIgnored(@Header String irrelevant) {
fail("this method should not be invoked");
return null;
}
}
public void voidMethodShouldBeIgnored(List<Integer> flags) {
fail("this method should not be invoked");
}
@Test
public void shouldFindFittingMethodAmongMultipleUnannotated() {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new UnnanotatedAggregator());
public String methodAcceptingNoCollectionShouldBeIgnored(@Header String irrelevant) {
fail("this method should not be invoked");
return null;
}
}
@SuppressWarnings("unchecked")
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
@Test
public void shouldFindFittingMethodAmongMultipleUnanotated() {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
new UnnanotatedAggregator()
);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
.forClass(Message.class);
@SuppressWarnings("unused")
private class AnnotatedParametersAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel
);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
public String listHeaderShouldBeIgnored(@Header List<Integer> flags) {
fail("this method should not be invoked");
return "";
}
}
private class AnnotatedParametersAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
for (Integer flag : flags) {
result = result | flag;
}
return result;
}
@Test
public void shouldFindFittingMethodAmongMultipleWithAnnotatedParameters() {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
public String listHeaderShouldBeIgnored(@Header List<Integer> flags) {
fail("this method should not be invoked");
return "";
}
}
@SuppressWarnings("unchecked")
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
@Test
public void shouldFindFittingMethodAmongMultipleWithAnnotatedParameters() {
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
new AnnotatedParametersAggregator()
);
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
.forClass(Message.class);
@Test
public void singleAnnotation() throws Exception {
SingleAnnotationTestBean bean = new SingleAnnotationTestBean();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean);
Method method = this.getMethod(aggregator);
Method expected = SingleAnnotationTestBean.class.getMethod("method1", new Class[] { List.class });
assertEquals(expected, method);
}
@Test(expected = IllegalArgumentException.class)
public void multipleAnnotations() {
MultipleAnnotationTestBean bean = new MultipleAnnotationTestBean();
new MethodInvokingMessageGroupProcessor(bean);
}
@Test
public void noAnnotations() throws Exception {
NoAnnotationTestBean bean = new NoAnnotationTestBean();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean);
Method method = this.getMethod(aggregator);
Method expected = NoAnnotationTestBean.class.getMethod("method1", new Class[] { List.class });
assertEquals(expected, method);
}
@Test(expected = IllegalArgumentException.class)
public void multiplePublicMethods() {
MultiplePublicMethodTestBean bean = new MultiplePublicMethodTestBean();
new MethodInvokingMessageGroupProcessor(bean);
}
@Test(expected = IllegalArgumentException.class)
public void noPublicMethods() {
NoPublicMethodTestBean bean = new NoPublicMethodTestBean();
new MethodInvokingMessageGroupProcessor(bean);
}
@Test
public void jdkProxy() {
DirectChannel input = new DirectChannel();
QueueChannel output = new QueueChannel();
GreetingService testBean = new GreetingBean();
ProxyFactory proxyFactory = new ProxyFactory(testBean);
proxyFactory.setProxyTargetClass(false);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
handler.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);
endpoint.start();
Message<?> message = MessageBuilder.withPayload("proxy").setCorrelationId("abc").build();
input.send(message);
assertEquals("hello proxy", output.receive(0).getPayload());
}
@Test
public void cglibProxy() {
DirectChannel input = new DirectChannel();
QueueChannel output = new QueueChannel();
GreetingService testBean = new GreetingBean();
ProxyFactory proxyFactory = new ProxyFactory(testBean);
proxyFactory.setProxyTargetClass(true);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
handler.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);
endpoint.start();
Message<?> message = MessageBuilder.withPayload("proxy").setCorrelationId("abc").build();
input.send(message);
assertEquals("hello proxy", output.receive(0).getPayload());
}
private Method getMethod(MethodInvokingMessageGroupProcessor aggregator) {
Object invoker = new DirectFieldAccessor(aggregator).getPropertyValue("adapter");
return (Method) new DirectFieldAccessor(invoker).getPropertyValue("method");
}
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {
@Aggregator
public String method1(List<String> input) {
return input.get(0);
}
public String method2(List<String> input) {
return input.get(0);
}
}
@SuppressWarnings("unused")
private static class MultipleAnnotationTestBean {
@Aggregator
public String method1(List<String> input) {
return input.get(0);
}
@Aggregator
public String method2(List<String> input) {
return input.get(0);
}
}
@SuppressWarnings("unused")
private static class NoAnnotationTestBean {
public String method1(List<String> input) {
return input.get(0);
}
String method2(List<String> input) {
return input.get(0);
}
}
@SuppressWarnings("unused")
private static class MultiplePublicMethodTestBean {
public String upperCase(String s) {
return s.toUpperCase();
}
public String lowerCase(String s) {
return s.toLowerCase();
}
}
@SuppressWarnings("unused")
private static class NoPublicMethodTestBean {
String lowerCase(String s) {
return s.toLowerCase();
}
}
public interface GreetingService {
String sayHello(List<String> names);
}
public static class GreetingBean implements GreetingService {
private String greeting = "hello";
public void setGreeting(String greeting) {
this.greeting = greeting;
}
@Aggregator
public String sayHello(List<String> names) {
return greeting + " " + names.get(0);
}
}
when(outputChannel.send(isA(Message.class))).thenReturn(true);
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel
);
// verify
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
}
}

View File

@@ -1,341 +0,0 @@
/*
* Copyright 2002-2009 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.aggregator;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author Mark Fisher
* @author Marius Bogoevici
* @author Iwein Fuld
*/
public class NewConcurrentAggregatorEndpointTests {
private TaskExecutor taskExecutor;
private ThreadPoolTaskScheduler taskScheduler;
private CorrelatingMessageHandler aggregator;
@Before
public void configureAggregator() {
this.taskExecutor = new SimpleAsyncTaskExecutor();
this.taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
this.taskScheduler.afterPropertiesSet();
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(50), new MultiplyingProcessor());
this.aggregator.setTaskScheduler(this.taskScheduler);
}
@Test
public void testCompleteGroupWithinTimeout() throws InterruptedException {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
latch.await(10000, TimeUnit.MILLISECONDS);
assertThat(latch.getCount(), is(0l));
Message<?> reply = replyChannel.receive(2000);
assertNotNull(reply);
assertEquals(reply.getPayload(), 105);
}
@Test
@Ignore
//dropped backwards compatibility for duplicate ID's
public void testCompleteGroupWithinTimeoutWithSameId() throws InterruptedException {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, "ID#1");
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, "ID#1");
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, "ID#1");
CountDownLatch latch = new CountDownLatch(3);
//for testing the duplication scenario, the messages must be processed synchronously
new AggregatorTestTask(this.aggregator, message1, latch).run();
new AggregatorTestTask(this.aggregator, message2, latch).run();
new AggregatorTestTask(this.aggregator, message3, latch).run();
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}
@Test
public void testShouldNotSendPartialResultOnTimeoutByDefault() throws InterruptedException {
this.aggregator.start();
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setTimeout(50);
this.aggregator.setReaperInterval(10);
this.aggregator.setDiscardChannel(discardChannel);
QueueChannel replyChannel = new QueueChannel();
Message<?> message = createMessage(3, "ABC", 2, 1, replyChannel, null);
CountDownLatch latch = new CountDownLatch(1);
AggregatorTestTask task = new AggregatorTestTask(this.aggregator, message, latch);
this.taskExecutor.execute(task);
latch.await(2000, TimeUnit.MILLISECONDS);
assertEquals("Task should have completed within timeout", 0, latch.getCount());
Message<?> reply = replyChannel.receive(100);
assertNull("No message should have been sent normally", reply);
Message<?> discardedMessage = discardChannel.receive(1000);
assertNotNull("A message should have been discarded", discardedMessage);
assertEquals(message, discardedMessage);
}
@Test
public void testShouldSendPartialResultOnTimeoutTrue() throws InterruptedException {
this.aggregator.setTimeout(500);
this.aggregator.setReaperInterval(10);
this.aggregator.setSendPartialResultOnTimeout(true);
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
CountDownLatch latch = new CountDownLatch(2);
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator, message1, latch);
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator, message2, latch);
this.taskExecutor.execute(task1);
this.taskExecutor.execute(task2);
latch.await(3000, TimeUnit.MILLISECONDS);
assertEquals("handlers should have been invoked within time limit", 0, latch.getCount());
Message<?> reply = replyChannel.receive(3000);
assertNotNull("A reply message should have been received", reply);
assertEquals(15, reply.getPayload());
assertNull(task1.getException());
assertNull(task2.getException());
}
@Test
public void testMultipleGroupsSimultaneously() throws InterruptedException {
this.aggregator.start();
QueueChannel replyChannel1 = new QueueChannel();
QueueChannel replyChannel2 = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel1, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel1, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel1, null);
Message<?> message4 = createMessage(11, "XYZ", 3, 1, replyChannel2, null);
Message<?> message5 = createMessage(13, "XYZ", 3, 2, replyChannel2, null);
Message<?> message6 = createMessage(17, "XYZ", 3, 3, replyChannel2, null);
CountDownLatch latch = new CountDownLatch(6);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message6, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message5, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
assertNotNull(reply1);
assertThat(reply1.getPayload(), is(105));
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
assertNotNull(reply2);
assertThat(reply2.getPayload(), is(2431));
}
@Test
@Ignore
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityAtLimit() {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
//this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
assertEquals(1, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel, null));
assertEquals(3, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel, null));
assertEquals(4, replyChannel.receive(100).getPayload());
//next message with same correlation ID is discarded
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel, null));
assertEquals(2, discardChannel.receive(100).getPayload());
}
@Test
@Ignore
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
public void testTrackedCorrelationIdsCapacityPassesLimit() {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
//this.aggregator.setTrackedCorrelationIdCapacity(3);
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
assertEquals(1, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel, null));
assertEquals(2, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel, null));
assertEquals(3, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel, null));
assertEquals(4, replyChannel.receive(100).getPayload());
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel, null));
assertEquals(5, replyChannel.receive(100).getPayload());
assertNull(discardChannel.receive(0));
}
@Test(expected = MessageHandlingException.class)
public void testExceptionThrownIfNoCorrelationId() throws InterruptedException {
this.aggregator.start();
Message<?> message = createMessage(3, null, 2, 1, new QueueChannel(), null);
this.aggregator.handleMessage(message);
}
@Test
public void testAdditionalMessageAfterCompletion() throws InterruptedException {
this.aggregator.start();
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(4);
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(100);
assertNotNull("A message should be aggregated", reply);
assertThat(((Integer) reply.getPayload()), is(105));
}
@Test
public void testNullReturningAggregator() throws InterruptedException {
this.aggregator.start();
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(50), new NullReturningMessageProcessor());
this.aggregator.setTaskScheduler(this.taskScheduler);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1, latch);
this.taskExecutor.execute(task1);
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2, latch);
this.taskExecutor.execute(task2);
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3, latch);
this.taskExecutor.execute(task3);
latch.await(1000, TimeUnit.MILLISECONDS);
assertNull(task1.getException());
assertNull(task2.getException());
assertNull(task3.getException());
Message<?> reply = replyChannel.receive(500);
assertNull(reply);
}
private static Message<?> createMessage(Object payload, Object correlationId,
int sequenceSize, int sequenceNumber, MessageChannel replyChannel, String predefinedId) {
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId)
.setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)
.setReplyChannel(replyChannel);
if (predefinedId != null) {
builder.setHeader(MessageHeaders.ID, predefinedId);
}
return builder.build();
}
private static class AggregatorTestTask implements Runnable {
private MessageHandler aggregator;
private Message<?> message;
private Exception exception;
private CountDownLatch latch;
AggregatorTestTask(MessageHandler aggregator, Message<?> message, CountDownLatch latch) {
this.aggregator = aggregator;
this.message = message;
this.latch = latch;
}
public Exception getException() {
return this.exception;
}
public void run() {
try {
this.aggregator.handleMessage(message);
}
catch (Exception e) {
e.printStackTrace();
this.exception = e;
}
finally {
this.latch.countDown();
}
}
}
@After
public void stopTaskScheduler() {
if (this.taskScheduler != null) this.taskScheduler.destroy();
if (this.aggregator != null) this.aggregator.stop();
}
private class MultiplyingProcessor implements MessageGroupProcessor {
public void processAndSend(MessageGroup group,
MessageChannelTemplate channelTemplate, MessageChannel outputChannel
) {
Integer product = 1;
for (Message<?> message : group.getMessages()) {
product *= (Integer) message.getPayload();
}
channelTemplate.send(MessageBuilder.withPayload(product).build(), outputChannel);
}
}
private class NullReturningMessageProcessor implements MessageGroupProcessor {
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
//noop
}
}
}

View File

@@ -1,279 +0,0 @@
/*
* Copyright 2002-2009 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.aggregator;
import org.junit.After;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.Ignore;
import static org.junit.matchers.JUnitMatchers.*;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import static java.util.Arrays.*;
/**
* @author Marius Bogoevici
* @author Alex Peters
* @author Iwein Fuld
*/
public class NewResequencerTests {
private CorrelatingMessageHandler resequencer;
private ThreadPoolTaskScheduler taskScheduler;
private DefaultResequencerStrategies resequencerStrategies;
@Before
public void configureResequencer() {
this.resequencerStrategies = new DefaultResequencerStrategies();
MessageStore store = new SimpleMessageStore(30);
this.resequencer = new CorrelatingMessageHandler(store, resequencerStrategies, resequencerStrategies, resequencerStrategies);
this.taskScheduler = TestUtils.createTaskScheduler(10);
this.resequencer.setTaskScheduler(taskScheduler);
this.taskScheduler.afterPropertiesSet();
this.resequencer.start();
}
@Test
public void testBasicResequencing() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 3, 2, replyChannel);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message3);
this.resequencer.handleMessage(message2);
Message<?> reply1 = replyChannel.receive(0);
Message<?> reply2 = replyChannel.receive(0);
Message<?> reply3 = replyChannel.receive(0);
assertNotNull(reply1);
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
assertNotNull(reply2);
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
assertNotNull(reply3);
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
}
@Test
public void testResequencingWithDuplicateMessages() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 3, 2, replyChannel);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message3);
this.resequencer.handleMessage(message3);
this.resequencer.handleMessage(message2);
Message<?> reply1 = replyChannel.receive(0);
Message<?> reply2 = replyChannel.receive(0);
Message<?> reply3 = replyChannel.receive(0);
assertNotNull(reply1);
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
assertNotNull(reply2);
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
assertNotNull(reply3);
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
}
@Test
@Ignore // TODO: fix this
public void testResequencingWithIncompleteSequenceRelease() throws InterruptedException {
this.resequencerStrategies.setReleasePartialSequences(true);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 4, 4, replyChannel);
Message<?> message4 = createMessage("XYZ", "ABC", 4, 3, replyChannel);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
this.resequencer.handleMessage(message3);
Message<?> reply1 = replyChannel.receive(0);
Message<?> reply2 = replyChannel.receive(0);
Message<?> reply3 = replyChannel.receive(0);
// only messages 1 and 2 should have been received by now
assertNotNull(reply1);
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
assertNotNull(reply2);
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
System.err.println(reply3);
assertNull(reply3);
// when sending the last message, the whole sequence must have been sent
this.resequencer.handleMessage(message4);
reply3 = replyChannel.receive(0);
Message<?> reply4 = replyChannel.receive(0);
assertNotNull(reply3);
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
assertNotNull(reply4);
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
}
@Test
public void testResequencingWithDiscard() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel);
this.resequencer.setSendPartialResultOnTimeout(false);
this.resequencerStrategies.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
this.resequencer.forceComplete("ABC");
Message<?> reply1 = discardChannel.receive(0);
Message<?> reply2 = discardChannel.receive(0);
Message<?> reply3 = discardChannel.receive(0);
// messages 1 and 2 should have been received by now in no particular order
assertNotNull(reply1);
assertNotNull(reply2);
Integer sequenceNo1 = reply1.getHeaders().getSequenceNumber();
Integer sequenceNo2 = reply2.getHeaders().getSequenceNumber();
assertThat(asList(sequenceNo1, sequenceNo2), hasItems(1, 2));
assertNull(reply3);
// when sending the last message, it waits in the buffer for retries of the other two
this.resequencer.handleMessage(message3);
reply3 = discardChannel.receive(0);
assertNull(reply3);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
reply1 = replyChannel.receive(0);
reply2 = replyChannel.receive(0);
reply3 = replyChannel.receive(0);
assertNotNull(reply1);
assertThat(reply1.getHeaders().getSequenceNumber(), is(new Integer(1)));
assertNotNull(reply2);
assertThat(reply2.getHeaders().getSequenceNumber(), is(new Integer(2)));
assertNotNull(reply3);
assertThat(reply3.getHeaders().getSequenceNumber(), is(new Integer(3)));
}
@Test
@Ignore
//different sequence sizes are not supported
public void testResequencingWithDifferentSequenceSizes() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 5, 1, replyChannel);
this.resequencer.setSendPartialResultOnTimeout(false);
//this.resequencer.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
//this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
Message<?> reply1 = discardChannel.receive(0);
Message<?> reply2 = discardChannel.receive(0);
// only messages 1 - with sequence number 2 - should have been received by now
// the other has been discarded
assertNotNull(reply1);
assertEquals(new Integer(2), reply1.getHeaders().getSequenceNumber());
assertNull(reply2);
}
@Test
public void testResequencingWithWrongSequenceSizeAndNumber() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 2, 4, replyChannel);
this.resequencer.setSendPartialResultOnTimeout(false);
//this.resequencer.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
//this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
Message<?> reply1 = discardChannel.receive(0);
// No message has been received - the message has been rejected.
assertNull(reply1);
}
@Test
public void testResequencingWithCompleteSequenceRelease() throws InterruptedException {
//this.resequencer.setReleasePartialSequences(false);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 4, 4, replyChannel);
Message<?> message4 = createMessage("XYZ", "ABC", 4, 3, replyChannel);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
this.resequencer.handleMessage(message3);
Message<?> reply1 = replyChannel.receive(0);
Message<?> reply2 = replyChannel.receive(0);
Message<?> reply3 = replyChannel.receive(0);
// no messages should have been received yet
assertNull(reply1);
assertNull(reply2);
assertNull(reply3);
// after sending the last message, the whole sequence should have been sent
this.resequencer.handleMessage(message4);
reply1 = replyChannel.receive(0);
reply2 = replyChannel.receive(0);
reply3 = replyChannel.receive(0);
Message<?> reply4 = replyChannel.receive(0);
assertNotNull(reply1);
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
assertNotNull(reply2);
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
assertNotNull(reply3);
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
assertNotNull(reply4);
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
}
@Test
public void testRemovalOfBarrierWhenLastMessageOfSequenceArrives() {
QueueChannel replyChannel = new QueueChannel();
String correlationId = "ABC";
Message<?> message1 = createMessage("123", correlationId, 1, 1,
replyChannel);
resequencer.handleMessage(message1);
//assertThat(resequencer.barriers.containsKey(correlationId), is(false));
}
private static Message<?> createMessage(String payload, Object correlationId,
int sequenceSize, int sequenceNumber, MessageChannel replyChannel) {
return MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId)
.setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)
.setReplyChannel(replyChannel)
.build();
}
@After
public void stopTaskScheduler() {
this.resequencer.stop();
this.taskScheduler.destroy();
}
}

View File

@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -24,178 +23,157 @@ import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.GenericMessage;
/**
* @author Marius Bogoevici
*/
public class CompletionStrategyAdapterTests {
private SimpleCompletionStrategy simpleCompletionStrategy;
public class ReleaseStrategyAdapterTests {
private SimpleReleaseStrategy simpleReleaseStrategy;
@Before
public void setUp() {
simpleCompletionStrategy = new SimpleCompletionStrategy();
simpleReleaseStrategy = new SimpleReleaseStrategy();
}
@Test
public void testTrueConvertedProperly() {
CompletionStrategyAdapter adapter = new CompletionStrategyAdapter(new AlwaysTrueCompletionStrategy(),
ReleaseStrategyAdapter adapter = new ReleaseStrategyAdapter(new AlwaysTrueReleaseStrategy(),
"checkCompleteness");
Assert.assertTrue(adapter.isComplete(new ArrayList<Message<?>>()));
Assert.assertTrue(adapter.canRelease(createListOfMessages(0)));
}
@Test
public void testFalseConvertedProperly() {
CompletionStrategyAdapter adapter = new CompletionStrategyAdapter(new AlwaysFalseCompletionStrategy(),
ReleaseStrategyAdapter adapter = new ReleaseStrategyAdapter(new AlwaysFalseReleaseStrategy(),
"checkCompleteness");
Assert.assertTrue(!adapter.isComplete(new ArrayList<Message<?>>()));
Assert.assertTrue(!adapter.canRelease(createListOfMessages(0)));
}
@Test
public void testAdapterWithNonParameterizedMessageListBasedMethod() {
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
"checkCompletenessOnNonParameterizedListOfMessages");
List<Message<?>> messages = createListOfMessages();
Assert.assertTrue(adapter.isComplete(messages));
MessageGroup messages = createListOfMessages(3);
Assert.assertTrue(adapter.canRelease(messages));
}
@Test
public void testAdapterWithWildcardParametrizedMessageBasedMethod() {
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
"checkCompletenessOnListOfMessagesParametrizedWithWildcard");
List<Message<?>> messages = createListOfMessages();
Assert.assertTrue(adapter.isComplete(messages));
MessageGroup messages = createListOfMessages(3);
Assert.assertTrue(adapter.canRelease(messages));
}
@Test
public void testAdapterWithTypeParametrizedMessageBasedMethod() {
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
"checkCompletenessOnListOfMessagesParametrizedWithString");
List<Message<?>> messages = createListOfMessages();
Assert.assertTrue(adapter.isComplete(messages));
MessageGroup messages = createListOfMessages(3);
Assert.assertTrue(adapter.canRelease(messages));
}
@Test
public void testAdapterWithPojoBasedMethod() {
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
"checkCompletenessOnListOfStrings");
List<Message<?>> messages = createListOfMessages();
Assert.assertTrue(adapter.isComplete(messages));
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy, "checkCompletenessOnListOfStrings");
MessageGroup messages = createListOfMessages(3);
Assert.assertTrue(adapter.canRelease(messages));
}
@Test
public void testAdapterWithPojoBasedMethodReturningObject() {
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
"checkCompletenessOnListOfStrings");
List<Message<?>> messages = createListOfMessages();
Assert.assertTrue(adapter.isComplete(messages));
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy, "checkCompletenessOnListOfStrings");
MessageGroup messages = createListOfMessages(3);
Assert.assertTrue(adapter.canRelease(messages));
}
@Test(expected = IllegalArgumentException.class)
public void testAdapterWithWrongMethodName() {
new CompletionStrategyAdapter(simpleCompletionStrategy, "methodThatDoesNotExist");
new ReleaseStrategyAdapter(simpleReleaseStrategy, "methodThatDoesNotExist");
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidParameterTypeUsingMethodName() {
new CompletionStrategyAdapter(simpleCompletionStrategy, "invalidParameterType");
new ReleaseStrategyAdapter(simpleReleaseStrategy, "invalidParameterType");
}
@Test(expected = IllegalArgumentException.class)
public void testTooManyParametersUsingMethodName() {
new CompletionStrategyAdapter(simpleCompletionStrategy, "tooManyParameters");
new ReleaseStrategyAdapter(simpleReleaseStrategy, "tooManyParameters");
}
@Test(expected = IllegalArgumentException.class)
public void testNotEnoughParametersUsingMethodName() {
new CompletionStrategyAdapter(simpleCompletionStrategy, "notEnoughParameters");
new ReleaseStrategyAdapter(simpleReleaseStrategy, "notEnoughParameters");
}
@Test(expected = IllegalArgumentException.class)
public void testListSubclassParameterUsingMethodName() {
new CompletionStrategyAdapter(simpleCompletionStrategy, "ListSubclassParameter");
new ReleaseStrategyAdapter(simpleReleaseStrategy, "ListSubclassParameter");
}
@Test(expected = IllegalArgumentException.class)
public void testWrongReturnType() throws SecurityException, NoSuchMethodError {
new CompletionStrategyAdapter(simpleCompletionStrategy, "wrongReturnType");
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidParameterTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
new MethodInvokingAggregator(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
"invalidParameterType", String.class));
new ReleaseStrategyAdapter(simpleReleaseStrategy, "wrongReturnType");
}
@Test(expected = IllegalArgumentException.class)
public void testTooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
"tooManyParameters", List.class, List.class));
}
@Test(expected = IllegalArgumentException.class)
public void testNotEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
"notEnoughParameters", new Class[] {}));
}
@Test(expected = IllegalArgumentException.class)
public void testListSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException {
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
"ListSubclassParameter", new Class[] { LinkedList.class }));
}
@Test(expected = IllegalArgumentException.class)
public void testWrongReturnTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
"wrongReturnType", new Class[] { List.class }));
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod("wrongReturnType",
new Class[] { List.class }));
}
@Test(expected = IllegalArgumentException.class)
public void testNullObject() {
new MethodInvokingAggregator(null, "doesNotMatter");
}
@Test(expected = IllegalArgumentException.class)
public void testNullMethodName() {
String methodName = null;
new MethodInvokingAggregator(simpleCompletionStrategy, methodName);
}
@Test(expected = IllegalArgumentException.class)
public void testNullMethodObject() {
Method method = null;
new MethodInvokingAggregator(simpleCompletionStrategy, method);
}
private static List<Message<?>> createListOfMessages() {
private static MessageGroup createListOfMessages(int size) {
List<Message<?>> messages = new ArrayList<Message<?>>();
messages.add(new GenericMessage<String>("123"));
messages.add(new GenericMessage<String>("456"));
messages.add(new GenericMessage<String>("789"));
return messages;
if (size > 0) {
messages.add(new GenericMessage<String>("123"));
}
if (size > 1) {
messages.add(new GenericMessage<String>("456"));
}
if (size > 2) {
messages.add(new GenericMessage<String>("789"));
}
return new MessageGroup(messages, "ABC");
}
private static class AlwaysTrueCompletionStrategy {
@SuppressWarnings("unused")
private static class AlwaysTrueReleaseStrategy {
public boolean checkCompleteness(List<Message<?>> messages) {
return true;
}
}
private static class AlwaysFalseCompletionStrategy {
@SuppressWarnings("unused")
private static class AlwaysFalseReleaseStrategy {
public boolean checkCompleteness(List<Message<?>> messages) {
return false;
}
}
private static class SimpleCompletionStrategy {
@SuppressWarnings("unused")
private static class SimpleReleaseStrategy {
public boolean checkCompletenessOnNonParameterizedListOfMessages(List<Message<?>> messages) {
Assert.assertTrue(messages.size() > 0);
@@ -207,13 +185,13 @@ public class CompletionStrategyAdapterTests {
return messages.size() > messages.iterator().next().getHeaders().getSequenceSize();
}
public boolean checkCompletenessOnListOfMessagesParametrizedWithString(
List<Message<String>> messages) {
public boolean checkCompletenessOnListOfMessagesParametrizedWithString(List<Message<String>> messages) {
Assert.assertTrue(messages.size() > 0);
return messages.size() > messages.iterator().next().getHeaders().getSequenceSize();
}
// Example for the case when completeness is checked on the structure of the data
// Example for the case when completeness is checked on the structure of
// the data
public boolean checkCompletenessOnListOfStrings(List<String> messages) {
StringBuffer buffer = new StringBuffer();
for (String content : messages) {

View File

@@ -16,22 +16,23 @@
package org.springframework.integration.aggregator;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
/**
* @author Marius Bogoevici
@@ -39,23 +40,20 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
*/
public class ResequencerTests {
private Resequencer resequencer;
private ThreadPoolTaskScheduler taskScheduler;
private CorrelatingMessageHandler resequencer;
private Resequencer processor = new Resequencer();
private MessageStore store = new SimpleMessageStore();
@Before
public void configureResequencer() {
this.resequencer = new Resequencer();
this.taskScheduler = TestUtils.createTaskScheduler(10);
this.resequencer.setTaskScheduler(taskScheduler);
this.taskScheduler.afterPropertiesSet();
this.resequencer.start();
this.resequencer = new CorrelatingMessageHandler(store, processor, processor, processor);
}
@Test
public void testBasicResequencing() throws InterruptedException {
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
@@ -76,7 +74,7 @@ public class ResequencerTests {
@Test
public void testResequencingWithDuplicateMessages() {
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
@@ -96,11 +94,9 @@ public class ResequencerTests {
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
}
@Test
public void testResequencingWithIncompleteSequenceRelease() throws InterruptedException {
this.resequencer.setReleasePartialSequences(true);
this.processor.setReleasePartialSequences(true);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
@@ -127,7 +123,7 @@ public class ResequencerTests {
assertNotNull(reply4);
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
}
@Test
public void testResequencingWithDiscard() throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
@@ -135,59 +131,60 @@ public class ResequencerTests {
Message<?> message2 = createMessage("456", "ABC", 4, 1, null);
Message<?> message3 = createMessage("789", "ABC", 4, 4, null);
this.resequencer.setSendPartialResultOnTimeout(false);
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
this.resequencer.forceComplete("ABC");
Message<?> reply1 = discardChannel.receive(0);
Message<?> reply2 = discardChannel.receive(0);
Message<?> reply3 = discardChannel.receive(0);
// only messages 1 and 2 should have been received by now
assertNotNull(reply1);
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
assertNotNull(reply2);
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
assertNull(reply3);
ArrayList<Integer> sequence = new ArrayList<Integer>(Arrays.asList(reply1.getHeaders().getSequenceNumber(), reply2.getHeaders()
.getSequenceNumber()));
Collections.sort(sequence);
assertEquals("[1, 2]", sequence.toString());
// when sending the last message, the whole sequence must have been sent
this.resequencer.handleMessage(message3);
reply3 = discardChannel.receive(0);
assertNull(reply3);
}
@Test
public void testResequencingWithDifferentSequenceSizes() throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, null);
Message<?> message2 = createMessage("456", "ABC", 5, 1, null);
this.resequencer.setSendPartialResultOnTimeout(false);
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
this.resequencer.handleMessage(message2);
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
Message<?> reply1 = discardChannel.receive(0);
Message<?> reply2 = discardChannel.receive(0);
// only messages 1 - with sequence number 2 - should have been received by now
// the other has been discarded
assertNotNull(reply1);
assertEquals(new Integer(2), reply1.getHeaders().getSequenceNumber());
assertNull(reply2);
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
Message<?> discard1 = discardChannel.receive(0);
Message<?> discard2 = discardChannel.receive(0);
// message2 has been discarded because it came in with the wrong sequence size
assertNotNull(discard1);
assertEquals(new Integer(1), discard1.getHeaders().getSequenceNumber());
assertNull(discard2);
}
@Test
public void testResequencingWithWrongSequenceSizeAndNumber() throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 2, 4, null);
this.resequencer.setSendPartialResultOnTimeout(false);
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
this.resequencer.setDiscardChannel(discardChannel);
this.resequencer.setTimeout(90000);
this.resequencer.handleMessage(message1);
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
Message<?> reply1 = discardChannel.receive(0);
// No message has been received - the message has been rejected.
assertNull(reply1);
@@ -195,7 +192,7 @@ public class ResequencerTests {
@Test
public void testResequencingWithCompleteSequenceRelease() throws InterruptedException {
this.resequencer.setReleasePartialSequences(false);
this.processor.setReleasePartialSequences(false);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
@@ -226,32 +223,20 @@ public class ResequencerTests {
assertNotNull(reply4);
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
}
@Test
public void testRemovalOfBarrierWhenLastMessageOfSequenceArrives() {
QueueChannel replyChannel = new QueueChannel();
String correlationId = "ABC";
Message<?> message1 = createMessage("123", correlationId, 1, 1,
replyChannel);
Message<?> message1 = createMessage("123", correlationId, 1, 1, replyChannel);
resequencer.handleMessage(message1);
assertThat(resequencer.barriers.containsKey(correlationId), is(false));
assertTrue(store.list(correlationId).isEmpty());
}
private static Message<?> createMessage(String payload, Object correlationId,
int sequenceSize, int sequenceNumber, MessageChannel replyChannel) {
return MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId)
.setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)
.setReplyChannel(replyChannel)
.build();
}
@After
public void stopTaskScheduler() {
this.resequencer.stop();
this.taskScheduler.destroy();
private static Message<?> createMessage(String payload, Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel replyChannel) {
return MessageBuilder.withPayload(payload).setCorrelationId(correlationId).setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber).setReplyChannel(replyChannel).build();
}
}

View File

@@ -19,28 +19,23 @@ package org.springframework.integration.aggregator;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.integration.aggregator.SequenceSizeCompletionStrategy;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
/**
* @author Mark Fisher
*/
public class SequenceSizeCompletionStrategyTests {
public class SequenceSizeReleaseStrategyTests {
@Test
public void testIncompleteList() {
Message<String> message = MessageBuilder.withPayload("test1")
.setSequenceSize(2).build();
List<Message<?>> messages = new ArrayList<Message<?>>();
MessageGroup messages = new MessageGroup("FOO");
messages.add(message);
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
assertFalse(completionStrategy.isComplete(messages));
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
assertFalse(ReleaseStrategy.canRelease(messages));
}
@Test
@@ -49,23 +44,17 @@ public class SequenceSizeCompletionStrategyTests {
.setSequenceSize(2).build();
Message<String> message2 = MessageBuilder.withPayload("test2")
.setSequenceSize(2).build();
List<Message<?>> messages = new ArrayList<Message<?>>();
MessageGroup messages = new MessageGroup("FOO");
messages.add(message1);
messages.add(message2);
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
assertTrue(completionStrategy.isComplete(messages));
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
assertTrue(ReleaseStrategy.canRelease(messages));
}
@Test
public void testEmptyList() {
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
assertFalse(completionStrategy.isComplete(new ArrayList<Message<?>>()));
}
@Test
public void testNullList() {
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
assertFalse(completionStrategy.isComplete(null));
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
assertTrue(ReleaseStrategy.canRelease(new MessageGroup("FOO")));
}
}

View File

@@ -77,7 +77,7 @@ public class AggregatorParserTests {
public void testPropertyAssignment() throws Exception {
EventDrivenConsumer endpoint =
(EventDrivenConsumer) context.getBean("completelyDefinedAggregator");
CompletionStrategy completionStrategy = (CompletionStrategy) context.getBean("completionStrategy");
ReleaseStrategy ReleaseStrategy = (ReleaseStrategy) context.getBean("releaseStrategy");
CorrelationStrategy correlationStrategy = (CorrelationStrategy) context.getBean("correlationStrategy");
MessageChannel outputChannel = (MessageChannel) context.getBean("outputChannel");
MessageChannel discardChannel = (MessageChannel) context.getBean("discardChannel");
@@ -88,8 +88,8 @@ public class AggregatorParserTests {
assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method",
expectedMethod, ((MessageListMethodAdapter) new DirectFieldAccessor(accessor.getPropertyValue("outputProcessor")).getPropertyValue("adapter")).getMethod());
assertEquals(
"The AggregatorEndpoint is not injected with the appropriate CompletionStrategy instance",
completionStrategy, accessor.getPropertyValue("completionStrategy"));
"The AggregatorEndpoint is not injected with the appropriate ReleaseStrategy instance",
ReleaseStrategy, accessor.getPropertyValue("ReleaseStrategy"));
assertEquals("The AggregatorEndpoint is not injected with the appropriate CorrelationStrategy instance",
correlationStrategy, accessor.getPropertyValue("correlationStrategy"));
Assert.assertEquals("The AggregatorEndpoint is not injected with the appropriate output channel",
@@ -101,10 +101,6 @@ public class AggregatorParserTests {
Assert.assertEquals(
"The AggregatorEndpoint is not configured with the appropriate 'send partial results on timeout' flag",
true, accessor.getPropertyValue("sendPartialResultOnTimeout"));
Assert.assertEquals("The AggregatorEndpoint is not configured with the appropriate reaper interval",
135l, accessor.getPropertyValue("reaperInterval"));
Assert.assertEquals("The AggregatorEndpoint is not configured with the appropriate timeout",
42l, accessor.getPropertyValue("timeout"));
}
@Test
@@ -119,7 +115,7 @@ public class AggregatorParserTests {
input.send(message);
}
PollableChannel outputChannel = (PollableChannel) context.getBean("outputChannel");
Message<?> response = outputChannel.receive();
Message<?> response = outputChannel.receive(10);
Assert.assertEquals(6l, response.getPayload());
}
@@ -129,38 +125,38 @@ public class AggregatorParserTests {
}
@Test(expected=BeanCreationException.class)
public void testDuplicateCompletionStrategyDefinition() {
public void testDuplicateReleaseStrategyDefinition() {
context = new ClassPathXmlApplicationContext(
"completionStrategyMethodWithMissingReference.xml", this.getClass());
"ReleaseStrategyMethodWithMissingReference.xml", this.getClass());
}
@Test
public void testAggregatorWithPojoCompletionStrategy() {
MessageChannel input = (MessageChannel) context.getBean("aggregatorWithPojoCompletionStrategyInput");
public void testAggregatorWithPojoReleaseStrategy() {
MessageChannel input = (MessageChannel) context.getBean("aggregatorWithPojoReleaseStrategyInput");
EventDrivenConsumer endpoint =
(EventDrivenConsumer) context.getBean("aggregatorWithPojoCompletionStrategy");
CompletionStrategy completionStrategy = (CompletionStrategy) new DirectFieldAccessor(
new DirectFieldAccessor(endpoint).getPropertyValue("handler")).getPropertyValue("completionStrategy");
Assert.assertTrue(completionStrategy instanceof CompletionStrategyAdapter);
DirectFieldAccessor completionStrategyAccessor = new DirectFieldAccessor(completionStrategy);
MethodInvoker invoker = (MethodInvoker) completionStrategyAccessor.getPropertyValue("invoker");
Assert.assertTrue(new DirectFieldAccessor(invoker).getPropertyValue("object") instanceof MaxValueCompletionStrategy);
Assert.assertTrue(((Method) completionStrategyAccessor.getPropertyValue("method")).getName().equals("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 0, 0, null));
input.send(createMessage(2l, "correllationId", 0, 1, null));
input.send(createMessage(3l, "correllationId", 0, 2, null));
(EventDrivenConsumer) context.getBean("aggregatorWithPojoReleaseStrategy");
ReleaseStrategy ReleaseStrategy = (ReleaseStrategy) new DirectFieldAccessor(
new DirectFieldAccessor(endpoint).getPropertyValue("handler")).getPropertyValue("ReleaseStrategy");
Assert.assertTrue(ReleaseStrategy instanceof ReleaseStrategyAdapter);
DirectFieldAccessor ReleaseStrategyAccessor = new DirectFieldAccessor(ReleaseStrategy);
MethodInvoker invoker = (MethodInvoker) ReleaseStrategyAccessor.getPropertyValue("invoker");
Assert.assertTrue(new DirectFieldAccessor(invoker).getPropertyValue("object") instanceof MaxValueReleaseStrategy);
Assert.assertTrue(((Method) ReleaseStrategyAccessor.getPropertyValue("method")).getName().equals("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 4, 0, null));
input.send(createMessage(2l, "correllationId", 4, 1, null));
input.send(createMessage(3l, "correllationId", 4, 2, null));
PollableChannel outputChannel = (PollableChannel) context.getBean("outputChannel");
Message<?> reply = outputChannel.receive(0);
Assert.assertNull(reply);
input.send(createMessage(5l, "correllationId", 0, 3, null));
input.send(createMessage(5l, "correllationId", 4, 3, null));
reply = outputChannel.receive(0);
Assert.assertNotNull(reply);
assertEquals(11l, reply.getPayload());
}
@Test(expected = BeanCreationException.class)
public void testAggregatorWithInvalidCompletionStrategyMethod() {
context = new ClassPathXmlApplicationContext("invalidCompletionStrategyMethod.xml", this.getClass());
public void testAggregatorWithInvalidReleaseStrategyMethod() {
context = new ClassPathXmlApplicationContext("invalidReleaseStrategyMethod.xml", this.getClass());
}

View File

@@ -12,7 +12,7 @@
<beans:bean id="pojoCorrelationStrategy"
class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$PojoCorrelationStrategy"/>
<beans:bean id="completionStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountCompletionStrategy">
<beans:bean id="releaseStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountReleaseStrategy">
<beans:constructor-arg value="3"/>
</beans:bean>
@@ -31,13 +31,13 @@
</channel>
<aggregator ref="aggregator"
completion-strategy="completionStrategy"
release-strategy="releaseStrategy"
correlation-strategy="correlationStrategy"
input-channel="inputChannel"
output-channel="outputChannel"/>
<aggregator ref="aggregator"
completion-strategy="completionStrategy"
release-strategy="releaseStrategy"
correlation-strategy="pojoCorrelationStrategy" correlation-strategy-method="correlate"
input-channel="pojoInputChannel"
output-channel="pojoOutputChannel"/>

View File

@@ -21,7 +21,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.aggregator.CompletionStrategy;
import org.springframework.integration.aggregator.MessageGroup;
import org.springframework.integration.aggregator.ReleaseStrategy;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.channel.PollableChannel;
@@ -31,7 +32,6 @@ import org.springframework.integration.message.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertThat;
@@ -45,108 +45,125 @@ import static org.junit.matchers.JUnitMatchers.containsString;
@RunWith(SpringJUnit4ClassRunner.class)
public class AggregatorWithCorrelationStrategyTests {
@Autowired
@Qualifier("inputChannel")
MessageChannel inputChannel;
@Autowired
@Qualifier("inputChannel")
MessageChannel inputChannel;
@Autowired
@Qualifier("outputChannel")
PollableChannel outputChannel;
@Autowired
@Qualifier("outputChannel")
PollableChannel outputChannel;
@Autowired
@Qualifier("pojoInputChannel")
MessageChannel pojoInputChannel;
@Autowired
@Qualifier("pojoInputChannel")
MessageChannel pojoInputChannel;
@Autowired
@Qualifier("pojoOutputChannel")
PollableChannel pojoOutputChannel;
@Autowired
@Qualifier("pojoOutputChannel")
PollableChannel pojoOutputChannel;
@Test
public void testCorrelationAndCompletion() {
inputChannel.send(MessageBuilder.withPayload("A1").setSequenceNumber(0).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("B2").setSequenceNumber(0).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("C3").setSequenceNumber(0).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("A4").setSequenceNumber(1).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("B5").setSequenceNumber(1).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("C6").setSequenceNumber(1).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("A7").setSequenceNumber(2).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("B8").setSequenceNumber(2).setSequenceSize(3)
.build());
inputChannel.send(MessageBuilder.withPayload("C9").setSequenceNumber(2).setSequenceSize(3)
.build());
receiveAndCompare(outputChannel, "A1", "A4", "A7");
receiveAndCompare(outputChannel, "B2", "B5", "B8");
receiveAndCompare(outputChannel, "C3", "C6", "C9");
}
@Test
public void testCorrelationAndCompletion() {
inputChannel.send(MessageBuilder.withPayload("A1").setSequenceNumber(0).build());
inputChannel.send(MessageBuilder.withPayload("B2").setSequenceNumber(0).build());
inputChannel.send(MessageBuilder.withPayload("C3").setSequenceNumber(0).build());
inputChannel.send(MessageBuilder.withPayload("A4").setSequenceNumber(1).build());
inputChannel.send(MessageBuilder.withPayload("B5").setSequenceNumber(1).build());
inputChannel.send(MessageBuilder.withPayload("C6").setSequenceNumber(1).build());
inputChannel.send(MessageBuilder.withPayload("A7").setSequenceNumber(2).build());
inputChannel.send(MessageBuilder.withPayload("B8").setSequenceNumber(2).build());
inputChannel.send(MessageBuilder.withPayload("C9").setSequenceNumber(2).build());
receiveAndCompare(outputChannel, "A1","A4","A7");
receiveAndCompare(outputChannel, "B2","B5","B8");
receiveAndCompare(outputChannel, "C3","C6","C9");
}
@Test
public void testCorrelationAndCompletionWithPojo() {
// the test verifies how a pojo strategy is applied
// Strings are correlated by their first letter, integers are correlated
// by the last digit
pojoInputChannel.send(MessageBuilder.withPayload("X1")
.setSequenceNumber(0).setSequenceSize(3).build());
pojoInputChannel.send(MessageBuilder.withPayload(93).setSequenceNumber(
0).setSequenceSize(3).build());
pojoInputChannel.send(MessageBuilder.withPayload("X4")
.setSequenceNumber(1).setSequenceSize(3).build());
pojoInputChannel.send(MessageBuilder.withPayload(113)
.setSequenceNumber(1).setSequenceSize(3).build());
pojoInputChannel.send(MessageBuilder.withPayload("X7")
.setSequenceNumber(2).setSequenceSize(3).build());
pojoInputChannel.send(MessageBuilder.withPayload(213)
.setSequenceNumber(2).setSequenceSize(3).build());
receiveAndCompare(pojoOutputChannel, "X1", "X4", "X7");
receiveAndCompare(pojoOutputChannel, "93", "113", "213");
}
@Test
public void testCorrelationAndCompletionWithPojo() {
// the test verifies how a pojo strategy is applied
// Strings are correlated by their first letter, integers are correlated by the last digit
pojoInputChannel.send(MessageBuilder.withPayload("X1").setSequenceNumber(0).build());
pojoInputChannel.send(MessageBuilder.withPayload(93).setSequenceNumber(0).build());
pojoInputChannel.send(MessageBuilder.withPayload("X4").setSequenceNumber(1).build());
pojoInputChannel.send(MessageBuilder.withPayload(113).setSequenceNumber(1).build());
pojoInputChannel.send(MessageBuilder.withPayload("X7").setSequenceNumber(2).build());
pojoInputChannel.send(MessageBuilder.withPayload(213).setSequenceNumber(2).build());
receiveAndCompare(pojoOutputChannel, "X1","X4","X7");
receiveAndCompare(pojoOutputChannel, "93","113","213");
}
private void receiveAndCompare(PollableChannel outputChannel,
String... expectedValues) {
Message<?> message = outputChannel.receive(500);
Assert.assertNotNull(message);
for (String expectedValue : expectedValues) {
assertThat((String) message.getPayload(),
containsString(expectedValue));
}
}
private void receiveAndCompare(PollableChannel outputChannel, String... expectedValues) {
Message<?> message = outputChannel.receive(500);
Assert.assertNotNull(message);
for (String expectedValue : expectedValues) {
assertThat((String)message.getPayload(), containsString(expectedValue));
}
}
public static class MessageCountReleaseStrategy implements
ReleaseStrategy {
private final int expectedSize;
public static class MessageCountCompletionStrategy implements CompletionStrategy {
public MessageCountReleaseStrategy(int expectedSize) {
this.expectedSize = expectedSize;
}
private final int expectedSize;
public boolean canRelease(MessageGroup messages) {
return messages.size() == expectedSize;
}
}
public MessageCountCompletionStrategy(int expectedSize) {
this.expectedSize = expectedSize;
}
public static class FirstLetterCorrelationStrategy implements
CorrelationStrategy {
public boolean isComplete(Collection<? extends Message<?>> messages) {
return messages.size() == expectedSize;
}
public Object getCorrelationKey(Message<?> message) {
return message.getPayload().toString().subSequence(0, 1);
}
}
}
public static class FirstLetterCorrelationStrategy implements CorrelationStrategy {
public static class PojoCorrelationStrategy {
public Object getCorrelationKey(Message<?> message) {
return message.getPayload().toString().subSequence(0,1);
}
public String correlate(String message) {
return message.substring(0, 1);
}
}
public String correlate(Integer message) {
return Integer.toString(message % 10);
}
public static class PojoCorrelationStrategy {
}
public String correlate(String message) {
return message.substring(0,1);
}
public static class SimpleAggregator {
public String correlate(Integer message) {
return Integer.toString(message % 10);
}
@Aggregator
public String concatenate(List<Object> payloads) {
StringBuffer buffer = new StringBuffer();
for (Object payload : payloads) {
buffer.append(payload.toString());
}
return buffer.toString();
}
}
public static class SimpleAggregator {
@Aggregator
public String concatenate(List<Object> payloads) {
StringBuffer buffer = new StringBuffer();
for (Object payload: payloads) {
buffer.append(payload.toString());
}
return buffer.toString();
}
}
}
}

View File

@@ -17,12 +17,12 @@ package org.springframework.integration.config;
import java.util.List;
public class MaxValueCompletionStrategy {
public class MaxValueReleaseStrategy {
private long maxValue;
public MaxValueCompletionStrategy(long maxValue){
public MaxValueReleaseStrategy(long maxValue){
this.maxValue = maxValue;
}

View File

@@ -7,7 +7,7 @@
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<aggregator id="aggregator" ref="adderBean" method="add" completion-strategy="testCompletionStrategy"
<aggregator id="aggregator" ref="adderBean" method="add" release-strategy="testReleaseStrategy"
input-channel="input-channel" output-channel="replyChannel">
</aggregator>
@@ -16,6 +16,6 @@
<beans:bean id="adderBean" class="org.springframework.integration.config.Adder"/>
<beans:bean id="completionStrategyBean" class="org.springframework.integration.config.TestCompletionStrategy"/>
<beans:bean id="ReleaseStrategyBean" class="org.springframework.integration.config.TestReleaseStrategy"/>
</beans:beans>

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
import java.util.ArrayList;
@@ -26,12 +27,12 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.aggregator.CorrelationStrategyAdapter;
import org.springframework.integration.aggregator.Resequencer;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
@@ -79,21 +80,15 @@ public class ResequencerParserTests {
@Test
public void testDefaultResequencerProperties() {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("defaultResequencer");
Resequencer resequencer = TestUtils.getPropertyValue(endpoint, "handler", Resequencer.class);
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler", CorrelatingMessageHandler.class);
assertNull(getPropertyValue(resequencer, "outputChannel"));
assertNull(getPropertyValue(resequencer, "discardChannel"));
assertTrue(getPropertyValue(resequencer, "discardChannel") instanceof NullChannel);
assertEquals("The ResequencerEndpoint is not set with the appropriate timeout value",
1000l, getPropertyValue(resequencer, "channelTemplate.sendTimeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate 'send partial results on timeout' flag",
false, getPropertyValue(resequencer, "sendPartialResultOnTimeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate reaper interval",
1000l, getPropertyValue(resequencer, "reaperInterval"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate tracked correlationId capacity",
1000, getPropertyValue(resequencer, "trackedCorrelationIdCapacity"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate timeout",
60000l, getPropertyValue(resequencer, "timeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate 'release partial sequences' flag",
true, getPropertyValue(resequencer, "releasePartialSequences"));
false, getPropertyValue(getPropertyValue(resequencer, "outputProcessor"), "releasePartialSequences"));
}
@Test
@@ -101,7 +96,7 @@ public class ResequencerParserTests {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("completelyDefinedResequencer");
MessageChannel outputChannel = (MessageChannel) context.getBean("outputChannel");
MessageChannel discardChannel = (MessageChannel) context.getBean("discardChannel");
Resequencer resequencer = TestUtils.getPropertyValue(endpoint, "handler", Resequencer.class);
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler", CorrelatingMessageHandler.class);
assertEquals("The ResequencerEndpoint is not injected with the appropriate output channel",
outputChannel, getPropertyValue(resequencer, "outputChannel"));
assertEquals("The ResequencerEndpoint is not injected with the appropriate discard channel",
@@ -110,20 +105,14 @@ public class ResequencerParserTests {
86420000l, getPropertyValue(resequencer, "channelTemplate.sendTimeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate 'send partial results on timeout' flag",
true, getPropertyValue(resequencer, "sendPartialResultOnTimeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate reaper interval",
135l, getPropertyValue(resequencer, "reaperInterval"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate tracked correlationId capacity",
99, getPropertyValue(resequencer, "trackedCorrelationIdCapacity"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate timeout",
42l, getPropertyValue(resequencer, "timeout"));
assertEquals("The ResequencerEndpoint is not configured with the appropriate 'release partial sequences' flag",
false, getPropertyValue(resequencer, "releasePartialSequences"));
false, getPropertyValue(getPropertyValue(resequencer, "outputProcessor"), "releasePartialSequences"));
}
@Test
public void testCorrelationStrategyRefOnly() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("resequencerWithCorrelationStrategyRefOnly");
Resequencer resequencer = TestUtils.getPropertyValue(endpoint, "handler", Resequencer.class);
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler", CorrelatingMessageHandler.class);
assertEquals("The ResequencerEndpoint is not configured with the appropriate CorrelationStrategy",
context.getBean("testCorrelationStrategy"), getPropertyValue(resequencer, "correlationStrategy"));
}
@@ -131,7 +120,7 @@ public class ResequencerParserTests {
@Test
public void testCorrelationStrategyRefAndMethod() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("resequencerWithCorrelationStrategyRefAndMethod");
Resequencer resequencer = TestUtils.getPropertyValue(endpoint, "handler", Resequencer.class);
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler", CorrelatingMessageHandler.class);
Object correlationStrategy = getPropertyValue(resequencer, "correlationStrategy");
assertEquals("The ResequencerEndpoint is not configured with a CorrelationStrategy adapter",
CorrelationStrategyAdapter.class, correlationStrategy.getClass());

View File

@@ -16,17 +16,16 @@
package org.springframework.integration.config;
import java.util.Collection;
import org.springframework.integration.aggregator.CompletionStrategy;
import org.springframework.integration.core.Message;
import org.springframework.integration.aggregator.MessageGroup;
import org.springframework.integration.aggregator.ReleaseStrategy;
/**
* @author Marius Bogoevici
*/
public class TestCompletionStrategy implements CompletionStrategy {
public class TestReleaseStrategy implements ReleaseStrategy {
public boolean isComplete(Collection<? extends Message<?>> messages) {
public boolean canRelease(MessageGroup messages) {
throw new UnsupportedOperationException("This is not intended to be implemented, but to verify injection into an <aggregator>");
}

View File

@@ -24,7 +24,7 @@
output-channel="outputChannel"
discard-channel="discardChannel"
ref="aggregatorBean"
completion-strategy="completionStrategy"
release-strategy="releaseStrategy"
correlation-strategy="correlationStrategy"
send-timeout="86420000"
send-partial-result-on-timeout="true"
@@ -39,14 +39,14 @@
input-channel="aggregatorWithReferenceAndMethodInput"
output-channel="outputChannel"/>
<channel id="aggregatorWithPojoCompletionStrategyInput"/>
<aggregator id="aggregatorWithPojoCompletionStrategy"
input-channel="aggregatorWithPojoCompletionStrategyInput"
<channel id="aggregatorWithPojoReleaseStrategyInput"/>
<aggregator id="aggregatorWithPojoReleaseStrategy"
input-channel="aggregatorWithPojoReleaseStrategyInput"
output-channel="outputChannel"
ref="adderBean"
method="add"
completion-strategy="pojoCompletionStrategy"
completion-strategy-method="checkCompleteness"/>
release-strategy="pojoReleaseStrategy"
release-strategy-method="checkCompleteness"/>
<beans:bean id="aggregatorBean"
class="org.springframework.integration.config.TestAggregatorBean" />
@@ -54,13 +54,13 @@
<beans:bean id="adderBean"
class="org.springframework.integration.config.Adder" />
<beans:bean id="completionStrategy"
class="org.springframework.integration.config.TestCompletionStrategy" />
<beans:bean id="releaseStrategy"
class="org.springframework.integration.config.TestReleaseStrategy" />
<beans:bean id="correlationStrategy" class="org.springframework.integration.config.TestCorrelationStrategy"/>
<beans:bean id="pojoCompletionStrategy"
class="org.springframework.integration.config.MaxValueCompletionStrategy">
<beans:bean id="pojoReleaseStrategy"
class="org.springframework.integration.config.MaxValueReleaseStrategy">
<beans:constructor-arg value="10" />
</beans:bean>

View File

@@ -20,7 +20,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
import java.lang.reflect.Method;
@@ -28,17 +27,18 @@ import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.aggregator.AbstractMessageAggregator;
import org.springframework.integration.aggregator.CompletionStrategyAdapter;
import org.springframework.integration.aggregator.ReleaseStrategyAdapter;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.CorrelationStrategyAdapter;
import org.springframework.integration.aggregator.SequenceSizeCompletionStrategy;
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
import org.springframework.integration.channel.BeanFactoryChannelResolver;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.test.util.TestUtils;
/**
@@ -52,18 +52,13 @@ public class AggregatorAnnotationTests {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "classpath:/org/springframework/integration/config/annotation/testAnnotatedAggregator.xml" });
final String endpointName = "endpointWithDefaultAnnotation";
AbstractMessageAggregator aggregator = this.getAggregator(context, endpointName);
assertTrue(getPropertyValue(aggregator, "completionStrategy") instanceof SequenceSizeCompletionStrategy);
MessageHandler aggregator = this.getAggregator(context, endpointName);
assertTrue(getPropertyValue(aggregator, "ReleaseStrategy") instanceof SequenceSizeReleaseStrategy);
assertNull(getPropertyValue(aggregator, "outputChannel"));
assertNull(getPropertyValue(aggregator, "discardChannel"));
assertEquals(AbstractMessageAggregator.DEFAULT_SEND_TIMEOUT,
assertTrue(getPropertyValue(aggregator, "discardChannel") instanceof NullChannel);
assertEquals(CorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT,
getPropertyValue(aggregator, "channelTemplate.sendTimeout"));
assertEquals(AbstractMessageAggregator.DEFAULT_TIMEOUT, getPropertyValue(aggregator, "timeout"));
assertEquals(false, getPropertyValue(aggregator, "sendPartialResultOnTimeout"));
assertEquals(AbstractMessageAggregator.DEFAULT_REAPER_INTERVAL,
getPropertyValue(aggregator, "reaperInterval"));
assertEquals(AbstractMessageAggregator.DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY,
getPropertyValue(aggregator, "trackedCorrelationIdCapacity"));
}
@Test
@@ -71,32 +66,29 @@ public class AggregatorAnnotationTests {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "classpath:/org/springframework/integration/config/annotation/testAnnotatedAggregator.xml" });
final String endpointName = "endpointWithCustomizedAnnotation";
AbstractMessageAggregator aggregator = this.getAggregator(context, endpointName);
assertTrue(getPropertyValue(aggregator, "completionStrategy")
instanceof SequenceSizeCompletionStrategy);
MessageHandler aggregator = this.getAggregator(context, endpointName);
assertTrue(getPropertyValue(aggregator, "ReleaseStrategy")
instanceof SequenceSizeReleaseStrategy);
ChannelResolver channelResolver = new BeanFactoryChannelResolver(context);
assertEquals(channelResolver.resolveChannelName("outputChannel"),
getPropertyValue(aggregator, "outputChannel"));
assertEquals(channelResolver.resolveChannelName("discardChannel"),
getPropertyValue(aggregator, "discardChannel"));
assertEquals(98765432l, getPropertyValue(aggregator, "channelTemplate.sendTimeout"));
assertEquals(4567890l, getPropertyValue(aggregator, "timeout"));
assertEquals(true, getPropertyValue(aggregator, "sendPartialResultOnTimeout"));
assertEquals(1234l, getPropertyValue(aggregator, "reaperInterval"));
assertEquals(42, getPropertyValue(aggregator, "trackedCorrelationIdCapacity"));
}
@Test
public void testAnnotationWithCustomCompletionStrategy() throws Exception {
public void testAnnotationWithCustomReleaseStrategy() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "classpath:/org/springframework/integration/config/annotation/testAnnotatedAggregator.xml" });
final String endpointName = "endpointWithDefaultAnnotationAndCustomCompletionStrategy";
AbstractMessageAggregator aggregator = this.getAggregator(context, endpointName);
Object completionStrategy = getPropertyValue(aggregator, "completionStrategy");
Assert.assertTrue(completionStrategy instanceof CompletionStrategyAdapter);
CompletionStrategyAdapter completionStrategyAdapter = (CompletionStrategyAdapter) completionStrategy;
final String endpointName = "endpointWithDefaultAnnotationAndCustomReleaseStrategy";
MessageHandler aggregator = this.getAggregator(context, endpointName);
Object ReleaseStrategy = getPropertyValue(aggregator, "ReleaseStrategy");
Assert.assertTrue(ReleaseStrategy instanceof ReleaseStrategyAdapter);
ReleaseStrategyAdapter ReleaseStrategyAdapter = (ReleaseStrategyAdapter) ReleaseStrategy;
DirectFieldAccessor invokerAccessor = new DirectFieldAccessor(
new DirectFieldAccessor(completionStrategyAdapter).getPropertyValue("invoker"));
new DirectFieldAccessor(ReleaseStrategyAdapter).getPropertyValue("invoker"));
Object targetObject = invokerAccessor.getPropertyValue("object");
assertSame(context.getBean(endpointName), targetObject);
Method completionCheckerMethod = (Method) invokerAccessor.getPropertyValue("method");
@@ -108,12 +100,12 @@ public class AggregatorAnnotationTests {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "classpath:/org/springframework/integration/config/annotation/testAnnotatedAggregator.xml" });
final String endpointName = "endpointWithCorrelationStrategy";
AbstractMessageAggregator aggregator = this.getAggregator(context, endpointName);
MessageHandler aggregator = this.getAggregator(context, endpointName);
Object correlationStrategy = getPropertyValue(aggregator, "correlationStrategy");
Assert.assertTrue(correlationStrategy instanceof CorrelationStrategyAdapter);
CorrelationStrategyAdapter completionStrategyAdapter = (CorrelationStrategyAdapter) correlationStrategy;
CorrelationStrategyAdapter ReleaseStrategyAdapter = (CorrelationStrategyAdapter) correlationStrategy;
DirectFieldAccessor processorAccessor = new DirectFieldAccessor(
new DirectFieldAccessor(completionStrategyAdapter).getPropertyValue("processor"));
new DirectFieldAccessor(ReleaseStrategyAdapter).getPropertyValue("processor"));
Object targetObject = processorAccessor.getPropertyValue("targetObject");
assertSame(context.getBean(endpointName), targetObject);
Map<?, ?> handlerMethods = (Map<?, ?>) processorAccessor.getPropertyValue("handlerMethods");
@@ -125,10 +117,10 @@ public class AggregatorAnnotationTests {
private AbstractMessageAggregator getAggregator(ApplicationContext context, final String endpointName) {
private MessageHandler getAggregator(ApplicationContext context, final String endpointName) {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean(
endpointName + ".aggregatingMethod.aggregator");
return TestUtils.getPropertyValue(endpoint, "handler", AbstractMessageAggregator.class);
return TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class);
}
}

View File

@@ -20,7 +20,7 @@ import java.util.List;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CompletionStrategy;
import org.springframework.integration.annotation.ReleaseStrategy;
import org.springframework.integration.annotation.CorrelationStrategy;
/**
@@ -38,7 +38,7 @@ public class TestAnnotatedEndpointWithCorrelationStrategy {
return buffer.toString();
}
@CompletionStrategy
@ReleaseStrategy
public boolean isComplete(List<String> payloads) {
return payloads.size() == 3;
}

View File

@@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.integration.aggregator.MessageSequenceComparator;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CompletionStrategy;
import org.springframework.integration.annotation.ReleaseStrategy;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.StringMessage;
@@ -32,8 +32,8 @@ import org.springframework.integration.message.StringMessage;
/**
* @author Marius Bogoevici
*/
@MessageEndpoint("endpointWithDefaultAnnotationAndCustomCompletionStrategy")
public class TestAnnotatedEndpointWithCompletionStrategy {
@MessageEndpoint("endpointWithDefaultAnnotationAndCustomReleaseStrategy")
public class TestAnnotatedEndpointWithReleaseStrategy {
private final ConcurrentMap<Object, Message<?>> aggregatedMessages = new ConcurrentHashMap<Object, Message<?>>();
@@ -54,7 +54,7 @@ public class TestAnnotatedEndpointWithCompletionStrategy {
return returnedMessage;
}
@CompletionStrategy
@ReleaseStrategy
public boolean completionChecker(List<Message<?>> messages) {
return true;
}

View File

@@ -8,7 +8,7 @@
<beans:bean id="correlationStrategy" class="org.springframework.integration.config.CorrelationStrategyInvalidConfigurationTests$VoidReturningCorrelationStrategy"/>
<beans:bean id="completionStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountCompletionStrategy">
<beans:bean id="releaseStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountReleaseStrategy">
<beans:constructor-arg value="3"/>
</beans:bean>
@@ -21,8 +21,8 @@
</channel>
<aggregator ref="aggregator"
completion-strategy="completionStrategy"
correlation-strategy="correlationStrategy" completion-strategy-method="invalidCorrelationMethod"
release-strategy="releaseStrategy" release-strategy-method="invalidCorrelationMethod"
correlation-strategy="correlationStrategy"
input-channel="inputChannel"
output-channel="outputChannel"/>

View File

@@ -7,12 +7,12 @@
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<aggregator id="aggregatorWithPojoCompletionStrategy"
completion-strategy="completionStrategy"
<aggregator id="aggregatorWithPojoReleaseStrategy"
ref="adderBean" method="add"
input-channel="inputChannel"
output-channel="replyChannel"
completion-strategy-method="invalidMethodName"/>
release-strategy="releaseStrategy"
release-strategy-method="invalidMethodName"/>
<channel id="inputChannel"/>
<channel id="replyChannel"/>
@@ -20,11 +20,11 @@
<beans:bean id="adderBean"
class="org.springframework.integration.config.Adder" />
<beans:bean id="completionStrategy"
class="org.springframework.integration.config.TestCompletionStrategy" />
<beans:bean id="releaseStrategy"
class="org.springframework.integration.config.TestReleaseStrategy" />
<beans:bean id="pojoCompletionStrategy"
class="org.springframework.integration.config.MaxValueCompletionStrategy">
<beans:bean id="pojoReleaseStrategy"
class="org.springframework.integration.config.MaxValueReleaseStrategy">
<beans:constructor-arg value="10" />
</beans:bean>

View File

@@ -20,8 +20,6 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
@@ -68,13 +66,4 @@ public class SimpleMessageStoreTests {
assertEquals(1, store.list("bar").size());
}
@Test
public void shouldListByCorrelationAfterAddAll() throws Exception {
SimpleMessageStore store = new SimpleMessageStore();
Message<String> testMessage1 = MessageBuilder.withPayload("foo").build();
Message<String> testMessage2 = MessageBuilder.withPayload("bar").build();
store.put("bar", Arrays.<Message<?>>asList(testMessage1, testMessage2));
assertEquals(2, store.list("bar").size());
}
}