diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java index c9ed34cfd1..a8b8dae4f3 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java @@ -64,7 +64,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag protected Map aggregateHeaders(MessageGroup group) { Map aggregatedHeaders = new HashMap(); Set conflictKeys = new HashSet(); - 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) diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageAggregator.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageAggregator.java deleted file mode 100644 index efc1f1c91a..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageAggregator.java +++ /dev/null @@ -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 - * complete 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}. - * - *

- * The default strategy for determining whether a group is complete is based on - * the 'sequenceSize' property of the header. Alternatively, a - * custom implementation of the {@link CompletionStrategy} may be provided. - * - *

- * All considerations regarding timeout and grouping by - * correlationId from {@link AbstractMessageBarrierHandler} apply - * here as well. - * - * @author Mark Fisher - * @author Marius Bogoevici - */ -public abstract class AbstractMessageAggregator extends - AbstractMessageBarrierHandler>> { - - 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>> createMessageBarrier(Object correlationKey) { - return new MessageBarrier>>(new ArrayList>(), correlationKey); - } - - @Override - protected void processBarrier(MessageBarrier>> 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> messages); - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageBarrierHandler.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageBarrierHandler.java deleted file mode 100644 index 61d1130fab..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/AbstractMessageBarrierHandler.java +++ /dev/null @@ -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. - *

- * Each {@link Message} that is received by this handler will be associated - * with a group based upon the 'correlationId' property of its - * header. If no such property is available, a {@link MessageHandlingException} - * will be thrown. - *

- * The 'timeout' 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. - *

- * 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. - *

- * 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>> - 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> barriers = new ConcurrentHashMap>(); - - 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 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(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 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> 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 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 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 barrier); - - /** - * A method for discarding the content of the message barrier. - * Can be overridden by subclasses. - * @param entry - * @param barrier - */ - - protected void discardBarrier(MessageBarrier 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> entry : barriers.entrySet()) { - if (currentTime - entry.getValue().getTimestamp() >= timeout) { - MessageBarrier barrier = entry.getValue(); - synchronized (barrier) { - removeBarrier(entry.getKey()); - if (sendPartialResultOnTimeout) { - barrier.setComplete(); - processBarrier(barrier); - } - else { - discardBarrier(barrier); - } - } - } - } - } - - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java index a972ec716b..052e9ea0bf 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java @@ -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. *

- * 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. *

- * 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 keysInBuffer = new DelayQueue(); - - 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 locks = new ConcurrentHashMap(); 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> 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> 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> 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> 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 trackerLocks = new ConcurrentHashMap(); - - private final Queue trackedCorrelationIds = new LinkedBlockingQueue(); - - 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); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java index b9fa3efca3..2efdbdf980 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java @@ -36,7 +36,7 @@ public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregating @Override protected final Object aggregatePayloads(MessageGroup group) { - Collection> messages = group.getMessages(); + Collection> messages = group.getUnmarked(); Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups"); List payloads = new ArrayList(messages.size()); for (Message message : messages) { diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultMessageAggregator.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultMessageAggregator.java deleted file mode 100644 index 39865b8f23..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultMessageAggregator.java +++ /dev/null @@ -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 correlationId. - * - *

- * The default strategy for determining whether a group is complete is based - * on the 'sequenceSize' property of the header. Alternatively, a - * custom implementation of the {@link CompletionStrategy} may be provided. - *

- * - *

- * All considerations regarding timeout and grouping by - * correlationId from {@link AbstractMessageBarrierHandler} apply - * here as well. - *

- * - * @author Alex Peters - * @since 1.0.3 - */ -public class DefaultMessageAggregator extends AbstractMessageAggregator { - - /** - * {@inheritDoc} - */ - @Override - protected Message aggregateMessages(List> messages) { - List payloads = new ArrayList(messages.size()); - for (Message message : messages) { - payloads.add(message.getPayload()); - } - return MessageBuilder.withPayload(payloads).build(); - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultResequencerStrategies.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultResequencerStrategies.java deleted file mode 100644 index 5be2d43401..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/DefaultResequencerStrategies.java +++ /dev/null @@ -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 nextMessagesToPass = new ConcurrentHashMap(); - private final ConcurrentMap lastMessagesToPass = new ConcurrentHashMap(); - - 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> messages) { - return releasePartialSequences - || (!messages.isEmpty() && messages.iterator().next().getHeaders().getSequenceSize() == messages.size()); - } - - public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) { - Collection> all = group.getMessages(); - Object correlationKey = group.getCorrelationKey(); - if (all.size() > 0) { - List> sorted = new ArrayList>(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> { - public int compare(Message o1, Message o2) { - return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber()); - } - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageBarrier.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageBarrier.java deleted file mode 100644 index 9c36e64cf0..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageBarrier.java +++ /dev/null @@ -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>> { - - protected final T messages; - - private volatile boolean complete = false; - - private Object correlationKey; - - private final long timestamp = System.currentTimeMillis(); - - private final Map attributes = new HashMap(); - - 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 getAttribute(String attributeName) { - return (V)this.attributes.get(attributeName); - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java new file mode 100755 index 0000000000..63dadfd5c9 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java @@ -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 n messages, where n 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; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroup.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroup.java index 5fb5684f96..ebb29cc6ce 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroup.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroup.java @@ -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 - * added to it. This is not thread safe and should not be - * used for long running aggregations. - *

- * According to its - * {@link org.springframework.integration.aggregator.CompletionStrategy} it can - * be complete depending on the messages in the group. - *

- * 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 added to it. This is not thread + * safe and should not be used for long running aggregations. * * @author Iwein Fuld * @author Oleg Zhurakousky * @author Dave Syer + * * @since 2.0 */ public class MessageGroup { private final Object correlationKey; - private final Collection> messages = new ArrayList>(); + private final Collection> marked = new HashSet>(); + + private final Collection> unmarked = new HashSet>(); + + public MessageGroup(Object correlationKey) { + this.correlationKey = correlationKey; + } public MessageGroup(Collection> 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> 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> getMessages() { - return messages; + public Collection> 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> messages, Integer messageSequenceNumber) { + for (Message member : messages) { + Integer memberSequenceNumber = member.getHeaders().getSequenceNumber(); + if (messageSequenceNumber.equals(memberSequenceNumber)) { + return true; + } + } + return false; + } + } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroupListener.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroupListener.java deleted file mode 100644 index dd58adeec9..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MessageGroupListener.java +++ /dev/null @@ -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); -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregator.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregator.java deleted file mode 100644 index 845d40c61e..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregator.java +++ /dev/null @@ -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 aggregator - * elements (e.g. <aggregator ref="beanReference" method="methodName"/>). - * - * @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> 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(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 annotatedMethod = new AtomicReference(); - 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; - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregatorFactoryBean.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregatorFactoryBean.java deleted file mode 100644 index 945b2675c2..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingAggregatorFactoryBean.java +++ /dev/null @@ -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 , 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 getObjectType() { - return CorrelatingMessageHandler.class; - } - - public boolean isSingleton() { - return false; - } -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java index f3d78e3d40..c69ded8b73 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java @@ -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> messagesUpForProcessing = group.getMessages(); + final Collection> messagesUpForProcessing = group.getUnmarked(); Object result = this.adapter.executeMethod(messagesUpForProcessing); return result; } - private Method selectMethodFrom(Object target) { - Method[] methods = target.getClass().getMethods(); - Set candidates = new HashSet(Arrays.asList(methods)); - - removeObjectMethodsFrom(candidates); - removeVoidMethodsFrom(candidates); - removeListIncompatibleMethodsFrom(candidates); - Set notAnnotatedCandidates = new HashSet(); - 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 annotatedMethod = new AtomicReference(); + 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 methods = new HashSet(); + 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 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 removeNotAnnotatedFrom(Set candidates) { - return removeMethodsMatchingSelector(candidates, new MethodSelector() { - public boolean select(Method method) { - Aggregator annotation = method.getAnnotation(Aggregator.class); - return (annotation == null); - } - }); - } - private Set removeUnfittingFrom(Set 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 candidates) { - removeMethodsMatchingSelector(candidates, new MethodSelector() { - public boolean select(Method method) { - return method.getDeclaringClass().equals(Object.class); - } - }); - } - private Set removeMethodsMatchingSelector(Set candidates, MethodSelector selector) { Set removed = new HashSet(); Iterator iterator = candidates.iterator(); diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java index 5acc2772f2..dbba086239 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/PassThroughMessageGroupProcessor.java @@ -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); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategy.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java similarity index 79% rename from org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategy.java rename to org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java index 9bdaa3a7aa..98a8610227 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategy.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java @@ -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> messages); + boolean canRelease(MessageGroup group); } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategyAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategyAdapter.java similarity index 62% rename from org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategyAdapter.java rename to org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategyAdapter.java index 4b672d4968..9bfcc12245 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/CompletionStrategyAdapter.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/ReleaseStrategyAdapter.java @@ -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 'completion-strategy' elements that include a 'method' - * attribute (e.g. <completion-strategy ref="beanReference" method="methodName"/>). + * {@link org.springframework.integration.annotation.ReleaseStrategy @ReleaseStrategy} + * and for 'release-strategy' elements that include a 'method' + * attribute (e.g. <release-strategy ref="beanReference" method="methodName"/>). * * @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> 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()), diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/Resequencer.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/Resequencer.java index 80f9c12551..a483e4ec55 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/Resequencer.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/Resequencer.java @@ -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 sequenceNumber. - *

- * This handler can either release partial sequences of messages or can - * wait for the whole sequence to arrive before re-sending them. - *

- * All considerations regarding timeout and grouping by - * 'correlationId' 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 sequence_size header attribute - * and that the sequence numbers of the messages are successive, starting with - * 1 up to sequenceSize. 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>> { +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> sorted = new ArrayList>(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> messages = group.getUnmarked(); + if (messages.size() > 0) { + List> sorted = new ArrayList>(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>> createMessageBarrier(Object correlationKey) { - MessageBarrier>> messageBarrier - = new MessageBarrier>>(new TreeSet>(new MessageSequenceComparator()), correlationKey); - messageBarrier.setAttribute(LAST_RELEASED_SEQUENCE_NUMBER, 0); - return messageBarrier; + private static class SequenceNumberComparator implements Comparator> { + public int compare(Message o1, Message o2) { + return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber()); + } } - @Override - protected void processBarrier(MessageBarrier>> barrier) { - if (hasReceivedAllMessages(barrier)) { - barrier.setComplete(); - } - List> 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>> 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> releaseAvailableMessages(MessageBarrier>> barrier) { - if (this.releasePartialSequences || barrier.isComplete()) { - ArrayList> releasedMessages = new ArrayList>(); - Iterator> 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>(); - } - } - - @Override - protected boolean canAddMessage(Message message, MessageBarrier>> 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; - } - } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategy.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategy.java deleted file mode 100644 index 5a20fd46ec..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategy.java +++ /dev/null @@ -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> messages) { - if (CollectionUtils.isEmpty(messages)) { - return false; - } - return messages.size() != 0 && (messages.size() >= messages.iterator().next().getHeaders().getSequenceSize()); - } - -} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java new file mode 100644 index 0000000000..060ab98bda --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java @@ -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(); + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Aggregator.java b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Aggregator.java index 58bd328e72..0cd416c365 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Aggregator.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Aggregator.java @@ -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; } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/CompletionStrategy.java b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java similarity index 96% rename from org.springframework.integration/src/main/java/org/springframework/integration/annotation/CompletionStrategy.java rename to org.springframework.integration/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java index 4844b37864..30f5723933 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/CompletionStrategy.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java @@ -31,6 +31,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented -public @interface CompletionStrategy { +public @interface ReleaseStrategy { } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java index c1f3247d59..93b067fd16 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java @@ -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 reference = new AtomicReference(); 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 reference = new AtomicReference(); 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(); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java index 51db2fb72f..2ac76b98ec 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java @@ -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 aggregator element of the integration namespace. - * Registers the annotation-driven post-processors. - * + * Parser for the aggregator 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()); + } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java index d3c5814223..83f8408d8b 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java @@ -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 <resequencer> 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; } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/store/MessageStore.java b/org.springframework.integration/src/main/java/org/springframework/integration/store/MessageStore.java index 01253c262b..61982fde85 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/store/MessageStore.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/store/MessageStore.java @@ -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 null if no Message with - * that id exists in the MessageStore. + * Return the Message with the given id, or null 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 */ Message put(Message 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 null. + * 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 null. */ 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> 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> 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 */ diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/store/SimpleMessageStore.java b/org.springframework.integration/src/main/java/org/springframework/integration/store/SimpleMessageStore.java index d43b989627..43eaf5f334 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/store/SimpleMessageStore.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/store/SimpleMessageStore.java @@ -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>(collection); } - public void put(Object correlationId, Collection> 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; } diff --git a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd index a19567d5f8..e168948183 100644 --- a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd +++ b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd @@ -1422,7 +1422,7 @@ - + @@ -1431,11 +1431,11 @@ - + - + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorEndpointTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorEndpointTests.java deleted file mode 100644 index 107df2aab0..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorEndpointTests.java +++ /dev/null @@ -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 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> messages) { - List> sortableList = new ArrayList>(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> 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(); - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorMethodResolutionTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorMethodResolutionTests.java deleted file mode 100644 index 0ea0cdf490..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorMethodResolutionTests.java +++ /dev/null @@ -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 input) { - return input.get(0); - } - - public String method2(List input) { - return input.get(0); - } - } - - - private static class MultipleAnnotationTestBean { - - @Aggregator - public String method1(List input) { - return input.get(0); - } - - @Aggregator - public String method2(List input) { - return input.get(0); - } - } - - - private static class NoAnnotationTestBean { - - public String method1(List input) { - return input.get(0); - } - - String method2(List 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 names); - - } - - - public static class GreetingBean implements GreetingService { - - private String greeting = "hello"; - - public void setGreeting(String greeting) { - this.greeting = greeting; - } - - @Aggregator - public String sayHello(List names) { - return greeting + " " + names.get(0); - } - - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewAggregatorEndpointTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java similarity index 92% rename from org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewAggregatorEndpointTests.java rename to org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java index 09683e8e54..286ea051ee 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewAggregatorEndpointTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java @@ -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 reply1 = (Message) replyChannel1.receive(500); assertNotNull(reply1); assertThat(reply1.getPayload(), is(105)); + @SuppressWarnings("unchecked") Message reply2 = (Message) 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); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java new file mode 100644 index 0000000000..8ae1082f86 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java @@ -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 reply1 = (Message) replyChannel1.receive(500); + assertNotNull(reply1); + assertThat(reply1.getPayload(), is(105)); + @SuppressWarnings("unchecked") + Message reply2 = (Message) 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 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 + } + } +} \ No newline at end of file diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerIntegrationTest.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerIntegrationTest.java index c5dc6d8225..d9f99f270c 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerIntegrationTest.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerIntegrationTest.java @@ -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") diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerTests.java index 1b9f2909c1..9d35cc87c2 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerTests.java @@ -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() { - 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> storedMessages = new ArrayList>(); - @Test - public void bufferCompletesNormally() throws Exception { - String correlationKey = "key"; - Message message1 = testMessage(correlationKey, 1); - Message message2 = testMessage(correlationKey, 2); - List> storedMessages = new ArrayList>(); + 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.>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.>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.>asList(message1)); - verify(completionStrategy).isComplete(Arrays.>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> storedMessages = new ArrayList>(); - /* - 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> storedMessages = new ArrayList>(); + 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.>asList(message1, message2))) - .thenAnswer(new Answer() { - 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(); + } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/DefaultMessageAggregatorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/DefaultMessageAggregatorTests.java deleted file mode 100644 index c007ee6865..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/DefaultMessageAggregatorTests.java +++ /dev/null @@ -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 anyPayloads = Arrays.asList("foo", "bar", 123L, new Object()); - List> messageGroup = new ArrayList>(anyPayloads.size()); - for (Object payload : anyPayloads) { - messageGroup.add(MessageBuilder.withPayload(payload).build()); - } - Message result = aggregator.aggregateMessages(messageGroup); - assertThat((List) result.getPayload(), is(anyPayloads)); - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageBarrierTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageBarrierTests.java deleted file mode 100644 index edc37537e7..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageBarrierTests.java +++ /dev/null @@ -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()); - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageGroupTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageGroupTests.java index 9a1b111f6c..8531758c6e 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageGroupTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MessageGroupTests.java @@ -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)); } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingAggregatorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingAggregatorTests.java deleted file mode 100644 index 24e7a14ede..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingAggregatorTests.java +++ /dev/null @@ -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> messages = new ArrayList>(); - - @Test - public void adapterWithNonParameterizedMessageListBasedMethod() { - expect(mockAggregator.doAggregationOnNonParameterizedListOfMessages(isA(List.class))).andStubReturn( - new GenericMessage("")); - 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("")); - 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("")); - 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("")); - 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 messages); - - public Message doAggregationOnListOfMessagesParametrizedWithWildcard(List> messages); - - public Message doAggregationOnListOfMessagesParametrizedWithString(List> messages); - - public Message doAggregationOnListOfStrings(List messages); - - public Long doAggregationOnListOfStringsReturningLong(List messages); - - public void doAggregationWithNoReturn(List message); - - public Message invalidParameterType(String invalid); - - public Message tooManyParameters(List c1, List c2); - - public Message notEnoughParameters(); - - public Message listSubclassParameter(LinkedList l1); - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java index ca1fd1ce75..abbeee2188 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java @@ -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> messagesUpForProcessing = new ArrayList>(3); - private List> messagesUpForProcessing = new ArrayList>( - 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 flags) { - int result = 0; - for (Integer flag : flags) { - result = result | flag; - } - return result; - } + @Aggregator + public Integer and(List flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } - public String know(List flags) { - return "I'm not the one "; - } - } + public String know(List flags) { + return "I'm not the one "; + } + } - @Test - public void shouldFindAnnotatedAggregatorMethod() throws Exception { - MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor( - new AnnotatedAggregatorMethod()); - ArgumentCaptor 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 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 flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + } - private class SimpleAggregator { - public Integer and(List 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 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 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 flags) { + int result = 0; + for (Integer flag : flags) { + result = result | flag; + } + return result; + } + public void voidMethodShouldBeIgnored(List flags) { + fail("this method should not be invoked"); + } - private class UnnanotatedAggregator { - public Integer and(List 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 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 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 messageCaptor = ArgumentCaptor - .forClass(Message.class); + @SuppressWarnings("unused") + private class AnnotatedParametersAggregator { + public Integer and(List 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 flags) { + fail("this method should not be invoked"); + return ""; + } + } - private class AnnotatedParametersAggregator { - public Integer and(List 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 flags) { - fail("this method should not be invoked"); - return ""; - } - } + @SuppressWarnings("unchecked") + ArgumentCaptor 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 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 input) { + return input.get(0); + } + + public String method2(List input) { + return input.get(0); + } + } + + @SuppressWarnings("unused") + private static class MultipleAnnotationTestBean { + + @Aggregator + public String method1(List input) { + return input.get(0); + } + + @Aggregator + public String method2(List input) { + return input.get(0); + } + } + + @SuppressWarnings("unused") + private static class NoAnnotationTestBean { + + public String method1(List input) { + return input.get(0); + } + + String method2(List 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 names); + + } + + public static class GreetingBean implements GreetingService { + + private String greeting = "hello"; + + public void setGreeting(String greeting) { + this.greeting = greeting; + } + + @Aggregator + public String sayHello(List 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)); - } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewConcurrentAggregatorEndpointTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewConcurrentAggregatorEndpointTests.java deleted file mode 100644 index 57f7c0791e..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewConcurrentAggregatorEndpointTests.java +++ /dev/null @@ -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 reply1 = (Message) replyChannel1.receive(500); - assertNotNull(reply1); - assertThat(reply1.getPayload(), is(105)); - Message reply2 = (Message) 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 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 - } - } -} \ No newline at end of file diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewResequencerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewResequencerTests.java deleted file mode 100644 index 7626f060da..0000000000 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/NewResequencerTests.java +++ /dev/null @@ -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(); - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CompletionStrategyAdapterTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java similarity index 55% rename from org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CompletionStrategyAdapterTests.java rename to org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java index b8ba12614d..6747a32329 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/CompletionStrategyAdapterTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ReleaseStrategyAdapterTests.java @@ -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>())); + 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>())); + Assert.assertTrue(!adapter.canRelease(createListOfMessages(0))); } @Test public void testAdapterWithNonParameterizedMessageListBasedMethod() { - CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy, + ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy, "checkCompletenessOnNonParameterizedListOfMessages"); - List> 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> 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> 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> 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> 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> createListOfMessages() { + private static MessageGroup createListOfMessages(int size) { List> messages = new ArrayList>(); - messages.add(new GenericMessage("123")); - messages.add(new GenericMessage("456")); - messages.add(new GenericMessage("789")); - return messages; + if (size > 0) { + messages.add(new GenericMessage("123")); + } + if (size > 1) { + messages.add(new GenericMessage("456")); + } + if (size > 2) { + messages.add(new GenericMessage("789")); + } + return new MessageGroup(messages, "ABC"); } - private static class AlwaysTrueCompletionStrategy { + @SuppressWarnings("unused") + private static class AlwaysTrueReleaseStrategy { public boolean checkCompleteness(List> messages) { return true; } } - private static class AlwaysFalseCompletionStrategy { + @SuppressWarnings("unused") + private static class AlwaysFalseReleaseStrategy { public boolean checkCompleteness(List> messages) { return false; } } - private static class SimpleCompletionStrategy { + @SuppressWarnings("unused") + private static class SimpleReleaseStrategy { public boolean checkCompletenessOnNonParameterizedListOfMessages(List> 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> messages) { + public boolean checkCompletenessOnListOfMessagesParametrizedWithString(List> 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 messages) { StringBuffer buffer = new StringBuffer(); for (String content : messages) { diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ResequencerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ResequencerTests.java index 6f23bfa07b..c8aee832c5 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ResequencerTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/ResequencerTests.java @@ -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 sequence = new ArrayList(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(); } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategyTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategyTests.java similarity index 60% rename from org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategyTests.java rename to org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategyTests.java index 73c1c8a3e8..6becb100af 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeCompletionStrategyTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategyTests.java @@ -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 message = MessageBuilder.withPayload("test1") .setSequenceSize(2).build(); - List> messages = new ArrayList>(); + 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 message2 = MessageBuilder.withPayload("test2") .setSequenceSize(2).build(); - List> messages = new ArrayList>(); + 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>())); - } - - @Test - public void testNullList() { - SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy(); - assertFalse(completionStrategy.isComplete(null)); + SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy(); + assertTrue(ReleaseStrategy.canRelease(new MessageGroup("FOO"))); } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorParserTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorParserTests.java index b77f2782fc..2d90f8e207 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorParserTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorParserTests.java @@ -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()); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests-context.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests-context.xml index 36b546c83c..f1f93257ad 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests-context.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests-context.xml @@ -12,7 +12,7 @@ - + @@ -31,13 +31,13 @@ diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests.java index 0f4f6ca513..10b3f0776b 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/AggregatorWithCorrelationStrategyTests.java @@ -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> 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 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 payloads) { - StringBuffer buffer = new StringBuffer(); - for (Object payload: payloads) { - buffer.append(payload.toString()); - } - return buffer.toString(); - } - - } + } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueCompletionStrategy.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueReleaseStrategy.java similarity index 90% rename from org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueCompletionStrategy.java rename to org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueReleaseStrategy.java index 7c4e5cb62f..aaca1a3b8f 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueCompletionStrategy.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/MaxValueReleaseStrategy.java @@ -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; } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/completionStrategyMethodWithMissingReference.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/ReleaseStrategyMethodWithMissingReference.xml similarity index 77% rename from org.springframework.integration/src/test/java/org/springframework/integration/config/completionStrategyMethodWithMissingReference.xml rename to org.springframework.integration/src/test/java/org/springframework/integration/config/ReleaseStrategyMethodWithMissingReference.xml index 1294e2a21f..8c5bc79734 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/completionStrategyMethodWithMissingReference.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/ReleaseStrategyMethodWithMissingReference.xml @@ -7,7 +7,7 @@ http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> - @@ -16,6 +16,6 @@ - + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/ResequencerParserTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/ResequencerParserTests.java index 8d141d41fa..65091f0f8f 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/ResequencerParserTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/ResequencerParserTests.java @@ -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()); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/TestCompletionStrategy.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/TestReleaseStrategy.java similarity index 74% rename from org.springframework.integration/src/test/java/org/springframework/integration/config/TestCompletionStrategy.java rename to org.springframework.integration/src/test/java/org/springframework/integration/config/TestReleaseStrategy.java index 4564046023..01e40d80b7 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/TestCompletionStrategy.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/TestReleaseStrategy.java @@ -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> messages) { + public boolean canRelease(MessageGroup messages) { throw new UnsupportedOperationException("This is not intended to be implemented, but to verify injection into an "); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/aggregatorParserTests.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/aggregatorParserTests.xml index b14f9aa125..00b78e0b28 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/aggregatorParserTests.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/aggregatorParserTests.xml @@ -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"/> - - + + release-strategy="pojoReleaseStrategy" + release-strategy-method="checkCompleteness"/> @@ -54,13 +54,13 @@ - + - + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java index 502716c2c6..fcbd53cc91 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java @@ -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); } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCorrelationStrategy.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCorrelationStrategy.java index 43ebafbb7b..fe72946420 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCorrelationStrategy.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCorrelationStrategy.java @@ -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 payloads) { return payloads.size() == 3; } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCompletionStrategy.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithReleaseStrategy.java similarity index 90% rename from org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCompletionStrategy.java rename to org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithReleaseStrategy.java index 0bf7ab25f0..259449622e 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithCompletionStrategy.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/TestAnnotatedEndpointWithReleaseStrategy.java @@ -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> aggregatedMessages = new ConcurrentHashMap>(); @@ -54,7 +54,7 @@ public class TestAnnotatedEndpointWithCompletionStrategy { return returnedMessage; } - @CompletionStrategy + @ReleaseStrategy public boolean completionChecker(List> messages) { return true; } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/correlationStrategyWithVoidMethods.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/correlationStrategyWithVoidMethods.xml index c7fb88f332..ba7efe8c7c 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/correlationStrategyWithVoidMethods.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/correlationStrategyWithVoidMethods.xml @@ -8,7 +8,7 @@ - + @@ -21,8 +21,8 @@ diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/invalidCompletionStrategyMethod.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/invalidReleaseStrategyMethod.xml similarity index 67% rename from org.springframework.integration/src/test/java/org/springframework/integration/config/invalidCompletionStrategyMethod.xml rename to org.springframework.integration/src/test/java/org/springframework/integration/config/invalidReleaseStrategyMethod.xml index b88c91628a..b1966afc96 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/invalidCompletionStrategyMethod.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/invalidReleaseStrategyMethod.xml @@ -7,12 +7,12 @@ http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> - + release-strategy="releaseStrategy" + release-strategy-method="invalidMethodName"/> @@ -20,11 +20,11 @@ - + - + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/store/SimpleMessageStoreTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/store/SimpleMessageStoreTests.java index 7bbe99d65b..d5e12e7745 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/store/SimpleMessageStoreTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/store/SimpleMessageStoreTests.java @@ -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 testMessage1 = MessageBuilder.withPayload("foo").build(); - Message testMessage2 = MessageBuilder.withPayload("bar").build(); - store.put("bar", Arrays.>asList(testMessage1, testMessage2)); - assertEquals(2, store.list("bar").size()); - } - }