INT-1105, INT-1063: Big merge. Removing old aggregation and correlation stuff, and reaping the reaper.
This commit is contained in:
@@ -64,7 +64,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
|
||||
protected Map<String, Object> aggregateHeaders(MessageGroup group) {
|
||||
Map<String, Object> aggregatedHeaders = new HashMap<String, Object>();
|
||||
Set<String> conflictKeys = new HashSet<String>();
|
||||
for (Message<?> message : group.getMessages()) {
|
||||
for (Message<?> message : group.getUnmarked()) {
|
||||
MessageHeaders currentHeaders = message.getHeaders();
|
||||
for (String key : currentHeaders.keySet()) {
|
||||
if (MessageHeaders.ID.equals(key) || MessageHeaders.TIMESTAMP.equals(key)
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A base class for aggregating a group of Messages into a single Message.
|
||||
* Extends {@link AbstractMessageBarrierHandler} and waits for a
|
||||
* <em>complete</em> group of {@link Message Messages} to arrive. Subclasses
|
||||
* must provide the implementation of the {@link #aggregateMessages(List)}
|
||||
* method to combine the group of Messages into a single {@link Message}.
|
||||
*
|
||||
* <p>
|
||||
* The default strategy for determining whether a group is complete is based on
|
||||
* the '<code>sequenceSize</code>' property of the header. Alternatively, a
|
||||
* custom implementation of the {@link CompletionStrategy} may be provided.
|
||||
*
|
||||
* <p>
|
||||
* All considerations regarding <code>timeout</code> and grouping by
|
||||
* <code>correlationId</code> from {@link AbstractMessageBarrierHandler} apply
|
||||
* here as well.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public abstract class AbstractMessageAggregator extends
|
||||
AbstractMessageBarrierHandler<List<Message<?>>> {
|
||||
|
||||
private volatile CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
|
||||
|
||||
/**
|
||||
* Strategy to determine whether the group of messages is complete.
|
||||
*/
|
||||
public void setCompletionStrategy(CompletionStrategy completionStrategy) {
|
||||
Assert.notNull(completionStrategy,
|
||||
"'completionStrategy' must not be null");
|
||||
this.completionStrategy = completionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "aggregator";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageBarrier<List<Message<?>>> createMessageBarrier(Object correlationKey) {
|
||||
return new MessageBarrier<List<Message<?>>>(new ArrayList<Message<?>>(), correlationKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processBarrier(MessageBarrier<List<Message<?>>> barrier) {
|
||||
if (!barrier.isComplete() && !CollectionUtils.isEmpty(barrier.getMessages())) {
|
||||
if (this.completionStrategy.isComplete(barrier.getMessages())) {
|
||||
barrier.setComplete();
|
||||
}
|
||||
}
|
||||
if (barrier.isComplete()) {
|
||||
this.removeBarrier(barrier.getCorrelationKey());
|
||||
Message<?> result = this.aggregateMessages(barrier.getMessages());
|
||||
if (result != null) {
|
||||
if (result.getHeaders().getCorrelationId() == null) {
|
||||
result = MessageBuilder.fromMessage(result)
|
||||
.setCorrelationId(barrier.getCorrelationKey())
|
||||
.build();
|
||||
}
|
||||
this.sendReply(result, this.resolveReplyChannelFromMessage(barrier.getMessages().get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Message<?> aggregateMessages(List<Message<?>> messages);
|
||||
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
import org.springframework.integration.core.MessageProducer;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.message.MessageDeliveryException;
|
||||
import org.springframework.integration.message.MessageHandler;
|
||||
import org.springframework.integration.message.MessageHandlingException;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for {@link MessageBarrier}-based Message Handlers. A
|
||||
* {@link MessageHandler} implementation that waits for a group of
|
||||
* {@link Message Messages} to arrive and processes them together. Uses a
|
||||
* {@link MessageBarrier} to store messages and to decide how the messages
|
||||
* should be released.
|
||||
* <p>
|
||||
* Each {@link Message} that is received by this handler will be associated
|
||||
* with a group based upon the '<code>correlationId</code>' property of its
|
||||
* header. If no such property is available, a {@link MessageHandlingException}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The '<code>timeout</code>' value determines how long to wait for the complete
|
||||
* group after the arrival of the first {@link Message} of the group. The
|
||||
* default value is 1 minute. If the timeout elapses prior to completion, then
|
||||
* Messages with that timed-out 'correlationId' will be sent to the
|
||||
* 'discardChannel' if provided unless 'sendPartialResultsOnTimeout' is set to
|
||||
* true in which case the incomplete group will be sent to the output channel.
|
||||
* <p>
|
||||
* Subclasses must decide what kind of a Collection they want to use. The semantics
|
||||
* of adding a Message to the MessageBarrier will be decided by the Collection type.
|
||||
* <p>
|
||||
* Note: this class is not part of the Spring Integration API, but
|
||||
* an internal class, used for implementing components that need to keep
|
||||
* a list of messages until they are ready to be released or processed
|
||||
* (e.g. Resequencer or Aggregator). As such it is subject to change in future
|
||||
* versions.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public abstract class AbstractMessageBarrierHandler<T extends Collection<? extends Message<?>>>
|
||||
extends AbstractMessageHandler implements MessageProducer, BeanFactoryAware, InitializingBean {
|
||||
|
||||
public final static long DEFAULT_SEND_TIMEOUT = 1000;
|
||||
|
||||
public final static long DEFAULT_TIMEOUT = 60000;
|
||||
|
||||
public final static long DEFAULT_REAPER_INTERVAL = 1000;
|
||||
|
||||
public final static int DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY = 1000;
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private volatile MessageChannel outputChannel;
|
||||
|
||||
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
|
||||
|
||||
private volatile MessageChannel discardChannel;
|
||||
|
||||
protected final ConcurrentMap<Object, MessageBarrier<T>> barriers = new ConcurrentHashMap<Object, MessageBarrier<T>>();
|
||||
|
||||
private volatile long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
private volatile boolean sendPartialResultOnTimeout = false;
|
||||
|
||||
private volatile long reaperInterval = DEFAULT_REAPER_INTERVAL;
|
||||
|
||||
private volatile int trackedCorrelationIdCapacity = DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY;
|
||||
|
||||
protected volatile BlockingQueue<Object> trackedCorrelationIds;
|
||||
|
||||
private volatile boolean autoStartup = true;
|
||||
|
||||
private volatile ScheduledFuture<?> reaperFutureTask;
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
|
||||
private volatile CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID);
|
||||
|
||||
|
||||
public AbstractMessageBarrierHandler() {
|
||||
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
public void setOutputChannel(MessageChannel outputChannel) {
|
||||
this.outputChannel = outputChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a channel for sending Messages that arrive after their
|
||||
* aggregation group has either completed or timed-out.
|
||||
*/
|
||||
public void setDiscardChannel(MessageChannel discardChannel) {
|
||||
this.discardChannel = discardChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to aggregate and send the resulting Message when the
|
||||
* timeout elapses prior to the CompletionStrategy returning true.
|
||||
*/
|
||||
public void setSendPartialResultOnTimeout(boolean sendPartialResultOnTimeout) {
|
||||
this.sendPartialResultOnTimeout = sendPartialResultOnTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the interval in milliseconds for the reaper thread. Default is 1000.
|
||||
*/
|
||||
public void setReaperInterval(long reaperInterval) {
|
||||
Assert.isTrue(reaperInterval > 0, "'reaperInterval' must be a positive value");
|
||||
this.reaperInterval = reaperInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of completed correlationIds to track. Default is 1000.
|
||||
*/
|
||||
public void setTrackedCorrelationIdCapacity(int trackedCorrelationIdCapacity) {
|
||||
this.trackedCorrelationIdCapacity = trackedCorrelationIdCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum time to wait (in milliseconds) for the completion strategy to
|
||||
* become true. The default is 60000 (1 minute).
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
Assert.isTrue(timeout >= 0, "'timeout' must be a positive value");
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void setSendTimeout(long sendTimeout) {
|
||||
this.channelTemplate.setSendTimeout(sendTimeout);
|
||||
}
|
||||
|
||||
public void setTaskScheduler(TaskScheduler taskScheduler) {
|
||||
super.setTaskScheduler(taskScheduler);
|
||||
}
|
||||
|
||||
public void setAutoStartup(boolean autoStartup) {
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) {
|
||||
this.correlationStrategy = correlationStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onInit() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (!this.initialized) {
|
||||
if (this.trackedCorrelationIdCapacity > 0) {
|
||||
this.trackedCorrelationIds = new ArrayBlockingQueue<Object>(this.trackedCorrelationIdCapacity);
|
||||
}
|
||||
if (this.autoStartup) {
|
||||
this.start();
|
||||
}
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return this.reaperFutureTask != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
Assert.state(this.getTaskScheduler() != null, "TaskScheduler must not be null");
|
||||
this.reaperFutureTask = this.getTaskScheduler().scheduleWithFixedDelay(
|
||||
new PrunerTask(), this.reaperInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (this.isRunning()) {
|
||||
this.reaperFutureTask.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void handleMessageInternal(Message<?> message) {
|
||||
if (!this.initialized) {
|
||||
this.afterPropertiesSet();
|
||||
}
|
||||
Object correlationKey = this.correlationStrategy.getCorrelationKey(message);
|
||||
if (correlationKey == null) {
|
||||
throw new MessageHandlingException(message, this.getClass().getSimpleName()
|
||||
+ " requires the 'correlationKey' property");
|
||||
}
|
||||
if (this.trackedCorrelationIds != null && this.trackedCorrelationIds.contains(correlationKey)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handling of Message group with correlationKey '" + correlationKey
|
||||
+ "' has already completed or timed out.");
|
||||
}
|
||||
this.discardMessage(message);
|
||||
}
|
||||
else {
|
||||
this.processMessage(message, correlationKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void discardMessage(Message<?> message) {
|
||||
if (this.discardChannel != null) {
|
||||
boolean sent = this.channelTemplate.send(message, this.discardChannel);
|
||||
if (!sent && logger.isWarnEnabled()) {
|
||||
logger.warn("unable to send to 'discardChannel', message: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void processMessage(Message<?> message, Object correlationKey) {
|
||||
MessageBarrier<T> barrier = barriers.putIfAbsent(correlationKey, createMessageBarrier(correlationKey));
|
||||
if (barrier == null) {
|
||||
barrier = barriers.get(correlationKey);
|
||||
}
|
||||
synchronized (barrier) {
|
||||
if (canAddMessage(message, barrier)) {
|
||||
((MessageBarrier)barrier).getMessages().add(message);
|
||||
}
|
||||
processBarrier(barrier);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void sendReplies(Collection<Message<?>> messages, MessageChannel defaultReplyChannel) {
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Message<?> result : messages) {
|
||||
sendReply(result, defaultReplyChannel);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void sendReply(Message<?> message, MessageChannel defaultReplyChannel) {
|
||||
MessageChannel replyChannel = this.outputChannel;
|
||||
if (replyChannel == null) {
|
||||
replyChannel = this.resolveReplyChannelFromMessage(message);
|
||||
if (replyChannel == null) {
|
||||
replyChannel = defaultReplyChannel;
|
||||
}
|
||||
}
|
||||
if (replyChannel != null) {
|
||||
if (defaultReplyChannel != null && !defaultReplyChannel.equals(replyChannel)) {
|
||||
message = MessageBuilder.fromMessage(message)
|
||||
.setHeaderIfAbsent(MessageHeaders.REPLY_CHANNEL, defaultReplyChannel)
|
||||
.build();
|
||||
}
|
||||
if (!this.channelTemplate.send(message, replyChannel)) {
|
||||
throw new MessageDeliveryException(message, "failed to send reply Message");
|
||||
}
|
||||
}
|
||||
else if (logger.isWarnEnabled()) {
|
||||
logger.warn("unable to determine reply target for aggregation result: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
protected final MessageChannel resolveReplyChannelFromMessage(Message<?> message) {
|
||||
Object replyChannel = message.getHeaders().getReplyChannel();
|
||||
if (replyChannel != null) {
|
||||
if (replyChannel instanceof MessageChannel) {
|
||||
return (MessageChannel) replyChannel;
|
||||
}
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Aggregator can only reply to a 'replyChannel' of type MessageChannel.");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final void removeBarrier(Object correlationId) {
|
||||
if (this.barriers.remove(correlationId) != null
|
||||
&& this.trackedCorrelationIds != null) {
|
||||
synchronized (this.trackedCorrelationIds) {
|
||||
boolean added = this.trackedCorrelationIds.offer(correlationId);
|
||||
if (!added) {
|
||||
this.trackedCorrelationIds.poll();
|
||||
this.trackedCorrelationIds.offer(correlationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a message can be added to the barrier. To be overridden by subclasses, which may add
|
||||
* their own verifications. Subclasses overriding this method must call the method from the superclass.
|
||||
*/
|
||||
protected boolean canAddMessage(Message<?> message, MessageBarrier<T> barrier) {
|
||||
if (barrier.isComplete()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Message received after aggregation has already completed: " + message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method for creating a MessageBarrier implementation.
|
||||
*/
|
||||
protected abstract MessageBarrier<T> createMessageBarrier(Object correlationKey);
|
||||
|
||||
/**
|
||||
* A method for processing the information in the message barrier after a message has been added or on pruning.
|
||||
* The decision as to whether the messages from the {@link MessageBarrier}
|
||||
* can be released normally belongs here, although calling code may forcibly set the MessageBarrier's 'complete'
|
||||
* flag to true before invoking the method.
|
||||
* @param barrier the {@link MessageBarrier} to be processed
|
||||
*/
|
||||
protected abstract void processBarrier(MessageBarrier<T> barrier);
|
||||
|
||||
/**
|
||||
* A method for discarding the content of the message barrier.
|
||||
* Can be overridden by subclasses.
|
||||
* @param entry
|
||||
* @param barrier
|
||||
*/
|
||||
|
||||
protected void discardBarrier(MessageBarrier<T> barrier) {
|
||||
for (Message<?> message : barrier.getMessages()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handling of Message group with correlation key '" + barrier.getCorrelationKey()+ "' has timed out.");
|
||||
}
|
||||
discardMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that runs periodically, pruning the timed-out message barriers.
|
||||
*/
|
||||
private class PrunerTask implements Runnable {
|
||||
|
||||
public void run() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
for (Map.Entry<Object, MessageBarrier<T>> entry : barriers.entrySet()) {
|
||||
if (currentTime - entry.getValue().getTimestamp() >= timeout) {
|
||||
MessageBarrier<T> barrier = entry.getValue();
|
||||
synchronized (barrier) {
|
||||
removeBarrier(entry.getKey());
|
||||
if (sendPartialResultOnTimeout) {
|
||||
barrier.setComplete();
|
||||
processBarrier(barrier);
|
||||
}
|
||||
else {
|
||||
discardBarrier(barrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +1,24 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.channel.ChannelResolver;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
@@ -46,33 +34,29 @@ import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* MessageHandler that holds a buffer of correlated messages in a MessageStore.
|
||||
* This class takes care of correlated groups of messages that can be completed
|
||||
* in batches. It is useful for aggregating, resequencing, or custom
|
||||
* MessageHandler that holds a buffer of correlated messages in a MessageStore. This class takes care of correlated
|
||||
* groups of messages that can be completed in batches. It is useful for aggregating, resequencing, or custom
|
||||
* implementations requiring correlation.
|
||||
* <p/>
|
||||
* To customize this handler inject
|
||||
* {@link org.springframework.integration.aggregator.CorrelationStrategy},
|
||||
* {@link org.springframework.integration.aggregator.CompletionStrategy}, and
|
||||
* {@link org.springframework.integration.aggregator.MessageGroupProcessor}
|
||||
* implementations as you require.
|
||||
* To customize this handler inject {@link CorrelationStrategy}, {@link ReleaseStrategy}, and
|
||||
* {@link MessageGroupProcessor} implementations as you require.
|
||||
* <p/>
|
||||
* By default the CorrelationStrategy will be a
|
||||
* HeaderAttributeCorrelationStrategy and the CompletionStrategy will be a
|
||||
* SequenceSizeCompletionStrategy.
|
||||
* By default the CorrelationStrategy will be a HeaderAttributeCorrelationStrategy and the ReleaseStrategy will be a
|
||||
* SequenceSizeReleaseStrategy.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer, Lifecycle {
|
||||
public class CorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CorrelatingMessageHandler.class);
|
||||
|
||||
private static final long DEFAULT_SEND_TIMEOUT = 1000L;
|
||||
public static final long DEFAULT_SEND_TIMEOUT = 1000L;
|
||||
|
||||
private static final long DEFAULT_REAPER_INTERVAL = 1000L;
|
||||
public static final long DEFAULT_REAPER_INTERVAL = 1000L;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 60000L;
|
||||
public static final long DEFAULT_TIMEOUT = 60000L;
|
||||
|
||||
private final MessageStore store;
|
||||
|
||||
@@ -81,7 +65,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
private volatile CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(
|
||||
MessageHeaders.CORRELATION_ID);
|
||||
|
||||
private volatile CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
private volatile ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
@@ -89,41 +73,29 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
|
||||
private volatile MessageChannel discardChannel = new NullChannel();
|
||||
|
||||
private final IdTracker tracker = new IdTracker();
|
||||
private boolean sendPartialResultOnTimeout = false;
|
||||
|
||||
private final BlockingQueue<DelayedKey> keysInBuffer = new DelayQueue<DelayedKey>();
|
||||
|
||||
private volatile ScheduledFuture<?> reaperFutureTask;
|
||||
|
||||
private volatile long reaperInterval = DEFAULT_REAPER_INTERVAL;
|
||||
|
||||
private volatile long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
private volatile boolean sendPartialResultOnTimeout;
|
||||
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
|
||||
|
||||
public CorrelatingMessageHandler(MessageStore store, CorrelationStrategy correlationStrategy,
|
||||
CompletionStrategy completionStrategy, MessageGroupProcessor processor) {
|
||||
ReleaseStrategy ReleaseStrategy, MessageGroupProcessor processor) {
|
||||
Assert.notNull(store);
|
||||
Assert.notNull(processor);
|
||||
Assert.notNull(correlationStrategy);
|
||||
Assert.notNull(completionStrategy);
|
||||
this.store = store;
|
||||
this.outputProcessor = processor;
|
||||
this.correlationStrategy = correlationStrategy;
|
||||
this.completionStrategy = completionStrategy;
|
||||
this.correlationStrategy = correlationStrategy == null ? new HeaderAttributeCorrelationStrategy(
|
||||
MessageHeaders.CORRELATION_ID) : correlationStrategy;
|
||||
this.ReleaseStrategy = ReleaseStrategy == null ? new SequenceSizeReleaseStrategy() : ReleaseStrategy;
|
||||
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
|
||||
}
|
||||
|
||||
public CorrelatingMessageHandler(MessageStore store, MessageGroupProcessor processor) {
|
||||
this(store, new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
|
||||
new SequenceSizeCompletionStrategy(), processor);
|
||||
this(store, null, null, processor);
|
||||
}
|
||||
|
||||
public CorrelatingMessageHandler(MessageGroupProcessor processor) {
|
||||
this(new SimpleMessageStore(0), new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
|
||||
new SequenceSizeCompletionStrategy(), processor);
|
||||
new SequenceSizeReleaseStrategy(), processor);
|
||||
}
|
||||
|
||||
public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) {
|
||||
@@ -131,9 +103,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
this.correlationStrategy = correlationStrategy;
|
||||
}
|
||||
|
||||
public void setCompletionStrategy(CompletionStrategy completionStrategy) {
|
||||
Assert.notNull(completionStrategy);
|
||||
this.completionStrategy = completionStrategy;
|
||||
public void setReleaseStrategy(ReleaseStrategy ReleaseStrategy) {
|
||||
Assert.notNull(ReleaseStrategy);
|
||||
this.ReleaseStrategy = ReleaseStrategy;
|
||||
}
|
||||
|
||||
public void setTaskScheduler(TaskScheduler taskScheduler) {
|
||||
@@ -141,11 +113,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void setReaperInterval(long reaperInterval) {
|
||||
this.reaperInterval = reaperInterval;
|
||||
}
|
||||
|
||||
public void setOutputChannel(MessageChannel outputChannel) {
|
||||
@@ -176,232 +146,115 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
|
||||
Object correlationKey = correlationStrategy.getCorrelationKey(message);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handling message with correlationKey [" + correlationKey + "]: " + message);
|
||||
}
|
||||
try {
|
||||
if (tracker.waitForLockIfNotTracked(correlationKey)) {
|
||||
Collection<Message<?>> messages = store.list(correlationKey);
|
||||
MessageGroup group = new MessageGroup(messages, correlationKey);
|
||||
|
||||
if (group.hasNoMessageSuperseding(message)) {
|
||||
store(message, correlationKey);
|
||||
group.add(message);
|
||||
if (completionStrategy.isComplete(group.getMessages())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Completing group with correlationKey [" + correlationKey + "]");
|
||||
}
|
||||
outputProcessor.processAndSend(group, channelTemplate, this.resolveReplyChannel(message,
|
||||
this.outputChannel));
|
||||
if (!correlationKey.equals(message.getHeaders().getCorrelationId())) {
|
||||
// TODO: strategise the treatment of overwritten correlation
|
||||
message = MessageBuilder.fromMessage(message).setCorrelationId(correlationKey).build();
|
||||
}
|
||||
|
||||
Object lock = getLock(correlationKey);
|
||||
synchronized (lock) {
|
||||
|
||||
Collection<Message<?>> messages = store.list(correlationKey);
|
||||
MessageGroup group = new MessageGroup(messages, correlationKey);
|
||||
|
||||
if (group.add(message)) {
|
||||
// TODO: use try/catch to detect problem in group.add() and use
|
||||
// that to decide on discard?
|
||||
store(message, correlationKey);
|
||||
if (ReleaseStrategy.canRelease(group)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Completing group with correlationKey [" + correlationKey + "]");
|
||||
}
|
||||
outputProcessor.processAndSend(group, channelTemplate, this.resolveReplyChannel(message,
|
||||
this.outputChannel));
|
||||
if (group.isComplete()) {
|
||||
complete(group);
|
||||
}
|
||||
}
|
||||
else {
|
||||
discardChannel.send(message);
|
||||
else {
|
||||
partialComplete(group);
|
||||
}
|
||||
} // If not releasing any messages the group might still be complete
|
||||
else if (group.isComplete()) {
|
||||
for (Message<?> discard : group.getUnmarked()) {
|
||||
discardChannel.send(discard);
|
||||
}
|
||||
complete(group);
|
||||
}
|
||||
}
|
||||
else {
|
||||
discardChannel.send(message);
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
tracker.unlock(correlationKey);
|
||||
|
||||
}
|
||||
|
||||
// TODO: arrange for this to be called if user desires, e.g. periodically
|
||||
public final boolean forceComplete(Object correlationKey) {
|
||||
|
||||
Object lock = getLock(correlationKey);
|
||||
synchronized (lock) {
|
||||
|
||||
Collection<Message<?>> all = store.list(correlationKey);
|
||||
MessageGroup group = new MessageGroup(all, correlationKey);
|
||||
if (all.size() > 0) {
|
||||
// last chance for normal completion
|
||||
if (ReleaseStrategy.canRelease(group)) {
|
||||
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator().next(),
|
||||
this.outputChannel));
|
||||
complete(group);
|
||||
}
|
||||
else {
|
||||
if (sendPartialResultOnTimeout) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Processing partially complete messages for key [" + correlationKey + "] to: "
|
||||
+ outputChannel);
|
||||
}
|
||||
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator()
|
||||
.next(), this.outputChannel));
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discarding partially complete messages for key [" + correlationKey + "] to: "
|
||||
+ discardChannel);
|
||||
}
|
||||
for (Message<?> message : all) {
|
||||
discardChannel.send(message);
|
||||
}
|
||||
}
|
||||
complete(group);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getLock(Object correlationKey) {
|
||||
locks.putIfAbsent(correlationKey, correlationKey);
|
||||
return locks.get(correlationKey);
|
||||
}
|
||||
|
||||
private void partialComplete(MessageGroup group) {
|
||||
for (Message<?> message : group.getMessages()) {
|
||||
store.delete(group.getCorrelationKey(), message.getHeaders().getId());
|
||||
for (Message<?> message : group.getUnmarked()) {
|
||||
store.mark(group.getCorrelationKey(), message.getHeaders().getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void complete(MessageGroup group) {
|
||||
tracker.pushCorrelationId(group.getCorrelationKey());
|
||||
store.deleteAll(group.getCorrelationKey());
|
||||
Object correlationKey = group.getCorrelationKey();
|
||||
store.deleteAll(correlationKey);
|
||||
locks.remove(correlationKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void store(Message<?> message, Object correlationKey) {
|
||||
Message toStore = message;
|
||||
if (!correlationKey.equals(message.getHeaders().getCorrelationId())) {
|
||||
toStore = MessageBuilder.fromMessage(message).setCorrelationId(correlationKey).build();
|
||||
}
|
||||
store.put(correlationKey, toStore);
|
||||
if (!keysInBuffer.contains(correlationKey)) {
|
||||
keysInBuffer.add(new DelayedKey(correlationKey, timeout));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return this.reaperFutureTask != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
Assert.state(this.getTaskScheduler() != null, "'taskScheduler' must not be null");
|
||||
this.reaperFutureTask = this.getTaskScheduler().scheduleWithFixedDelay(new PrunerTask(),
|
||||
this.reaperInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (this.isRunning()) {
|
||||
this.reaperFutureTask.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PrunerTask implements Runnable {
|
||||
public void run() {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("PrunerTask is running");
|
||||
}
|
||||
DelayedKey delayedKey;
|
||||
try {
|
||||
while ((delayedKey = keysInBuffer.poll(reaperInterval, TimeUnit.MILLISECONDS)) != null) {
|
||||
Object key = delayedKey.getKey();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(this + "'s PrunerTask is processing " + key);
|
||||
}
|
||||
if (!forceComplete(key)) {
|
||||
keysInBuffer.offer(delayedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean forceComplete(Object key) {
|
||||
try {
|
||||
if (tracker.tryLockFor(key)) {
|
||||
Collection<Message<?>> all = store.list(key);
|
||||
MessageGroup group = new MessageGroup(all, key);
|
||||
if (all.size() > 0) {
|
||||
// last chance for normal completion
|
||||
MessageChannel outputChannel = resolveReplyChannel(all.iterator().next(), this.outputChannel);
|
||||
if (completionStrategy.isComplete(all)) {
|
||||
outputProcessor.processAndSend(group, channelTemplate, outputChannel);
|
||||
complete(group);
|
||||
} else {
|
||||
if (sendPartialResultOnTimeout) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Processing partially complete messages for key [" + key + "] to: "
|
||||
+ outputChannel);
|
||||
}
|
||||
outputProcessor.processAndSend(group, channelTemplate, outputChannel);
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discarding partially complete messages for key [" + key + "] to: "
|
||||
+ discardChannel);
|
||||
}
|
||||
for (Message<?> message : all) {
|
||||
discardChannel.send(message);
|
||||
}
|
||||
}
|
||||
partialComplete(group);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
tracker.unlock(key);
|
||||
}
|
||||
}
|
||||
|
||||
private final class DelayedKey implements Delayed {
|
||||
|
||||
private final Object key;
|
||||
|
||||
private final Long releaseTime;
|
||||
|
||||
private final TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||
|
||||
public DelayedKey(Object correlationKey, long delay) {
|
||||
Assert.notNull(correlationKey, "'correlationKey' must not be null");
|
||||
this.key = correlationKey;
|
||||
this.releaseTime = System.currentTimeMillis() + delay;
|
||||
}
|
||||
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return unit.convert(this.releaseTime - System.currentTimeMillis(), this.unit);
|
||||
}
|
||||
|
||||
public int compareTo(Delayed o) {
|
||||
return ((Long) this.getDelay(this.unit)).compareTo(o.getDelay(this.unit));
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private final class IdTracker {
|
||||
|
||||
private final ConcurrentMap<Object, ReentrantLock> trackerLocks = new ConcurrentHashMap<Object, ReentrantLock>();
|
||||
|
||||
private final Queue<Object> trackedCorrelationIds = new LinkedBlockingQueue<Object>();
|
||||
|
||||
private void pushCorrelationId(Object correlationKey) {
|
||||
while (!trackedCorrelationIds.offer(correlationKey)) {
|
||||
// make room in the queue
|
||||
trackedCorrelationIds.poll();
|
||||
}
|
||||
trackerLocks.remove(correlationKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to check if an id is tracked and obtain a lock for
|
||||
* it. Don't forget to finally unlock afterwards.
|
||||
* @return false if the key was tracked, true after obtaining the lock
|
||||
* otherwise
|
||||
*/
|
||||
private boolean waitForLockIfNotTracked(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock == null) {
|
||||
if (trackedCorrelationIds.contains(correlationKey)) {
|
||||
// this correlation key is already processed
|
||||
// in the near past: disallow processing
|
||||
return false;
|
||||
}
|
||||
lock = new ReentrantLock();
|
||||
ReentrantLock original = trackerLocks.putIfAbsent(correlationKey, lock);
|
||||
lock = original == null ? lock : original;
|
||||
}
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tryLockFor(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock == null) {
|
||||
lock = new ReentrantLock();
|
||||
ReentrantLock original = trackerLocks.putIfAbsent(correlationKey, lock);
|
||||
lock = original == null ? lock : original;
|
||||
}
|
||||
return lock.tryLock();
|
||||
}
|
||||
|
||||
private void unlock(Object correlationKey) {
|
||||
ReentrantLock lock = trackerLocks.get(correlationKey);
|
||||
if (lock != null && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
store.put(correlationKey, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregating
|
||||
|
||||
@Override
|
||||
protected final Object aggregatePayloads(MessageGroup group) {
|
||||
Collection<Message<?>> messages = group.getMessages();
|
||||
Collection<Message<?>> messages = group.getUnmarked();
|
||||
Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups");
|
||||
List<Object> payloads = new ArrayList<Object>(messages.size());
|
||||
for (Message<?> message : messages) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
/**
|
||||
* The Default Message Aggregator implementation that combines a group of
|
||||
* messages into a single message containing a {@link List} of all payloads. The
|
||||
* elements of the List are in order of their receiving. Any MessageHeader value
|
||||
* is ignored except the <code>correlationId</code>.
|
||||
*
|
||||
* <p>
|
||||
* The default strategy for determining whether a group is complete is based
|
||||
* on the '<code>sequenceSize</code>' property of the header. Alternatively, a
|
||||
* custom implementation of the {@link CompletionStrategy} may be provided.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* All considerations regarding <code>timeout</code> and grouping by
|
||||
* <code>correlationId</code> from {@link AbstractMessageBarrierHandler} apply
|
||||
* here as well.
|
||||
* </p>
|
||||
*
|
||||
* @author Alex Peters
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public class DefaultMessageAggregator extends AbstractMessageAggregator {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Message<?> aggregateMessages(List<Message<?>> messages) {
|
||||
List<Object> payloads = new ArrayList<Object>(messages.size());
|
||||
for (Message<?> message : messages) {
|
||||
payloads.add(message.getPayload());
|
||||
}
|
||||
return MessageBuilder.withPayload(payloads).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
|
||||
/**
|
||||
* This class implements all the strategy interfaces needed for a default
|
||||
* resequencer.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultResequencerStrategies implements CorrelationStrategy, CompletionStrategy, MessageGroupProcessor {
|
||||
|
||||
private final ConcurrentMap<Object, AtomicInteger> nextMessagesToPass = new ConcurrentHashMap<Object, AtomicInteger>();
|
||||
private final ConcurrentMap<Object, AtomicInteger> lastMessagesToPass = new ConcurrentHashMap<Object, AtomicInteger>();
|
||||
|
||||
private volatile SequenceNumberComparator sequenceNumberComparator = new SequenceNumberComparator();
|
||||
|
||||
private volatile boolean releasePartialSequences;
|
||||
|
||||
public Object getCorrelationKey(Message<?> message) {
|
||||
Object key = message.getHeaders().getCorrelationId();
|
||||
nextMessagesToPass.putIfAbsent(key, new AtomicInteger(1));
|
||||
return key;
|
||||
}
|
||||
|
||||
public boolean isComplete(Collection<? extends Message<?>> messages) {
|
||||
return releasePartialSequences
|
||||
|| (!messages.isEmpty() && messages.iterator().next().getHeaders().getSequenceSize() == messages.size());
|
||||
}
|
||||
|
||||
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
|
||||
Collection<Message<?>> all = group.getMessages();
|
||||
Object correlationKey = group.getCorrelationKey();
|
||||
if (all.size() > 0) {
|
||||
List<Message<?>> sorted = new ArrayList<Message<?>>(all);
|
||||
Collections.sort(sorted, sequenceNumberComparator);
|
||||
AtomicInteger nextSequence = nextMessagesToPass.get(correlationKey);
|
||||
for (Message<?> message : sorted) {
|
||||
final int sequenceNumber = message.getHeaders().getSequenceNumber();
|
||||
if (sequenceNumber <= nextSequence.get()) {
|
||||
channelTemplate.send(message, outputChannel);
|
||||
nextSequence.compareAndSet(sequenceNumber, sequenceNumber + 1);
|
||||
}
|
||||
}
|
||||
MessageHeaders headers = sorted.get(0).getHeaders();
|
||||
if (all.size() == headers.getSequenceSize()) {
|
||||
// TODO: it's only complete if this is true...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setReleasePartialSequences(boolean releasePartialSequences) {
|
||||
this.releasePartialSequences = releasePartialSequences;
|
||||
}
|
||||
|
||||
private static class SequenceNumberComparator implements Comparator<Message<?>> {
|
||||
public int compare(Message<?> o1, Message<?> o2) {
|
||||
return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
|
||||
/**
|
||||
* Utility class for AbstractMessageBarrierHandler and its subclasses for
|
||||
* storing objects while in transit. It is a wrapper around a {@link java.util.Collection},
|
||||
* providing special properties for recording the complete status, the creation
|
||||
* time (for determining if a group of messages has timed out), and the
|
||||
* correlation id for a group of messages (available after the first message has
|
||||
* been added to it). This is a parameterized type, allowing different different
|
||||
* client classes to use different types of Collections and their respective features.
|
||||
*
|
||||
* Can store/retrieve attributes through its setAttribute() and getAttribute() methods.
|
||||
*
|
||||
* This class is not thread-safe and will be synchronized by the calling code.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class MessageBarrier<T extends Collection<? extends Message<?>>> {
|
||||
|
||||
protected final T messages;
|
||||
|
||||
private volatile boolean complete = false;
|
||||
|
||||
private Object correlationKey;
|
||||
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
public MessageBarrier(T messages, Object correlationKey) {
|
||||
this.messages = messages;
|
||||
this.correlationKey = correlationKey;
|
||||
}
|
||||
|
||||
public Object getCorrelationKey() {
|
||||
return this.correlationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creation time of this barrier as the number of milliseconds
|
||||
* since January 1, 1970.
|
||||
*
|
||||
* @see System#currentTimeMillis()
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the barrier as complete.
|
||||
*/
|
||||
public void setComplete() {
|
||||
this.complete = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the barrier has received all the messages and can proceed to
|
||||
* release them.
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return this.complete;
|
||||
}
|
||||
|
||||
public T getMessages() {
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a the value of a given attribute on the MessageBarrier.
|
||||
* @param attributeName
|
||||
* @param value
|
||||
*/
|
||||
public void setAttribute(String attributeName, Object value) {
|
||||
this.attributes.put(attributeName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a given attribute from the MessageBarrier.
|
||||
* @param attributeName
|
||||
*/
|
||||
public <V> V getAttribute(String attributeName) {
|
||||
return (V)this.attributes.get(attributeName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
/**
|
||||
* A {@link ReleaseStrategy} that releases only the first <code>n</code> messages, where <code>n</code> is a threshold.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MessageCountReleaseStrategy implements ReleaseStrategy {
|
||||
|
||||
private final int threshold;
|
||||
|
||||
/**
|
||||
* @param threshold the number of messages to accept before releasing
|
||||
*/
|
||||
public MessageCountReleaseStrategy(int threshold) {
|
||||
super();
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient constructor is only one message is required (threshold=1).
|
||||
*/
|
||||
public MessageCountReleaseStrategy() {
|
||||
this(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the group if it has more messages than the threshold and has not previously been released. Previous
|
||||
* releases leave an imprint on the group in the form of marked messages. It is possible that more messages than the
|
||||
* threshold could be released, but only if multiple consumers receive messages from the same group concurrently.
|
||||
*/
|
||||
public boolean canRelease(MessageGroup group) {
|
||||
return group.size() >= threshold && group.getMarked().size() == 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,97 +1,145 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.store.MessageStore;
|
||||
|
||||
/**
|
||||
* Represents a mutable group of correlated messages that is bound to a certain
|
||||
* {@link org.springframework.integration.store.MessageStore} and correlation
|
||||
* key. The group will grow during its lifetime, when messages are
|
||||
* <code>add</code>ed to it. <strong>This is not thread safe and should not be
|
||||
* used for long running aggregations</strong>.
|
||||
* <p/>
|
||||
* According to its
|
||||
* {@link org.springframework.integration.aggregator.CompletionStrategy} it can
|
||||
* be <i>complete</i> depending on the messages in the group.
|
||||
* <p/>
|
||||
* Optionally MessageGroupListeners can be added to get callbacks when (parts
|
||||
* of) the group are processed or the whole group is completed.
|
||||
* Represents a mutable group of correlated messages that is bound to a certain {@link MessageStore} and correlation
|
||||
* key. The group will grow during its lifetime, when messages are <code>add</code>ed to it. <strong>This is not thread
|
||||
* safe and should not be used for long running aggregations</strong>.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MessageGroup {
|
||||
|
||||
private final Object correlationKey;
|
||||
|
||||
private final Collection<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
private final Collection<Message<?>> marked = new HashSet<Message<?>>();
|
||||
|
||||
private final Collection<Message<?>> unmarked = new HashSet<Message<?>>();
|
||||
|
||||
public MessageGroup(Object correlationKey) {
|
||||
this.correlationKey = correlationKey;
|
||||
}
|
||||
|
||||
public MessageGroup(Collection<? extends Message<?>> originalMessages, Object correlationKey) {
|
||||
this.correlationKey = correlationKey;
|
||||
this.messages.addAll(originalMessages);
|
||||
this(correlationKey);
|
||||
for (Message<?> message : originalMessages) {
|
||||
add(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether messages have been added to this group
|
||||
* that supersede the given message based on its sequence id. This can be
|
||||
* helpful to avoid ending up with sequences larger than their required
|
||||
* sequence size or sequences that are missing certain sequence numbers.
|
||||
* Add a message to the internal list. This is needed to avoid hitting the underlying store or copying the internal
|
||||
* list. Use with care.
|
||||
*/
|
||||
public boolean hasNoMessageSuperseding(Message<?> message) {
|
||||
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
|
||||
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
|
||||
for (Message<?> member : messages) {
|
||||
Integer memberSequenceNumber = member.getHeaders().getSequenceNumber();
|
||||
if (messageSequenceNumber.equals(memberSequenceNumber)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean add(Message<?> message) {
|
||||
if (isMember(message)) {
|
||||
return false;
|
||||
}
|
||||
if (message.getHeaders().containsKey(MessageStore.PROCESSED)
|
||||
&& message.getHeaders().get(MessageStore.PROCESSED, Boolean.class)) {
|
||||
this.marked.add(message);
|
||||
} else {
|
||||
this.unmarked.add(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message to the internal list. This is needed to avoid hitting the
|
||||
* underlying store or copying the internal list. Use with care.
|
||||
* @return internal message list, modification is allowed, but not recommended
|
||||
*/
|
||||
protected void add(Message<?> message) {
|
||||
messages.add(message);
|
||||
public Collection<Message<?>> getUnmarked() {
|
||||
return unmarked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return internal message list, modification is allowed, but not
|
||||
* recommended
|
||||
* @return internal message list, modification is allowed, but not recommended
|
||||
*/
|
||||
public Collection<Message<?>> getMessages() {
|
||||
return messages;
|
||||
public Collection<Message<?>> getMarked() {
|
||||
return marked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the correlation key that links these messages together according
|
||||
* to a particular CorrelationStrategy
|
||||
* @return the correlation key that links these messages together according to a particular CorrelationStrategy
|
||||
*/
|
||||
public Object getCorrelationKey() {
|
||||
return correlationKey;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
if (size() == 0) {
|
||||
return true;
|
||||
}
|
||||
int sequenceSize = getSequenceSize();
|
||||
// If there is no sequence then it must be complete....
|
||||
return sequenceSize == 0 || sequenceSize == size();
|
||||
}
|
||||
|
||||
public int getSequenceSize() {
|
||||
if (size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return getOne().getHeaders().getSequenceSize();
|
||||
}
|
||||
|
||||
private Message<?> getOne() {
|
||||
return unmarked.isEmpty() ? (marked.isEmpty() ? null : marked.iterator().next()) : unmarked.iterator().next();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return marked.size() + unmarked.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether messages have been added to this group that supersede the given message based on
|
||||
* its sequence id. This can be helpful to avoid ending up with sequences larger than their required sequence size
|
||||
* or sequences that are missing certain sequence numbers.
|
||||
*/
|
||||
private boolean isMember(Message<?> message) {
|
||||
if (size() == 0) {
|
||||
return false;
|
||||
}
|
||||
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
|
||||
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
|
||||
Integer messageSequenceSize = message.getHeaders().getSequenceSize();
|
||||
if (!messageSequenceSize.equals(getSequenceSize())
|
||||
|| containsSequenceNumber(unmarked, messageSequenceNumber)
|
||||
|| containsSequenceNumber(marked, messageSequenceNumber)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsSequenceNumber(Collection<Message<?>> messages, Integer messageSequenceNumber) {
|
||||
for (Message<?> member : messages) {
|
||||
Integer memberSequenceNumber = member.getHeaders().getSequenceNumber();
|
||||
if (messageSequenceNumber.equals(memberSequenceNumber)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link AbstractMessageAggregator} adapter for methods annotated with
|
||||
* {@link Aggregator @Aggregator} annotation and for <code>aggregator</code>
|
||||
* elements (e.g. <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<Message<?>> messages) {
|
||||
if (CollectionUtils.isEmpty(messages)) {
|
||||
return null;
|
||||
}
|
||||
Object returnedValue = this.methodInvoker.executeMethod(messages);
|
||||
if (returnedValue == null) {
|
||||
return null;
|
||||
}
|
||||
if (returnedValue instanceof Message) {
|
||||
return (Message<?>) returnedValue;
|
||||
}
|
||||
return new GenericMessage<Object>(returnedValue);
|
||||
}
|
||||
|
||||
|
||||
private Method findAggregatorMethod(Object candidate) {
|
||||
Class<?> targetClass = AopUtils.getTargetClass(candidate);
|
||||
if (targetClass == null) {
|
||||
targetClass = candidate.getClass();
|
||||
}
|
||||
Method method = this.findAnnotatedMethod(targetClass);
|
||||
if (method == null) {
|
||||
method = this.findSinglePublicMethod(targetClass);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private Method findAnnotatedMethod(final Class<?> targetClass) {
|
||||
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, Aggregator.class);
|
||||
if (annotation != null) {
|
||||
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
|
||||
+ targetClass + "] with the annotation type [" + Aggregator.class.getName() + "]");
|
||||
annotatedMethod.set(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
return annotatedMethod.get();
|
||||
}
|
||||
|
||||
private Method findSinglePublicMethod(Class<?> targetClass) {
|
||||
Method result = null;
|
||||
for (Method method : targetClass.getMethods()) {
|
||||
if (!method.getDeclaringClass().equals(Object.class)) {
|
||||
if (result != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Class [" + targetClass + "] contains more than one public Method.");
|
||||
}
|
||||
result = method;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
import org.springframework.integration.store.MessageStore;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
|
||||
public class MethodInvokingAggregatorFactoryBean implements
|
||||
FactoryBean<CorrelatingMessageHandler> , InitializingBean{
|
||||
|
||||
private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE;
|
||||
|
||||
private MessageStore store = new SimpleMessageStore(DEFAULT_CAPACITY);
|
||||
|
||||
private CorrelationStrategy correlationStrategy = new HeaderAttributeCorrelationStrategy(
|
||||
MessageHeaders.CORRELATION_ID);
|
||||
|
||||
private CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
|
||||
private MessageGroupProcessor processor;
|
||||
|
||||
private Object target;
|
||||
|
||||
public void setTarget(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// build correllation strategy
|
||||
// build completion strategy
|
||||
// build processor
|
||||
}
|
||||
|
||||
public CorrelatingMessageHandler getObject() throws Exception {
|
||||
return new CorrelatingMessageHandler(store, correlationStrategy,
|
||||
completionStrategy, processor);
|
||||
}
|
||||
|
||||
public Class<? extends CorrelatingMessageHandler> getObjectType() {
|
||||
return CorrelatingMessageHandler.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,19 @@ package org.springframework.integration.aggregator;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.Header;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* MessageGroupProcessor that serves as an adapter for the invocation of a POJO method.
|
||||
@@ -41,23 +43,20 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
|
||||
private final MessageListMethodAdapter adapter;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a wrapper around the target passed in. This constructor will
|
||||
* choose the best fitting method and throw an exception when methods are
|
||||
* ambiguous or no fitting methods can be found.
|
||||
* Creates a wrapper around the target passed in. This constructor will choose the best fitting method and throw an
|
||||
* exception when methods are ambiguous or no fitting methods can be found.
|
||||
*
|
||||
* @param target the object to wrap
|
||||
* @throws IllegalStateException when no single method can be found unambiguously
|
||||
*/
|
||||
public MethodInvokingMessageGroupProcessor(Object target) {
|
||||
this.adapter = new MessageListMethodAdapter(target, this.selectMethodFrom(target));
|
||||
this.adapter = new MessageListMethodAdapter(target, this.findAggregatorMethod(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper around the object passed in. This constructor will look
|
||||
* for a named method specifically and fail when it cannot find a method
|
||||
* with the given name.
|
||||
* Creates a wrapper around the object passed in. This constructor will look for a named method specifically and
|
||||
* fail when it cannot find a method with the given name.
|
||||
*
|
||||
* @param target the object to wrap
|
||||
* @param method the name of the method to look for
|
||||
@@ -66,34 +65,54 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
this.adapter = new MessageListMethodAdapter(target, method);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final Object aggregatePayloads(MessageGroup group) {
|
||||
final Collection<Message<?>> messagesUpForProcessing = group.getMessages();
|
||||
final Collection<Message<?>> messagesUpForProcessing = group.getUnmarked();
|
||||
Object result = this.adapter.executeMethod(messagesUpForProcessing);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Method selectMethodFrom(Object target) {
|
||||
Method[] methods = target.getClass().getMethods();
|
||||
Set<Method> candidates = new HashSet<Method>(Arrays.asList(methods));
|
||||
|
||||
removeObjectMethodsFrom(candidates);
|
||||
removeVoidMethodsFrom(candidates);
|
||||
removeListIncompatibleMethodsFrom(candidates);
|
||||
Set<Method> notAnnotatedCandidates = new HashSet<Method>();
|
||||
if (candidates.size() > 1) {
|
||||
notAnnotatedCandidates.addAll(removeNotAnnotatedFrom(candidates));
|
||||
private Method findAggregatorMethod(Object candidate) {
|
||||
Class<?> targetClass = AopUtils.getTargetClass(candidate);
|
||||
if (targetClass == null) {
|
||||
targetClass = candidate.getClass();
|
||||
}
|
||||
|
||||
// if no methods are annotated we need to look in more detail in the unannotated methods
|
||||
if (candidates.size() < 1) {
|
||||
candidates = notAnnotatedCandidates;
|
||||
removeUnfittingFrom(candidates);
|
||||
Method method = this.findAnnotatedMethod(targetClass);
|
||||
if (method == null) {
|
||||
method = this.findSinglePublicMethod(targetClass);
|
||||
}
|
||||
Assert.state(candidates.size() == 1,
|
||||
"Method selection failed, there should be exactly one candidate, found [" + candidates + "]");
|
||||
return candidates.iterator().next();
|
||||
return method;
|
||||
}
|
||||
|
||||
private Method findAnnotatedMethod(final Class<?> targetClass) {
|
||||
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, Aggregator.class);
|
||||
if (annotation != null) {
|
||||
Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + targetClass
|
||||
+ "] with the annotation type [" + Aggregator.class.getName() + "]");
|
||||
annotatedMethod.set(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
return annotatedMethod.get();
|
||||
}
|
||||
|
||||
private Method findSinglePublicMethod(Class<?> targetClass) {
|
||||
Set<Method> methods = new HashSet<Method>();
|
||||
for (Method method : targetClass.getMethods()) {
|
||||
if (!method.getDeclaringClass().equals(Object.class)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
removeListIncompatibleMethodsFrom(methods);
|
||||
removeVoidMethodsFrom(methods);
|
||||
removeUnfittingFrom(methods);
|
||||
if (methods.size() > 1) {
|
||||
throw new IllegalArgumentException("Class [" + targetClass + "] contains more than one public Method.");
|
||||
}
|
||||
return methods.isEmpty() ? null : methods.iterator().next();
|
||||
}
|
||||
|
||||
private void removeListIncompatibleMethodsFrom(Set<Method> candidates) {
|
||||
@@ -101,7 +120,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
public boolean select(Method method) {
|
||||
int found = 0;
|
||||
for (Class<?> parameterClass : method.getParameterTypes()) {
|
||||
if (parameterClass.isAssignableFrom(List.class)) {
|
||||
if (Collection.class.isAssignableFrom(parameterClass)) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
@@ -118,15 +137,6 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
});
|
||||
}
|
||||
|
||||
private Set<Method> removeNotAnnotatedFrom(Set<Method> candidates) {
|
||||
return removeMethodsMatchingSelector(candidates, new MethodSelector() {
|
||||
public boolean select(Method method) {
|
||||
Aggregator annotation = method.getAnnotation(Aggregator.class);
|
||||
return (annotation == null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Set<Method> removeUnfittingFrom(Set<Method> candidates) {
|
||||
return removeMethodsMatchingSelector(candidates, new MethodSelector() {
|
||||
public boolean select(Method method) {
|
||||
@@ -141,7 +151,7 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
int candidateParametersFound = 0;
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
Class<?> parameterType = parameterTypes[i];
|
||||
if (parameterType.isAssignableFrom(List.class)) {
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
boolean headerAnnotationFound = false;
|
||||
for (Annotation annotation : parameterAnnotations[i]) {
|
||||
if (annotation instanceof Header) {
|
||||
@@ -156,14 +166,6 @@ public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMess
|
||||
return candidateParametersFound == 1;
|
||||
}
|
||||
|
||||
private void removeObjectMethodsFrom(Set<Method> candidates) {
|
||||
removeMethodsMatchingSelector(candidates, new MethodSelector() {
|
||||
public boolean select(Method method) {
|
||||
return method.getDeclaringClass().equals(Object.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Set<Method> removeMethodsMatchingSelector(Set<Method> candidates, MethodSelector selector) {
|
||||
Set<Method> removed = new HashSet<Method>();
|
||||
Iterator<Method> iterator = candidates.iterator();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,17 @@
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
|
||||
/**
|
||||
* Strategy for determining when a group of messages reaches a state of
|
||||
* completion (i.e. can trip a barrier).
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @see AbstractMessageAggregator
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface CompletionStrategy {
|
||||
public interface ReleaseStrategy {
|
||||
|
||||
boolean isComplete(Collection<? extends Message<?>> messages);
|
||||
boolean canRelease(MessageGroup group);
|
||||
|
||||
}
|
||||
@@ -17,36 +17,34 @@
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Adapter for methods annotated with
|
||||
* {@link org.springframework.integration.annotation.CompletionStrategy @CompletionStrategy}
|
||||
* and for '<code>completion-strategy</code>' elements that include a '<code>method</code>'
|
||||
* attribute (e.g. <completion-strategy ref="beanReference" method="methodName"/>).
|
||||
* {@link org.springframework.integration.annotation.ReleaseStrategy @ReleaseStrategy}
|
||||
* and for '<code>release-strategy</code>' elements that include a '<code>method</code>'
|
||||
* 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<? extends Message<?>> messages) {
|
||||
return ((Boolean) executeMethod(messages)).booleanValue();
|
||||
public boolean canRelease(MessageGroup messages) {
|
||||
return ((Boolean) executeMethod(messages.getUnmarked())).booleanValue() && messages.getMarked().isEmpty();
|
||||
}
|
||||
|
||||
|
||||
private void assertMethodReturnsBoolean() {
|
||||
Assert.isTrue(Boolean.class.equals(this.getMethod().getReturnType())
|
||||
|| boolean.class.equals(this.getMethod().getReturnType()),
|
||||
@@ -17,135 +17,65 @@
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
|
||||
/**
|
||||
* An {@link AbstractMessageBarrierHandler} that waits for a group of
|
||||
* {@link Message Messages} to arrive and re-sends them in order, sorted
|
||||
* by their <code>sequenceNumber</code>.
|
||||
* <p>
|
||||
* This handler can either release partial sequences of messages or can
|
||||
* wait for the whole sequence to arrive before re-sending them.
|
||||
* <p>
|
||||
* All considerations regarding <code>timeout</code> and grouping by
|
||||
* '<code>correlationId</code>' from {@link AbstractMessageBarrierHandler}
|
||||
* apply here as well.
|
||||
* This class implements all the strategy interfaces needed for a default
|
||||
* resequencer.
|
||||
*
|
||||
* It is assumed that all messages have the same <code>sequence_size</code> header attribute
|
||||
* and that the sequence numbers of the messages are successive, starting with
|
||||
* 1 up to <code>sequenceSize</code>. Messages that do not satisfy this condition are
|
||||
* considered out-of-sequence and thus rejected.
|
||||
*
|
||||
*
|
||||
* Note: messages with the same sequence number will be treated as equivalent
|
||||
* by this class (i.e. after a message with a given sequence number is received,
|
||||
* further messages from within the same group, that have the same sequence number,
|
||||
* will be ignored.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Alex Peters
|
||||
* @author Iwein Fuld
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*/
|
||||
public class Resequencer extends AbstractMessageBarrierHandler<SortedSet<Message<?>>> {
|
||||
public class Resequencer implements CorrelationStrategy, ReleaseStrategy, MessageGroupProcessor {
|
||||
|
||||
private volatile boolean releasePartialSequences = true;
|
||||
|
||||
private static final String LAST_RELEASED_SEQUENCE_NUMBER = "last.released.sequence.number";
|
||||
private volatile SequenceNumberComparator sequenceNumberComparator = new SequenceNumberComparator();
|
||||
|
||||
private volatile boolean releasePartialSequences;
|
||||
|
||||
public Object getCorrelationKey(Message<?> message) {
|
||||
// TODO: remove this (as its duplicating the default)
|
||||
Object correlationKey = message.getHeaders().getCorrelationId();
|
||||
return correlationKey;
|
||||
}
|
||||
|
||||
public boolean canRelease(MessageGroup messages) {
|
||||
if (releasePartialSequences) {
|
||||
List<Message<?>> sorted = new ArrayList<Message<?>>(messages.getUnmarked());
|
||||
Collections.sort(sorted, sequenceNumberComparator);
|
||||
int head = sorted.get(sorted.size() - 1).getHeaders().getSequenceNumber();
|
||||
int tail = sorted.get(0).getHeaders().getSequenceNumber() - 1;
|
||||
return tail == messages.getMarked().size() && head - tail == sorted.size();
|
||||
}
|
||||
return messages.isComplete();
|
||||
}
|
||||
|
||||
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
|
||||
Collection<Message<?>> messages = group.getUnmarked();
|
||||
if (messages.size() > 0) {
|
||||
List<Message<?>> sorted = new ArrayList<Message<?>>(messages);
|
||||
Collections.sort(sorted, sequenceNumberComparator);
|
||||
for (Message<?> message : sorted) {
|
||||
channelTemplate.send(message, outputChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setReleasePartialSequences(boolean releasePartialSequences) {
|
||||
this.releasePartialSequences = releasePartialSequences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "resequencer";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageBarrier<SortedSet<Message<?>>> createMessageBarrier(Object correlationKey) {
|
||||
MessageBarrier<SortedSet<Message<?>>> messageBarrier
|
||||
= new MessageBarrier<SortedSet<Message<?>>>(new TreeSet<Message<?>>(new MessageSequenceComparator()), correlationKey);
|
||||
messageBarrier.setAttribute(LAST_RELEASED_SEQUENCE_NUMBER, 0);
|
||||
return messageBarrier;
|
||||
private static class SequenceNumberComparator implements Comparator<Message<?>> {
|
||||
public int compare(Message<?> o1, Message<?> o2) {
|
||||
return o1.getHeaders().getSequenceNumber().compareTo(o2.getHeaders().getSequenceNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processBarrier(MessageBarrier<SortedSet<Message<?>>> barrier) {
|
||||
if (hasReceivedAllMessages(barrier)) {
|
||||
barrier.setComplete();
|
||||
}
|
||||
List<Message<?>> releasedMessages = releaseAvailableMessages(barrier);
|
||||
if (!CollectionUtils.isEmpty(releasedMessages)) {
|
||||
Message<?> lastMessage = releasedMessages.get(releasedMessages.size()-1);
|
||||
if (lastMessage.getHeaders().getSequenceNumber().equals(lastMessage.getHeaders().getSequenceSize())) {
|
||||
this.removeBarrier(barrier.getCorrelationKey());
|
||||
}
|
||||
this.sendReplies(releasedMessages, this.resolveReplyChannelFromMessage(releasedMessages.get(0)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasReceivedAllMessages(MessageBarrier<SortedSet<Message<?>>> barrier) {
|
||||
if(barrier.getMessages().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int sequenceSize = barrier.getMessages().first().getHeaders().getSequenceSize();
|
||||
int messagesCurrentlyInBarrier = barrier.getMessages().size();
|
||||
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
|
||||
return (lastReleasedSequenceNumber + messagesCurrentlyInBarrier == sequenceSize);
|
||||
}
|
||||
|
||||
private List<Message<?>> releaseAvailableMessages(MessageBarrier<SortedSet<Message<?>>> barrier) {
|
||||
if (this.releasePartialSequences || barrier.isComplete()) {
|
||||
ArrayList<Message<?>> releasedMessages = new ArrayList<Message<?>>();
|
||||
Iterator<Message<?>> it = barrier.getMessages().iterator();
|
||||
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
|
||||
while (it.hasNext()) {
|
||||
Message<?> currentMessage = it.next();
|
||||
if (lastReleasedSequenceNumber == currentMessage.getHeaders().getSequenceNumber() - 1) {
|
||||
releasedMessages.add(currentMessage);
|
||||
lastReleasedSequenceNumber = currentMessage.getHeaders().getSequenceNumber();
|
||||
it.remove();
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
barrier.setAttribute(LAST_RELEASED_SEQUENCE_NUMBER, lastReleasedSequenceNumber);
|
||||
return releasedMessages;
|
||||
}
|
||||
else {
|
||||
return new ArrayList<Message<?>>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canAddMessage(Message<?> message, MessageBarrier<SortedSet<Message<?>>> barrier) {
|
||||
if (!super.canAddMessage(message, barrier)) {
|
||||
return false;
|
||||
}
|
||||
Integer lastReleasedSequenceNumber = barrier.getAttribute(LAST_RELEASED_SEQUENCE_NUMBER);
|
||||
if (barrier.messages.contains(message)
|
||||
|| lastReleasedSequenceNumber >= message.getHeaders().getSequenceNumber()) {
|
||||
logger.debug("A message with the same sequence number has been already received: " + message);
|
||||
return false;
|
||||
}
|
||||
if (message.getHeaders().getSequenceSize() < message.getHeaders().getSequenceNumber()) {
|
||||
logger.debug("The message has a sequence number which is larger than the sequence size: "+ message);
|
||||
return false;
|
||||
}
|
||||
if (!barrier.getMessages().isEmpty() &&
|
||||
! message.getHeaders().getSequenceSize().equals(barrier.getMessages().first().getHeaders().getSequenceSize())) {
|
||||
logger.debug("The message has a sequence size which is different from other messages handled so far: " + message
|
||||
+ ", expected value is " + barrier.getMessages().first().getHeaders().getSequenceNumber());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An implementation of {@link CompletionStrategy} that simply compares the
|
||||
* current size of the message list to the expected 'sequenceSize' according to
|
||||
* the first {@link Message} in the list.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class SequenceSizeCompletionStrategy implements CompletionStrategy {
|
||||
|
||||
public boolean isComplete(Collection<? extends Message<?>> messages) {
|
||||
if (CollectionUtils.isEmpty(messages)) {
|
||||
return false;
|
||||
}
|
||||
return messages.size() != 0 && (messages.size() >= messages.iterator().next().getHeaders().getSequenceSize());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface CompletionStrategy {
|
||||
public @interface ReleaseStrategy {
|
||||
|
||||
}
|
||||
@@ -18,18 +18,20 @@ package org.springframework.integration.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.aggregator.AbstractMessageAggregator;
|
||||
import org.springframework.integration.aggregator.CompletionStrategyAdapter;
|
||||
import org.springframework.integration.aggregator.MethodInvokingAggregator;
|
||||
import org.springframework.integration.aggregator.ReleaseStrategyAdapter;
|
||||
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
|
||||
import org.springframework.integration.aggregator.CorrelationStrategyAdapter;
|
||||
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.CompletionStrategy;
|
||||
import org.springframework.integration.annotation.ReleaseStrategy;
|
||||
import org.springframework.integration.annotation.CorrelationStrategy;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.MessageHandler;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -48,49 +50,54 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler(Object bean, Method method, Aggregator annotation) {
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean, method);
|
||||
this.configureCompletionStrategy(bean, aggregator);
|
||||
this.configureCorrelationStrategy(bean, aggregator);
|
||||
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method.getName());
|
||||
ReleaseStrategyAdapter ReleaseStrategy = getReleaseStrategy(bean);
|
||||
CorrelationStrategyAdapter correlationStrategy = getCorrelationStrategy(bean);
|
||||
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(new SimpleMessageStore(), correlationStrategy, ReleaseStrategy, processor);
|
||||
String discardChannelName = annotation.discardChannel();
|
||||
if (StringUtils.hasText(discardChannelName)) {
|
||||
MessageChannel discardChannel = this.channelResolver.resolveChannelName(discardChannelName);
|
||||
Assert.notNull(discardChannel, "failed to resolve discardChannel '" + discardChannelName + "'");
|
||||
aggregator.setDiscardChannel(discardChannel);
|
||||
handler.setDiscardChannel(discardChannel);
|
||||
}
|
||||
String outputChannelName = annotation.outputChannel();
|
||||
if (StringUtils.hasText(outputChannelName)) {
|
||||
aggregator.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
|
||||
handler.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
|
||||
}
|
||||
aggregator.setSendTimeout(annotation.sendTimeout());
|
||||
aggregator.setSendPartialResultOnTimeout(annotation.sendPartialResultsOnTimeout());
|
||||
aggregator.setReaperInterval(annotation.reaperInterval());
|
||||
aggregator.setTimeout(annotation.timeout());
|
||||
aggregator.setTrackedCorrelationIdCapacity(annotation.trackedCorrelationIdCapacity());
|
||||
aggregator.setBeanFactory(this.beanFactory);
|
||||
aggregator.afterPropertiesSet();
|
||||
return aggregator;
|
||||
handler.setSendTimeout(annotation.sendTimeout());
|
||||
handler.setSendPartialResultOnTimeout(annotation.sendPartialResultsOnTimeout());
|
||||
handler.setReaperInterval(annotation.reaperInterval());
|
||||
handler.setTimeout(annotation.timeout());
|
||||
// handler.setTrackedCorrelationIdCapacity(annotation.trackedCorrelationIdCapacity());
|
||||
handler.setBeanFactory(this.beanFactory);
|
||||
handler.afterPropertiesSet();
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void configureCompletionStrategy(final Object bean, final AbstractMessageAggregator aggregator) {
|
||||
private ReleaseStrategyAdapter getReleaseStrategy(final Object bean) {
|
||||
final AtomicReference<ReleaseStrategyAdapter> reference = new AtomicReference<ReleaseStrategyAdapter>();
|
||||
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, CompletionStrategy.class);
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, ReleaseStrategy.class);
|
||||
if (annotation != null) {
|
||||
aggregator.setCompletionStrategy(new CompletionStrategyAdapter(bean, method));
|
||||
reference.set(new ReleaseStrategyAdapter(bean, method));
|
||||
}
|
||||
}
|
||||
});
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
private void configureCorrelationStrategy(final Object bean, final AbstractMessageAggregator aggregator) {
|
||||
private CorrelationStrategyAdapter getCorrelationStrategy(final Object bean) {
|
||||
final AtomicReference<CorrelationStrategyAdapter> reference = new AtomicReference<CorrelationStrategyAdapter>();
|
||||
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, CorrelationStrategy.class);
|
||||
if (annotation != null) {
|
||||
aggregator.setCorrelationStrategy(new CorrelationStrategyAdapter(bean, method));
|
||||
reference.set(new CorrelationStrategyAdapter(bean, method));
|
||||
}
|
||||
}
|
||||
});
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.config.xml;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
@@ -25,116 +26,117 @@ import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <em>aggregator</em> element of the integration namespace.
|
||||
* Registers the annotation-driven post-processors.
|
||||
*
|
||||
* Parser for the <em>aggregator</em> element of the integration namespace. Registers the annotation-driven
|
||||
* post-processors.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class AggregatorParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
private static final String COMPLETION_STRATEGY_REF_ATTRIBUTE = "completion-strategy";
|
||||
private static final String RELEASE_STRATEGY_REF_ATTRIBUTE = "release-strategy";
|
||||
|
||||
private static final String COMPLETION_STRATEGY_METHOD_ATTRIBUTE = "completion-strategy-method";
|
||||
private static final String RELEASE_STRATEGY_METHOD_ATTRIBUTE = "release-strategy-method";
|
||||
|
||||
private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy";
|
||||
private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy";
|
||||
|
||||
private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method";
|
||||
private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method";
|
||||
|
||||
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
|
||||
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
|
||||
|
||||
private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
|
||||
private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
|
||||
|
||||
private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
|
||||
private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
|
||||
|
||||
private static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE = "send-partial-result-on-timeout";
|
||||
private static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE = "send-partial-result-on-timeout";
|
||||
|
||||
private static final String REAPER_INTERVAL_ATTRIBUTE = "reaper-interval";
|
||||
private static final String REAPER_INTERVAL_ATTRIBUTE = "reaper-interval";
|
||||
|
||||
//private static final String TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE = "tracked-correlation-id-capacity";
|
||||
private static final String TIMEOUT_ATTRIBUTE = "timeout";
|
||||
|
||||
private static final String TIMEOUT_ATTRIBUTE = "timeout";
|
||||
private static final String RELEASE_STRATEGY_PROPERTY = "releaseStrategy";
|
||||
|
||||
private static final String AUTO_STARTUP_ATTRIBUTE = "auto-startup";
|
||||
|
||||
private static final String COMPLETION_STRATEGY_PROPERTY = "completionStrategy";
|
||||
|
||||
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
|
||||
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
|
||||
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(
|
||||
element, parserContext);
|
||||
String ref = element.getAttribute(REF_ATTRIBUTE);
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
|
||||
String ref = element.getAttribute(REF_ATTRIBUTE);
|
||||
BeanDefinitionBuilder builder;
|
||||
|
||||
BeanDefinitionBuilder processorBuilder = null;
|
||||
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
|
||||
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.MethodInvokingMessageGroupProcessor");
|
||||
if (innerHandlerDefinition != null) {
|
||||
processorBuilder.addConstructorArgValue(innerHandlerDefinition);
|
||||
}
|
||||
else {
|
||||
processorBuilder.addConstructorArgReference(ref);
|
||||
}
|
||||
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
|
||||
}
|
||||
else {
|
||||
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.DefaultAggregatingMessageGroupProcessor")
|
||||
.getBeanDefinition());
|
||||
}
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
|
||||
BeanDefinitionBuilder processorBuilder = null;
|
||||
|
||||
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
|
||||
String method = element.getAttribute(METHOD_ATTRIBUTE);
|
||||
processorBuilder.getRawBeanDefinition().getConstructorArgumentValues()
|
||||
.addGenericArgumentValue(method, "java.lang.String");
|
||||
}
|
||||
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
|
||||
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.MethodInvokingMessageGroupProcessor");
|
||||
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
|
||||
} else {
|
||||
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.DefaultAggregatingMessageGroupProcessor").getBeanDefinition());
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, DISCARD_CHANNEL_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, OUTPUT_CHANNEL_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_TIMEOUT_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(
|
||||
builder, element, SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REAPER_INTERVAL_ATTRIBUTE);
|
||||
// IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,
|
||||
// element, TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, AUTO_STARTUP_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TIMEOUT_ATTRIBUTE);
|
||||
this.injectPropertyWithBean(COMPLETION_STRATEGY_REF_ATTRIBUTE, COMPLETION_STRATEGY_METHOD_ATTRIBUTE,
|
||||
COMPLETION_STRATEGY_PROPERTY, "CompletionStrategyAdapter", element, builder, parserContext);
|
||||
this.injectPropertyWithBean(CORRELATION_STRATEGY_REF_ATTRIBUTE, CORRELATION_STRATEGY_METHOD_ATTRIBUTE,
|
||||
CORRELATION_STRATEGY_PROPERTY, "CorrelationStrategyAdapter", element, builder, parserContext);
|
||||
return builder;
|
||||
}
|
||||
if (innerHandlerDefinition != null) {
|
||||
processorBuilder.addConstructorArgValue(innerHandlerDefinition);
|
||||
} else {
|
||||
if (StringUtils.hasText(ref)) {
|
||||
processorBuilder.addConstructorArgReference(ref);
|
||||
}
|
||||
}
|
||||
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
|
||||
String method = element.getAttribute(METHOD_ATTRIBUTE);
|
||||
processorBuilder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
|
||||
}
|
||||
|
||||
private void injectPropertyWithBean(String beanRefAttribute, String methodRefAttribute, String beanProperty,
|
||||
String adapterClass, Element element, BeanDefinitionBuilder builder, ParserContext parserContext) {
|
||||
final String beanRef = element.getAttribute(beanRefAttribute);
|
||||
final String beanMethod = element.getAttribute(methodRefAttribute);
|
||||
if (StringUtils.hasText(beanRef)) {
|
||||
if (StringUtils.hasText(beanMethod)) {
|
||||
String adapterBeanName = this.createAdapter(beanRef, beanMethod, adapterClass, parserContext);
|
||||
builder.addPropertyReference(beanProperty, adapterBeanName);
|
||||
}
|
||||
else {
|
||||
builder.addPropertyReference(beanProperty, beanRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
|
||||
DISCARD_CHANNEL_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
|
||||
OUTPUT_CHANNEL_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
SEND_TIMEOUT_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
REAPER_INTERVAL_ATTRIBUTE);
|
||||
// IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TIMEOUT_ATTRIBUTE);
|
||||
this.injectPropertyWithBean(RELEASE_STRATEGY_REF_ATTRIBUTE,
|
||||
RELEASE_STRATEGY_METHOD_ATTRIBUTE, RELEASE_STRATEGY_PROPERTY,
|
||||
"ReleaseStrategyAdapter", element, builder, parserContext);
|
||||
this.injectPropertyWithBean(CORRELATION_STRATEGY_REF_ATTRIBUTE,
|
||||
CORRELATION_STRATEGY_METHOD_ATTRIBUTE, CORRELATION_STRATEGY_PROPERTY,
|
||||
"CorrelationStrategyAdapter", element, builder, parserContext);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private String createAdapter(String ref, String method, String unqualifiedClassName, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator." + unqualifiedClassName);
|
||||
builder.addConstructorArgReference(ref);
|
||||
builder.getRawBeanDefinition().getConstructorArgumentValues()
|
||||
.addGenericArgumentValue(method, "java.lang.String");
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
builder.getBeanDefinition(), parserContext.getRegistry());
|
||||
}
|
||||
private void injectPropertyWithBean(String beanRefAttribute, String methodRefAttribute,
|
||||
String beanProperty, String adapterClass, Element element,
|
||||
BeanDefinitionBuilder builder, ParserContext parserContext) {
|
||||
final String beanRef = element.getAttribute(beanRefAttribute);
|
||||
final String beanMethod = element.getAttribute(methodRefAttribute);
|
||||
if (StringUtils.hasText(beanRef)) {
|
||||
if (StringUtils.hasText(beanMethod)) {
|
||||
String adapterBeanName = this.createAdapter(beanRef, beanMethod, adapterClass,
|
||||
parserContext);
|
||||
builder.addPropertyReference(beanProperty, adapterBeanName);
|
||||
} else {
|
||||
builder.addPropertyReference(beanProperty, beanRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createAdapter(String ref, String method, String unqualifiedClassName,
|
||||
ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator." + unqualifiedClassName);
|
||||
builder.addConstructorArgReference(ref);
|
||||
builder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
|
||||
parserContext.getRegistry());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
|
||||
/**
|
||||
* Strategy interface for storing and retrieving messages. The interface mimics
|
||||
* the semantics for REST for the methods named after REST operations. This is
|
||||
* helpful when mapping to a RESTful API.
|
||||
* Strategy interface for storing and retrieving messages. The interface mimics the semantics for REST for the methods
|
||||
* named after REST operations. This is helpful when mapping to a RESTful API.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Iwein Fuld
|
||||
@@ -33,43 +33,40 @@ import org.springframework.integration.core.Message;
|
||||
*/
|
||||
public interface MessageStore {
|
||||
|
||||
String PROCESSED = MessageHeaders.PREFIX + "processed";
|
||||
|
||||
/**
|
||||
* Return the Message with the given id, or <i>null</i> if no Message with
|
||||
* that id exists in the MessageStore.
|
||||
* Return the Message with the given id, or <i>null</i> if no Message with that id exists in the MessageStore.
|
||||
*/
|
||||
Message<?> get(UUID id);
|
||||
|
||||
/**
|
||||
* Put the provided Message into the MessageStore. The store may need to
|
||||
* mutate the message internally, and if it does then the return value can
|
||||
* be different than the input. The id of the return value will be used as
|
||||
* an index so that the {@link #get(UUID)} and {@link #delete(Object)}
|
||||
* behave properly. Since messages are immutable, putting the same message
|
||||
* more than once is a no-op.
|
||||
* Put the provided Message into the MessageStore. The store may need to mutate the message internally, and if it
|
||||
* does then the return value can be different than the input. The id of the return value will be used as an index
|
||||
* so that the {@link #get(UUID)} and {@link #delete(Object)} behave properly. Since messages are immutable, putting
|
||||
* the same message more than once is a no-op.
|
||||
*
|
||||
* @return the message that was stored
|
||||
*/
|
||||
<T> Message<T> put(Message<T> message);
|
||||
|
||||
/**
|
||||
* Remove the Message with the given id from the MessageStore, if present,
|
||||
* and return it. If no Message with that id is present in the store, this
|
||||
* will return <i>null</i>.
|
||||
* Remove the Message with the given id from the MessageStore, if present, and return it. If no Message with that id
|
||||
* is present in the store, this will return <i>null</i>.
|
||||
*/
|
||||
Message<?> delete(UUID id);
|
||||
|
||||
/**
|
||||
* Return all Messages currently in the MessageStore that were stored using
|
||||
* {@link #put(Object, Message)} or {@link #put(Object, Collection)} with
|
||||
* this correlation id.
|
||||
* Return all Messages currently in the MessageStore that were stored using {@link #put(Object, Message)} or
|
||||
* {@link #put(Object, Collection)} with this correlation id.
|
||||
*
|
||||
* @see org.springframework.integration.core.MessageHeaders#getCorrelationId()
|
||||
*/
|
||||
Collection<Message<?>> list(Object correlationId);
|
||||
|
||||
/**
|
||||
* Store a message with an association to a correlation id. This can be used
|
||||
* to group messages together instead of storing them just under their id.
|
||||
* Store a message with an association to a correlation id. This can be used to group messages together instead of
|
||||
* storing them just under their id.
|
||||
*
|
||||
* @param correlationId the correlation id to store the message under
|
||||
* @param message a message
|
||||
@@ -77,28 +74,17 @@ public interface MessageStore {
|
||||
void put(Object correlationId, Message<?> message);
|
||||
|
||||
/**
|
||||
* Store a group of message with an association to a correlation id.
|
||||
* Mark a message from the association with this correlation id. If the message was previously added using
|
||||
* {@link #put(Object, Message)} then it will be removed and re-inserted with the {@value #PROCESSED} flag set in
|
||||
* the headers.
|
||||
*
|
||||
* @param correlationId the correlation id to store the message under
|
||||
* @param messages a collection of messages
|
||||
*
|
||||
* @see MessageStore#put(UUID, Message)
|
||||
* @param correlationId the correlation id to mark the message under
|
||||
*/
|
||||
void put(Object correlationId, Collection<Message<?>> messages);
|
||||
Message<?> mark(Object correlationId, UUID messageId);
|
||||
|
||||
/**
|
||||
* Delete a message from the association with this correlation id. If the
|
||||
* message was stored under through {@link #put(Message)} as well, then it
|
||||
* is still accessible via {@link #get(UUID)}.
|
||||
*
|
||||
* @param correlationId the correlation id to delete all messages under
|
||||
*/
|
||||
Message<?> delete(Object correlationId, UUID messageId);
|
||||
|
||||
/**
|
||||
* Delete all the messages from the association with this correlation id. If
|
||||
* the messages were stored under their id through {@link #put(Message)}
|
||||
* they are still accessible via {@link #get(UUID)}.
|
||||
* Delete all the messages from the association with this correlation id. If the messages were stored under their id
|
||||
* through {@link #put(Message)} they are still accessible via {@link #get(UUID)}.
|
||||
*
|
||||
* @param correlationId the correlation id to delete all messages under
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessagingException;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.util.UpperBound;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -101,15 +102,11 @@ public class SimpleMessageStore implements MessageStore {
|
||||
return new HashSet<Message<?>>(collection);
|
||||
}
|
||||
|
||||
public void put(Object correlationId, Collection<Message<?>> messages) {
|
||||
getMessagesInternal(correlationId).addAll(messages);
|
||||
}
|
||||
|
||||
public void put(Object correlationId, Message<?> message) {
|
||||
getMessagesInternal(correlationId).add(message);
|
||||
}
|
||||
|
||||
public Message<?> delete(Object correlationId, UUID messageId) {
|
||||
public Message<?> mark(Object correlationId, UUID messageId) {
|
||||
if (!correlationToMessage.containsKey(correlationId)) {
|
||||
return null;
|
||||
}
|
||||
@@ -119,9 +116,10 @@ public class SimpleMessageStore implements MessageStore {
|
||||
Message<?> message = (Message<?>) iterator.next();
|
||||
if (message.getHeaders().getId().equals(messageId)) {
|
||||
iterator.remove();
|
||||
result = message;
|
||||
result = MessageBuilder.fromMessage(message).setHeader(PROCESSED, true).build();
|
||||
}
|
||||
}
|
||||
messages.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1422,7 +1422,7 @@
|
||||
</xsd:annotation>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="innerEndpointDefinitionAware">
|
||||
<xsd:attribute name="completion-strategy" type="xsd:string">
|
||||
<xsd:attribute name="release-strategy" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
@@ -1431,11 +1431,11 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="completion-strategy-method" type="xsd:string">
|
||||
<xsd:attribute name="release-strategy-method" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:expected-method type-ref="@completion-strategy"/>
|
||||
<tool:expected-method type-ref="@release-strategy"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.message.MessageHandlingException;
|
||||
import org.springframework.integration.message.StringMessage;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class AggregatorEndpointTests {
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private ThreadPoolTaskScheduler taskScheduler;
|
||||
|
||||
private AbstractMessageAggregator aggregator;
|
||||
|
||||
|
||||
@Before
|
||||
public void configureAggregator() {
|
||||
this.taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
this.taskScheduler = new ThreadPoolTaskScheduler();
|
||||
this.taskScheduler.afterPropertiesSet();
|
||||
this.aggregator = new TestAggregator();
|
||||
this.aggregator.setTaskScheduler(this.taskScheduler);
|
||||
this.aggregator.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteGroupWithinTimeout() throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNotNull(reply);
|
||||
assertEquals("123456789", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteGroupWithinTimeoutWithSameId() throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
UUID id = UUID.randomUUID();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, id);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, id);
|
||||
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, id);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
//for testing the duplication scenario, the messages must be processed synchronously
|
||||
new AggregatorTestTask(this.aggregator, message1, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message2, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message3, latch).run();
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNotNull(reply);
|
||||
assertEquals("123456789", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotSendPartialResultOnTimeoutByDefault() throws InterruptedException {
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setTimeout(50);
|
||||
this.aggregator.setReaperInterval(10);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message = createMessage("123", "ABC", 2, 1, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AggregatorTestTask task = new AggregatorTestTask(this.aggregator, message, latch);
|
||||
this.taskExecutor.execute(task);
|
||||
latch.await(2000, TimeUnit.MILLISECONDS);
|
||||
assertEquals("task should have completed within timeout", 0, latch.getCount());
|
||||
Message<?> reply = replyChannel.receive(0);
|
||||
assertNull(reply);
|
||||
Message<?> discardedMessage = discardChannel.receive(2000);
|
||||
assertNotNull(discardedMessage);
|
||||
assertEquals(message, discardedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSendPartialResultOnTimeoutTrue() throws InterruptedException {
|
||||
this.aggregator.setTimeout(500);
|
||||
this.aggregator.setReaperInterval(10);
|
||||
this.aggregator.setSendPartialResultOnTimeout(true);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator, message1, latch);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator, message2, latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
this.taskExecutor.execute(task2);
|
||||
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||
assertEquals("handlers should have been invoked within time limit", 0, latch.getCount());
|
||||
Message<?> reply = replyChannel.receive(3000);
|
||||
assertNotNull("a reply message should have been received", reply);
|
||||
assertEquals("123456", reply.getPayload());
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleGroupsSimultaneously() throws InterruptedException {
|
||||
QueueChannel replyChannel1 = new QueueChannel();
|
||||
QueueChannel replyChannel2 = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel1, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel1, null);
|
||||
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel1, null);
|
||||
Message<?> message4 = createMessage("abc", "XYZ", 3, 1, replyChannel2, null);
|
||||
Message<?> message5 = createMessage("def", "XYZ", 3, 2, replyChannel2, null);
|
||||
Message<?> message6 = createMessage("ghi", "XYZ", 3, 3, replyChannel2, null);
|
||||
CountDownLatch latch = new CountDownLatch(6);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message6, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message5, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<?> reply1 = replyChannel1.receive(500);
|
||||
assertNotNull(reply1);
|
||||
assertEquals("123456789", reply1.getPayload());
|
||||
Message<?> reply2 = replyChannel2.receive(500);
|
||||
assertNotNull(reply2);
|
||||
assertEquals("abcdefghi", reply2.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscardChannelForTrackedCorrelationId() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1a", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1b", discardChannel.receive(100).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrackedCorrelationIdsCapacityAtLimit() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1a", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-2", 2, 1, 1, replyChannel, null));
|
||||
assertEquals("test-2", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-3", 3, 1, 1, replyChannel, null));
|
||||
assertEquals("test-3", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1b", discardChannel.receive(100).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrackedCorrelationIdsCapacityPassesLimit() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage("test-1a", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1a", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-2", 2, 1, 1, replyChannel, null));
|
||||
assertEquals("test-2", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-3", 3, 1, 1, replyChannel, null));
|
||||
assertEquals("test-3", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-4", 4, 1, 1, replyChannel, null));
|
||||
assertEquals("test-4", replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage("test-1b", 1, 1, 1, replyChannel, null));
|
||||
assertEquals("test-1b", replyChannel.receive(100).getPayload());
|
||||
assertNull(discardChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test(expected = MessageHandlingException.class)
|
||||
public void testExceptionThrownIfNoCorrelationId() throws InterruptedException {
|
||||
Message<?> message = createMessage("123", null, 2, 1, new QueueChannel(), null);
|
||||
this.aggregator.handleMessage(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdditionalMessageAfterCompletion() throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
|
||||
Message<?> message4 = createMessage("abc", "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(4);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNotNull(reply);
|
||||
assertEquals("123456789".length(), ((String)reply.getPayload()).length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullReturningAggregator() throws InterruptedException {
|
||||
this.aggregator = new NullReturningAggregator();
|
||||
this.aggregator.setTaskScheduler(this.taskScheduler);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1, latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2, latch);
|
||||
this.taskExecutor.execute(task2);
|
||||
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3, latch);
|
||||
this.taskExecutor.execute(task3);
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
assertNull(task3.getException());
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNull(reply);
|
||||
assertTrue(((NullReturningAggregator) this.aggregator).isAggregationComplete());
|
||||
}
|
||||
|
||||
|
||||
private static Message<?> createMessage(String payload, Object correlationId,
|
||||
int sequenceSize, int sequenceNumber, MessageChannel replyChannel, UUID predefinedId) {
|
||||
MessageBuilder<String> builder = MessageBuilder.withPayload(payload)
|
||||
.setCorrelationId(correlationId)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setSequenceNumber(sequenceNumber)
|
||||
.setReplyChannel(replyChannel);
|
||||
if (predefinedId != null) {
|
||||
builder.setHeader(MessageHeaders.ID, predefinedId);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
private static class TestAggregator extends AbstractMessageAggregator {
|
||||
|
||||
public Message<?> aggregateMessages(List<Message<?>> messages) {
|
||||
List<Message<?>> sortableList = new ArrayList<Message<?>>(messages);
|
||||
Collections.sort(sortableList, new MessageSequenceComparator());
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (Message<?> message : sortableList) {
|
||||
buffer.append(message.getPayload().toString());
|
||||
}
|
||||
return new StringMessage(buffer.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class NullReturningAggregator extends AbstractMessageAggregator {
|
||||
|
||||
private boolean aggregationComplete;
|
||||
|
||||
|
||||
public boolean isAggregationComplete() {
|
||||
return aggregationComplete;
|
||||
}
|
||||
|
||||
|
||||
public Message<?> aggregateMessages(List<Message<?>> messages) {
|
||||
this.aggregationComplete = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class AggregatorTestTask implements Runnable {
|
||||
|
||||
private AbstractMessageAggregator aggregator;
|
||||
|
||||
private Message<?> message;
|
||||
|
||||
private Exception exception;
|
||||
|
||||
private CountDownLatch latch;
|
||||
|
||||
|
||||
AggregatorTestTask(AbstractMessageAggregator aggregator, Message<?> message, CountDownLatch latch) {
|
||||
this.aggregator = aggregator;
|
||||
this.message = message;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
this.aggregator.handleMessage(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.exception = e;
|
||||
}
|
||||
finally {
|
||||
this.latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopTaskScheduler() {
|
||||
this.taskScheduler.destroy();
|
||||
this.aggregator.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
/*
|
||||
* TODO This class needs to be removed with INT-1017. We need to ensure that the tests herein are superseded by
|
||||
* MethodInvokingMessageGroupProcessorTests before deleting it entirely.
|
||||
*/
|
||||
public class AggregatorMethodResolutionTests {
|
||||
|
||||
@Test
|
||||
public void singleAnnotation() throws Exception {
|
||||
SingleAnnotationTestBean bean = new SingleAnnotationTestBean();
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean);
|
||||
Method method = this.getMethod(aggregator);
|
||||
Method expected = SingleAnnotationTestBean.class.getMethod("method1", new Class[]{List.class});
|
||||
assertEquals(expected, method);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void multipleAnnotations() {
|
||||
MultipleAnnotationTestBean bean = new MultipleAnnotationTestBean();
|
||||
new MethodInvokingAggregator(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAnnotations() throws Exception {
|
||||
NoAnnotationTestBean bean = new NoAnnotationTestBean();
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(bean);
|
||||
Method method = this.getMethod(aggregator);
|
||||
Method expected = NoAnnotationTestBean.class.getMethod("method1", new Class[]{List.class});
|
||||
assertEquals(expected, method);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void multiplePublicMethods() {
|
||||
MultiplePublicMethodTestBean bean = new MultiplePublicMethodTestBean();
|
||||
new MethodInvokingAggregator(bean);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void noPublicMethods() {
|
||||
NoPublicMethodTestBean bean = new NoPublicMethodTestBean();
|
||||
new MethodInvokingAggregator(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jdkProxy() {
|
||||
DirectChannel input = new DirectChannel();
|
||||
QueueChannel output = new QueueChannel();
|
||||
GreetingService testBean = new GreetingBean();
|
||||
ProxyFactory proxyFactory = new ProxyFactory(testBean);
|
||||
proxyFactory.setProxyTargetClass(false);
|
||||
testBean = (GreetingService) proxyFactory.getProxy();
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(testBean);
|
||||
aggregator.setAutoStartup(false);
|
||||
aggregator.setOutputChannel(output);
|
||||
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, aggregator);
|
||||
endpoint.start();
|
||||
Message<?> message = MessageBuilder.withPayload("proxy")
|
||||
.setCorrelationId("abc")
|
||||
.build();
|
||||
input.send(message);
|
||||
assertEquals("hello proxy", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cglibProxy() {
|
||||
DirectChannel input = new DirectChannel();
|
||||
QueueChannel output = new QueueChannel();
|
||||
GreetingService testBean = new GreetingBean();
|
||||
ProxyFactory proxyFactory = new ProxyFactory(testBean);
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
testBean = (GreetingService) proxyFactory.getProxy();
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(testBean);
|
||||
aggregator.setAutoStartup(false);
|
||||
aggregator.setOutputChannel(output);
|
||||
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, aggregator);
|
||||
endpoint.start();
|
||||
Message<?> message = MessageBuilder.withPayload("proxy")
|
||||
.setCorrelationId("abc")
|
||||
.build();
|
||||
input.send(message);
|
||||
assertEquals("hello proxy", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
|
||||
private Method getMethod(MethodInvokingAggregator aggregator) {
|
||||
Object invoker = new DirectFieldAccessor(aggregator).getPropertyValue("methodInvoker");
|
||||
return (Method) new DirectFieldAccessor(invoker).getPropertyValue("method");
|
||||
}
|
||||
|
||||
|
||||
private static class SingleAnnotationTestBean {
|
||||
|
||||
@Aggregator
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
public String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MultipleAnnotationTestBean {
|
||||
|
||||
@Aggregator
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
@Aggregator
|
||||
public String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NoAnnotationTestBean {
|
||||
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MultiplePublicMethodTestBean {
|
||||
|
||||
public String upperCase(String s) {
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
public String lowerCase(String s) {
|
||||
return s.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NoPublicMethodTestBean {
|
||||
|
||||
String lowerCase(String s) {
|
||||
return s.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface GreetingService {
|
||||
|
||||
String sayHello(List<String> names);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class GreetingBean implements GreetingService {
|
||||
|
||||
private String greeting = "hello";
|
||||
|
||||
public void setGreeting(String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Aggregator
|
||||
public String sayHello(List<String> names) {
|
||||
return greeting + " " + names.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -43,7 +42,7 @@ import org.springframework.integration.store.SimpleMessageStore;
|
||||
* @author Marius Bogoevici
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class NewAggregatorEndpointTests {
|
||||
public class AggregatorTests {
|
||||
|
||||
private CorrelatingMessageHandler aggregator;
|
||||
|
||||
@@ -115,34 +114,20 @@ public class NewAggregatorEndpointTests {
|
||||
aggregator.handleMessage(message6);
|
||||
aggregator.handleMessage(message4);
|
||||
aggregator.handleMessage(message2);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
|
||||
assertNotNull(reply1);
|
||||
assertThat(reply1.getPayload(), is(105));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
|
||||
assertNotNull(reply2);
|
||||
assertThat(reply2.getPayload(), is(2431));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscardChannelForTrackedCorrelationId() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage(1, "tracked", 1, 1, replyChannel, null));
|
||||
Message<?> received1 = replyChannel.receive(0);
|
||||
assertEquals(1, received1.getPayload());
|
||||
assertNotNull("Expected aggregated message, but got null", received1);
|
||||
this.aggregator.handleMessage(createMessage(2, "tracked", 1, 1, replyChannel, null));
|
||||
Message<?> received2 = discardChannel.receive(0);
|
||||
assertNotNull("Expected discarded message, but got null", received2);
|
||||
assertEquals(2, received2.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityAtLimit() {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
//this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
@@ -162,7 +147,6 @@ public class NewAggregatorEndpointTests {
|
||||
@Ignore
|
||||
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityPassesLimit() {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
//this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
@@ -258,7 +242,7 @@ public class NewAggregatorEndpointTests {
|
||||
MessageChannelTemplate channelTemplate, MessageChannel outputChannel
|
||||
) {
|
||||
Integer product = 1;
|
||||
for (Message<?> message : group.getMessages()) {
|
||||
for (Message<?> message : group.getUnmarked()) {
|
||||
product *= (Integer) message.getPayload();
|
||||
}
|
||||
channelTemplate.send(MessageBuilder.withPayload(product).build(), outputChannel);
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.message.MessageHandler;
|
||||
import org.springframework.integration.message.MessageHandlingException;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class ConcurrentAggregatorTests {
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private CorrelatingMessageHandler aggregator;
|
||||
|
||||
@Before
|
||||
public void configureAggregator() {
|
||||
this.taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(
|
||||
50), new MultiplyingProcessor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteGroupWithinTimeout() throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message3, latch));
|
||||
latch.await(10000, TimeUnit.MILLISECONDS);
|
||||
assertThat(latch.getCount(), is(0l));
|
||||
Message<?> reply = replyChannel.receive(2000);
|
||||
assertNotNull(reply);
|
||||
assertEquals(reply.getPayload(), 105);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// dropped backwards compatibility for duplicate ID's
|
||||
public void testCompleteGroupWithinTimeoutWithSameId()
|
||||
throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel,
|
||||
"ID#1");
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel,
|
||||
"ID#1");
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel,
|
||||
"ID#1");
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
// for testing the duplication scenario, the messages must be processed
|
||||
// synchronously
|
||||
new AggregatorTestTask(this.aggregator, message1, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message2, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message3, latch).run();
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNotNull(reply);
|
||||
assertEquals("123456789", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotSendPartialResultOnTimeoutByDefault()
|
||||
throws InterruptedException {
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message = createMessage(3, "ABC", 2, 1, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AggregatorTestTask task = new AggregatorTestTask(this.aggregator,
|
||||
message, latch);
|
||||
this.taskExecutor.execute(task);
|
||||
latch.await(200, TimeUnit.MILLISECONDS);
|
||||
assertEquals("Task should have completed within timeout", 0, latch
|
||||
.getCount());
|
||||
Message<?> reply = replyChannel.receive(100);
|
||||
assertNull("No message should have been sent normally", reply);
|
||||
aggregator.forceComplete("ABC");
|
||||
Message<?> discardedMessage = discardChannel.receive(100);
|
||||
assertNotNull("A message should have been discarded", discardedMessage);
|
||||
assertEquals(message, discardedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSendPartialResultOnTimeoutTrue()
|
||||
throws InterruptedException {
|
||||
this.aggregator.setSendPartialResultOnTimeout(true);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator,
|
||||
message1, latch);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator,
|
||||
message2, latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
this.taskExecutor.execute(task2);
|
||||
latch.await(300, TimeUnit.MILLISECONDS);
|
||||
assertEquals("handlers should have been invoked within time limit", 0,
|
||||
latch.getCount());
|
||||
this.aggregator.forceComplete("ABC");
|
||||
Message<?> reply = replyChannel.receive(100);
|
||||
assertNotNull("A reply message should have been received", reply);
|
||||
assertEquals(15, reply.getPayload());
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleGroupsSimultaneously() throws InterruptedException {
|
||||
QueueChannel replyChannel1 = new QueueChannel();
|
||||
QueueChannel replyChannel2 = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel1, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel1, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel1, null);
|
||||
Message<?> message4 = createMessage(11, "XYZ", 3, 1, replyChannel2,
|
||||
null);
|
||||
Message<?> message5 = createMessage(13, "XYZ", 3, 2, replyChannel2,
|
||||
null);
|
||||
Message<?> message6 = createMessage(17, "XYZ", 3, 3, replyChannel2,
|
||||
null);
|
||||
CountDownLatch latch = new CountDownLatch(6);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message6, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message5, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
|
||||
assertNotNull(reply1);
|
||||
assertThat(reply1.getPayload(), is(105));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
|
||||
assertNotNull(reply2);
|
||||
assertThat(reply2.getPayload(), is(2431));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// dropped backwards compatibility for setting capacity limit (it's always
|
||||
// Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityAtLimit() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
// this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(1, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(3, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(4, replyChannel.receive(100).getPayload());
|
||||
// next message with same correlation ID is discarded
|
||||
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(2, discardChannel.receive(100).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// dropped backwards compatibility for setting capacity limit (it's always
|
||||
// Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityPassesLimit() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
// this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(1, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(2, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(3, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(4, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel,
|
||||
null));
|
||||
assertEquals(5, replyChannel.receive(100).getPayload());
|
||||
assertNull(discardChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test(expected = MessageHandlingException.class)
|
||||
public void testExceptionThrownIfNoCorrelationId()
|
||||
throws InterruptedException {
|
||||
Message<?> message = createMessage(3, null, 2, 1, new QueueChannel(),
|
||||
null);
|
||||
this.aggregator.handleMessage(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdditionalMessageAfterCompletion()
|
||||
throws InterruptedException {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(4);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
|
||||
message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<?> reply = replyChannel.receive(100);
|
||||
assertNotNull("A message should be aggregated", reply);
|
||||
assertThat(((Integer) reply.getPayload()), is(105));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullReturningAggregator() throws InterruptedException {
|
||||
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(
|
||||
50), new NullReturningMessageProcessor());
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1,
|
||||
latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2,
|
||||
latch);
|
||||
this.taskExecutor.execute(task2);
|
||||
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3,
|
||||
latch);
|
||||
this.taskExecutor.execute(task3);
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
assertNull(task3.getException());
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNull(reply);
|
||||
}
|
||||
|
||||
private static Message<?> createMessage(Object payload,
|
||||
Object correlationId, int sequenceSize, int sequenceNumber,
|
||||
MessageChannel replyChannel, String predefinedId) {
|
||||
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload)
|
||||
.setCorrelationId(correlationId).setSequenceSize(sequenceSize)
|
||||
.setSequenceNumber(sequenceNumber)
|
||||
.setReplyChannel(replyChannel);
|
||||
if (predefinedId != null) {
|
||||
builder.setHeader(MessageHeaders.ID, predefinedId);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static class AggregatorTestTask implements Runnable {
|
||||
|
||||
private MessageHandler aggregator;
|
||||
|
||||
private Message<?> message;
|
||||
|
||||
private Exception exception;
|
||||
|
||||
private CountDownLatch latch;
|
||||
|
||||
AggregatorTestTask(MessageHandler aggregator, Message<?> message,
|
||||
CountDownLatch latch) {
|
||||
this.aggregator = aggregator;
|
||||
this.message = message;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
this.aggregator.handleMessage(message);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.exception = e;
|
||||
} finally {
|
||||
this.latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MultiplyingProcessor implements MessageGroupProcessor {
|
||||
public void processAndSend(MessageGroup group,
|
||||
MessageChannelTemplate channelTemplate,
|
||||
MessageChannel outputChannel) {
|
||||
Integer product = 1;
|
||||
for (Message<?> message : group.getUnmarked()) {
|
||||
product *= (Integer) message.getPayload();
|
||||
}
|
||||
channelTemplate.send(MessageBuilder.withPayload(product).build(),
|
||||
outputChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private class NullReturningMessageProcessor implements
|
||||
MessageGroupProcessor {
|
||||
public void processAndSend(MessageGroup group,
|
||||
MessageChannelTemplate channelTemplate,
|
||||
MessageChannel outputChannel) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -34,137 +34,107 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.internal.stubbing.answers.DoesNothing;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.store.MessageStore;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* @author Iwein Fuld
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CorrelatingMessageHandlerTests {
|
||||
|
||||
private CorrelatingMessageHandler handler;
|
||||
private CorrelatingMessageHandler handler;
|
||||
|
||||
@Mock
|
||||
private MessageStore store;
|
||||
@Mock
|
||||
private CorrelationStrategy correlationStrategy;
|
||||
|
||||
@Mock
|
||||
private CorrelationStrategy correlationStrategy;
|
||||
private ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
|
||||
|
||||
@Mock
|
||||
private CompletionStrategy completionStrategy;
|
||||
@Mock
|
||||
private MessageGroupProcessor processor;
|
||||
|
||||
@Mock
|
||||
private MessageGroupProcessor processor;
|
||||
@Mock
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
@Mock
|
||||
private MessageChannel outputChannel;
|
||||
@Before
|
||||
public void initializeSubject() {
|
||||
handler = new CorrelatingMessageHandler(new SimpleMessageStore(), correlationStrategy, ReleaseStrategy,
|
||||
processor);
|
||||
handler.setOutputChannel(outputChannel);
|
||||
doAnswer(new DoesNothing()).when(processor).processAndSend(isA(MessageGroup.class),
|
||||
isA(MessageChannelTemplate.class), eq(outputChannel));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initializeSubject() {
|
||||
handler = new CorrelatingMessageHandler(
|
||||
store, correlationStrategy, completionStrategy, processor);
|
||||
handler.setOutputChannel(outputChannel);
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
MessageGroup messageGroup = (MessageGroup) invocation.getArguments()[0];
|
||||
// TODO: remove this?
|
||||
return null;
|
||||
}
|
||||
}).when(processor).processAndSend(isA(MessageGroup.class),
|
||||
isA(MessageChannelTemplate.class), eq(outputChannel));
|
||||
}
|
||||
@Test
|
||||
public void bufferCompletesNormally() throws Exception {
|
||||
String correlationKey = "key";
|
||||
Message<?> message1 = testMessage(correlationKey, 1, 2);
|
||||
Message<?> message2 = testMessage(correlationKey, 2, 2);
|
||||
List<Message<?>> storedMessages = new ArrayList<Message<?>>();
|
||||
|
||||
@Test
|
||||
public void bufferCompletesNormally() throws Exception {
|
||||
String correlationKey = "key";
|
||||
Message<?> message1 = testMessage(correlationKey, 1);
|
||||
Message<?> message2 = testMessage(correlationKey, 2);
|
||||
List<Message<?>> storedMessages = new ArrayList<Message<?>>();
|
||||
when(correlationStrategy.getCorrelationKey(isA(Message.class))).thenReturn(correlationKey);
|
||||
|
||||
when(store.list(correlationKey)).thenReturn(storedMessages);
|
||||
handler.handleMessage(message1);
|
||||
storedMessages.add(message1);
|
||||
verifyLocks(handler, 1);
|
||||
|
||||
when(correlationStrategy.getCorrelationKey(isA(Message.class)))
|
||||
.thenReturn(correlationKey);
|
||||
handler.handleMessage(message2);
|
||||
storedMessages.add(message2);
|
||||
verifyLocks(handler, 0); // lock is removed when group is complete
|
||||
|
||||
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1))).thenReturn(false);
|
||||
verify(correlationStrategy).getCorrelationKey(message1);
|
||||
verify(correlationStrategy).getCorrelationKey(message2);
|
||||
verify(processor).processAndSend(isA(MessageGroup.class), isA(MessageChannelTemplate.class), eq(outputChannel));
|
||||
}
|
||||
|
||||
handler.handleMessage(message1);
|
||||
storedMessages.add(message1);
|
||||
private void verifyLocks(CorrelatingMessageHandler handler, int lockCount) {
|
||||
assertEquals(lockCount, ((Map<?, ?>) ReflectionTestUtils.getField(handler, "locks")).size());
|
||||
}
|
||||
|
||||
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1, message2))).thenReturn(true);
|
||||
handler.handleMessage(message2);
|
||||
storedMessages.add(message2);
|
||||
/*
|
||||
* The next test verifies that when pruning happens after the completing message arrived, but before the group was
|
||||
* processed locking prevents forced completion and the group completes normally.
|
||||
*/
|
||||
|
||||
verify(store).put(correlationKey, message1);
|
||||
verify(store).put(correlationKey, message2);
|
||||
verify(store, times(2)).list(correlationKey);
|
||||
verify(correlationStrategy).getCorrelationKey(message1);
|
||||
verify(correlationStrategy).getCorrelationKey(message2);
|
||||
verify(completionStrategy).isComplete(Arrays.<Message<?>>asList(message1));
|
||||
verify(completionStrategy).isComplete(Arrays.<Message<?>>asList(message1, message2));
|
||||
verify(processor).processAndSend(isA(MessageGroup.class),
|
||||
isA(MessageChannelTemplate.class), eq(outputChannel)
|
||||
);
|
||||
}
|
||||
@Test
|
||||
public void shouldNotPruneWhileCompleting() throws Exception {
|
||||
String correlationKey = "key";
|
||||
final Message<?> message1 = testMessage(correlationKey, 1, 2);
|
||||
final Message<?> message2 = testMessage(correlationKey, 2, 2);
|
||||
final List<Message<?>> storedMessages = new ArrayList<Message<?>>();
|
||||
|
||||
/*
|
||||
The next test verifies that when pruning happens after the completing message arrived, but before the group was
|
||||
processed locking prevents forced completion and the group completes normally.
|
||||
*/
|
||||
final CountDownLatch bothMessagesHandled = new CountDownLatch(2);
|
||||
|
||||
@Test
|
||||
public void shouldNotPruneWhileCompleting() throws Exception {
|
||||
String correlationKey = "key";
|
||||
final Message<?> message1 = testMessage(correlationKey, 1);
|
||||
final Message<?> message2 = testMessage(correlationKey, 2);
|
||||
final List<Message<?>> storedMessages = new ArrayList<Message<?>>();
|
||||
when(correlationStrategy.getCorrelationKey(isA(Message.class))).thenReturn(correlationKey);
|
||||
|
||||
final CountDownLatch bothMessagesHandled = new CountDownLatch(2);
|
||||
handler.handleMessage(message1);
|
||||
bothMessagesHandled.countDown();
|
||||
storedMessages.add(message1);
|
||||
Executors.newSingleThreadExecutor().submit(new Runnable() {
|
||||
public void run() {
|
||||
handler.handleMessage(message2);
|
||||
storedMessages.add(message2);
|
||||
bothMessagesHandled.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
when(store.list(correlationKey)).thenReturn(storedMessages);
|
||||
Thread.sleep(20);
|
||||
assertFalse(handler.forceComplete("key"));
|
||||
|
||||
when(correlationStrategy.getCorrelationKey(isA(Message.class)))
|
||||
.thenReturn(correlationKey);
|
||||
bothMessagesHandled.await();
|
||||
|
||||
when(completionStrategy.isComplete(Arrays.<Message<?>>asList(message1, message2)))
|
||||
.thenAnswer(new Answer<Boolean>() {
|
||||
public Boolean answer(InvocationOnMock invocation) throws Throwable {
|
||||
Thread.sleep(50);
|
||||
return true;
|
||||
}
|
||||
}).thenReturn(true);
|
||||
}
|
||||
|
||||
handler.handleMessage(message1);
|
||||
bothMessagesHandled.countDown();
|
||||
storedMessages.add(message1);
|
||||
Executors.newSingleThreadExecutor().submit(new Runnable() {
|
||||
public void run() {
|
||||
handler.handleMessage(message2);
|
||||
storedMessages.add(message2);
|
||||
bothMessagesHandled.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Thread.sleep(20);
|
||||
assertFalse(handler.forceComplete("key"));
|
||||
|
||||
bothMessagesHandled.await();
|
||||
verify(store).put(correlationKey, message1);
|
||||
verify(store).put(correlationKey, message2);
|
||||
verify(store).deleteAll(correlationKey);
|
||||
}
|
||||
|
||||
private Message<?> testMessage(String correlationKey, int sequenceNumber) {
|
||||
return MessageBuilder.withPayload("test" + sequenceNumber)
|
||||
.setCorrelationId(correlationKey)
|
||||
.setSequenceNumber(sequenceNumber).build();
|
||||
}
|
||||
private Message<?> testMessage(String correlationKey, int sequenceNumber, int sequenceSize) {
|
||||
return MessageBuilder.withPayload("test" + sequenceNumber).setCorrelationId(correlationKey).setSequenceNumber(
|
||||
sequenceNumber).setSequenceSize(sequenceSize).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
/**
|
||||
* @author Alex Peters
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class DefaultMessageAggregatorTests {
|
||||
|
||||
DefaultMessageAggregator aggregator = new DefaultMessageAggregator();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void aggregateMessages_withMultiplePayloads_allAsListInResultMsg() {
|
||||
List<Object> anyPayloads = Arrays.asList("foo", "bar", 123L, new Object());
|
||||
List<Message<?>> messageGroup = new ArrayList<Message<?>>(anyPayloads.size());
|
||||
for (Object payload : anyPayloads) {
|
||||
messageGroup.add(MessageBuilder.withPayload(payload).build());
|
||||
}
|
||||
Message<?> result = aggregator.aggregateMessages(messageGroup);
|
||||
assertThat((List<Object>) result.getPayload(), is(anyPayloads));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class MethodInvokingAggregatorTests {
|
||||
|
||||
private TestAggregator mockAggregator = createMock(TestAggregator.class);
|
||||
|
||||
private List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
|
||||
@Test
|
||||
public void adapterWithNonParameterizedMessageListBasedMethod() {
|
||||
expect(mockAggregator.doAggregationOnNonParameterizedListOfMessages(isA(List.class))).andStubReturn(
|
||||
new GenericMessage<String>(""));
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnNonParameterizedListOfMessages");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithWildcardParameterizedMessageBasedMethod() {
|
||||
expect(mockAggregator.doAggregationOnListOfMessagesParametrizedWithWildcard(isA(List.class))).andStubReturn(
|
||||
new GenericMessage<String>(""));
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnListOfMessagesParametrizedWithWildcard");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithTypeParameterizedMessageBasedMethod() {
|
||||
expect(mockAggregator.doAggregationOnListOfMessagesParametrizedWithString(isA(List.class))).andStubReturn(
|
||||
new GenericMessage<String>(""));
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnListOfMessagesParametrizedWithString");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithPojoBasedMethod() {
|
||||
expect(mockAggregator.doAggregationOnListOfStrings(isA(List.class))).andStubReturn(
|
||||
new GenericMessage<String>(""));
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnListOfStrings");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithPojoBasedMethodReturningObject() {
|
||||
expect(mockAggregator.doAggregationOnListOfStringsReturningLong(isA(List.class))).andStubReturn(6l);
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnListOfStringsReturningLong");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithVoidReturnType() {
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator, "doAggregationWithNoReturn");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adapterWithNullReturn() {
|
||||
MethodInvokingAggregator aggregator = new MethodInvokingAggregator(mockAggregator,
|
||||
"doAggregationOnListOfStrings");
|
||||
replay(mockAggregator);
|
||||
aggregator.aggregateMessages(messages);
|
||||
verify(mockAggregator);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void adapterWithWrongMethodName() {
|
||||
new MethodInvokingAggregator(mockAggregator, "methodThatDoesNotExist");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidParameterTypeUsingMethodName() {
|
||||
new MethodInvokingAggregator(mockAggregator, "invalidParameterType");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void tooManyParametersUsingMethodName() {
|
||||
new MethodInvokingAggregator(mockAggregator, "tooManyParameters");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void notEnoughParametersUsingMethodName() {
|
||||
new MethodInvokingAggregator(mockAggregator, "notEnoughParameters");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void listSubclassParameterUsingMethodName() {
|
||||
new MethodInvokingAggregator(mockAggregator, "ListSubclassParameter");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidParameterTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("invalidParameterType",
|
||||
String.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void tooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("tooManyParameters",
|
||||
List.class, List.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void notEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("notEnoughParameters",
|
||||
new Class[] {}));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void listSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new MethodInvokingAggregator(mockAggregator, mockAggregator.getClass().getMethod("listSubclassParameter",
|
||||
new Class[] { LinkedList.class }));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nullObject() {
|
||||
new MethodInvokingAggregator(null, "doesNotMatter");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nullMethodName() {
|
||||
String methodName = null;
|
||||
new MethodInvokingAggregator(mockAggregator, methodName);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nullMethodObject() {
|
||||
Method method = null;
|
||||
new MethodInvokingAggregator(mockAggregator, method);
|
||||
}
|
||||
|
||||
private interface TestAggregator {
|
||||
public Message<?> doAggregationOnNonParameterizedListOfMessages(List<Message> messages);
|
||||
|
||||
public Message<?> doAggregationOnListOfMessagesParametrizedWithWildcard(List<Message<?>> messages);
|
||||
|
||||
public Message<?> doAggregationOnListOfMessagesParametrizedWithString(List<Message<String>> messages);
|
||||
|
||||
public Message<?> doAggregationOnListOfStrings(List<String> messages);
|
||||
|
||||
public Long doAggregationOnListOfStringsReturningLong(List<String> messages);
|
||||
|
||||
public void doAggregationWithNoReturn(List<String> message);
|
||||
|
||||
public Message<?> invalidParameterType(String invalid);
|
||||
|
||||
public Message<?> tooManyParameters(List<?> c1, List<?> c2);
|
||||
|
||||
public Message<?> notEnoughParameters();
|
||||
|
||||
public Message<?> listSubclassParameter(LinkedList<?> l1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,7 @@
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.Header;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.eq;
|
||||
@@ -24,154 +9,324 @@ import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.Header;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MethodInvokingMessageGroupProcessorTests {
|
||||
|
||||
@Mock
|
||||
private MessageGroupListener processedCallback;
|
||||
@Mock
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
@Mock
|
||||
private MessageChannel outputChannel;
|
||||
private List<Message<?>> messagesUpForProcessing = new ArrayList<Message<?>>(3);
|
||||
|
||||
private List<Message<?>> messagesUpForProcessing = new ArrayList<Message<?>>(
|
||||
3);
|
||||
@Mock
|
||||
private MessageGroup messageGroupMock;
|
||||
@Mock
|
||||
private MessageGroup messageGroupMock;
|
||||
|
||||
@Mock
|
||||
private MessageChannelTemplate channelTemplate;
|
||||
@Mock
|
||||
private MessageChannelTemplate channelTemplate;
|
||||
|
||||
@Before
|
||||
public void initializeMessagesUpForProcessing() {
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(1).build());
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(2).build());
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(4).build());
|
||||
}
|
||||
@Before
|
||||
public void initializeMessagesUpForProcessing() {
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(1).build());
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(2).build());
|
||||
messagesUpForProcessing.add(MessageBuilder.withPayload(4).build());
|
||||
}
|
||||
|
||||
private class AnnotatedAggregatorMethod {
|
||||
@SuppressWarnings("unused")
|
||||
private class AnnotatedAggregatorMethod {
|
||||
|
||||
@Aggregator
|
||||
@SuppressWarnings("unused")
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Aggregator
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String know(List<Integer> flags) {
|
||||
return "I'm not the one ";
|
||||
}
|
||||
}
|
||||
public String know(List<Integer> flags) {
|
||||
return "I'm not the one ";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAnnotatedAggregatorMethod() throws Exception {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
|
||||
new AnnotatedAggregatorMethod());
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
|
||||
.forClass(Message.class);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
@Test
|
||||
public void shouldFindAnnotatedAggregatorMethod() throws Exception {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedAggregatorMethod());
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private class SimpleAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void shouldFindSimpleAggregatorMethod() throws Exception {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new SimpleAggregator());
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindSimpleAggregatorMethod() throws Exception {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
|
||||
new SimpleAggregator());
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
|
||||
.forClass(Message.class);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
private class UnnanotatedAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void voidMethodShouldBeIgnored(List<Integer> flags) {
|
||||
fail("this method should not be invoked");
|
||||
}
|
||||
|
||||
private class UnnanotatedAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public String methodAcceptingNoCollectionShouldBeIgnored(@Header String irrelevant) {
|
||||
fail("this method should not be invoked");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void voidMethodShouldBeIgnored(List<Integer> flags) {
|
||||
fail("this method should not be invoked");
|
||||
}
|
||||
@Test
|
||||
public void shouldFindFittingMethodAmongMultipleUnannotated() {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new UnnanotatedAggregator());
|
||||
|
||||
public String methodAcceptingNoCollectionShouldBeIgnored(@Header String irrelevant) {
|
||||
fail("this method should not be invoked");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
|
||||
@Test
|
||||
public void shouldFindFittingMethodAmongMultipleUnanotated() {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
|
||||
new UnnanotatedAggregator()
|
||||
);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
|
||||
.forClass(Message.class);
|
||||
@SuppressWarnings("unused")
|
||||
private class AnnotatedParametersAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel
|
||||
);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
public String listHeaderShouldBeIgnored(@Header List<Integer> flags) {
|
||||
fail("this method should not be invoked");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private class AnnotatedParametersAggregator {
|
||||
public Integer and(List<Integer> flags) {
|
||||
int result = 0;
|
||||
for (Integer flag : flags) {
|
||||
result = result | flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Test
|
||||
public void shouldFindFittingMethodAmongMultipleWithAnnotatedParameters() {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
|
||||
|
||||
public String listHeaderShouldBeIgnored(@Header List<Integer> flags) {
|
||||
fail("this method should not be invoked");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
|
||||
@Test
|
||||
public void shouldFindFittingMethodAmongMultipleWithAnnotatedParameters() {
|
||||
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(
|
||||
new AnnotatedParametersAggregator()
|
||||
);
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getUnmarked()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor
|
||||
.forClass(Message.class);
|
||||
@Test
|
||||
public void singleAnnotation() throws Exception {
|
||||
SingleAnnotationTestBean bean = new SingleAnnotationTestBean();
|
||||
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean);
|
||||
Method method = this.getMethod(aggregator);
|
||||
Method expected = SingleAnnotationTestBean.class.getMethod("method1", new Class[] { List.class });
|
||||
assertEquals(expected, method);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void multipleAnnotations() {
|
||||
MultipleAnnotationTestBean bean = new MultipleAnnotationTestBean();
|
||||
new MethodInvokingMessageGroupProcessor(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAnnotations() throws Exception {
|
||||
NoAnnotationTestBean bean = new NoAnnotationTestBean();
|
||||
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(bean);
|
||||
Method method = this.getMethod(aggregator);
|
||||
Method expected = NoAnnotationTestBean.class.getMethod("method1", new Class[] { List.class });
|
||||
assertEquals(expected, method);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void multiplePublicMethods() {
|
||||
MultiplePublicMethodTestBean bean = new MultiplePublicMethodTestBean();
|
||||
new MethodInvokingMessageGroupProcessor(bean);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void noPublicMethods() {
|
||||
NoPublicMethodTestBean bean = new NoPublicMethodTestBean();
|
||||
new MethodInvokingMessageGroupProcessor(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jdkProxy() {
|
||||
DirectChannel input = new DirectChannel();
|
||||
QueueChannel output = new QueueChannel();
|
||||
GreetingService testBean = new GreetingBean();
|
||||
ProxyFactory proxyFactory = new ProxyFactory(testBean);
|
||||
proxyFactory.setProxyTargetClass(false);
|
||||
testBean = (GreetingService) proxyFactory.getProxy();
|
||||
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
|
||||
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
|
||||
handler.setOutputChannel(output);
|
||||
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);
|
||||
endpoint.start();
|
||||
Message<?> message = MessageBuilder.withPayload("proxy").setCorrelationId("abc").build();
|
||||
input.send(message);
|
||||
assertEquals("hello proxy", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cglibProxy() {
|
||||
DirectChannel input = new DirectChannel();
|
||||
QueueChannel output = new QueueChannel();
|
||||
GreetingService testBean = new GreetingBean();
|
||||
ProxyFactory proxyFactory = new ProxyFactory(testBean);
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
testBean = (GreetingService) proxyFactory.getProxy();
|
||||
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
|
||||
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
|
||||
handler.setOutputChannel(output);
|
||||
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);
|
||||
endpoint.start();
|
||||
Message<?> message = MessageBuilder.withPayload("proxy").setCorrelationId("abc").build();
|
||||
input.send(message);
|
||||
assertEquals("hello proxy", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
private Method getMethod(MethodInvokingMessageGroupProcessor aggregator) {
|
||||
Object invoker = new DirectFieldAccessor(aggregator).getPropertyValue("adapter");
|
||||
return (Method) new DirectFieldAccessor(invoker).getPropertyValue("method");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SingleAnnotationTestBean {
|
||||
|
||||
@Aggregator
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
public String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MultipleAnnotationTestBean {
|
||||
|
||||
@Aggregator
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
@Aggregator
|
||||
public String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class NoAnnotationTestBean {
|
||||
|
||||
public String method1(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
|
||||
String method2(List<String> input) {
|
||||
return input.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MultiplePublicMethodTestBean {
|
||||
|
||||
public String upperCase(String s) {
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
public String lowerCase(String s) {
|
||||
return s.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class NoPublicMethodTestBean {
|
||||
|
||||
String lowerCase(String s) {
|
||||
return s.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public interface GreetingService {
|
||||
|
||||
String sayHello(List<String> names);
|
||||
|
||||
}
|
||||
|
||||
public static class GreetingBean implements GreetingService {
|
||||
|
||||
private String greeting = "hello";
|
||||
|
||||
public void setGreeting(String greeting) {
|
||||
this.greeting = greeting;
|
||||
}
|
||||
|
||||
@Aggregator
|
||||
public String sayHello(List<String> names) {
|
||||
return greeting + " " + names.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
when(outputChannel.send(isA(Message.class))).thenReturn(true);
|
||||
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
|
||||
processor.processAndSend(messageGroupMock, channelTemplate, outputChannel
|
||||
);
|
||||
// verify
|
||||
verify(channelTemplate).send(messageCaptor.capture(), eq(outputChannel));
|
||||
assertThat((Integer) messageCaptor.getValue().getPayload(), is(7));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.integration.channel.MessageChannelTemplate;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.core.MessageHeaders;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.message.MessageHandler;
|
||||
import org.springframework.integration.message.MessageHandlingException;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class NewConcurrentAggregatorEndpointTests {
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private ThreadPoolTaskScheduler taskScheduler;
|
||||
|
||||
private CorrelatingMessageHandler aggregator;
|
||||
|
||||
@Before
|
||||
public void configureAggregator() {
|
||||
this.taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
this.taskScheduler = new ThreadPoolTaskScheduler();
|
||||
taskScheduler.afterPropertiesSet();
|
||||
this.taskScheduler.afterPropertiesSet();
|
||||
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(50), new MultiplyingProcessor());
|
||||
this.aggregator.setTaskScheduler(this.taskScheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteGroupWithinTimeout() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
latch.await(10000, TimeUnit.MILLISECONDS);
|
||||
assertThat(latch.getCount(), is(0l));
|
||||
Message<?> reply = replyChannel.receive(2000);
|
||||
assertNotNull(reply);
|
||||
assertEquals(reply.getPayload(), 105);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
//dropped backwards compatibility for duplicate ID's
|
||||
public void testCompleteGroupWithinTimeoutWithSameId() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, "ID#1");
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, "ID#1");
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, "ID#1");
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
//for testing the duplication scenario, the messages must be processed synchronously
|
||||
new AggregatorTestTask(this.aggregator, message1, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message2, latch).run();
|
||||
new AggregatorTestTask(this.aggregator, message3, latch).run();
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNotNull(reply);
|
||||
assertEquals("123456789", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldNotSendPartialResultOnTimeoutByDefault() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
this.aggregator.setTimeout(50);
|
||||
this.aggregator.setReaperInterval(10);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message = createMessage(3, "ABC", 2, 1, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AggregatorTestTask task = new AggregatorTestTask(this.aggregator, message, latch);
|
||||
this.taskExecutor.execute(task);
|
||||
latch.await(2000, TimeUnit.MILLISECONDS);
|
||||
assertEquals("Task should have completed within timeout", 0, latch.getCount());
|
||||
Message<?> reply = replyChannel.receive(100);
|
||||
assertNull("No message should have been sent normally", reply);
|
||||
Message<?> discardedMessage = discardChannel.receive(1000);
|
||||
assertNotNull("A message should have been discarded", discardedMessage);
|
||||
assertEquals(message, discardedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSendPartialResultOnTimeoutTrue() throws InterruptedException {
|
||||
this.aggregator.setTimeout(500);
|
||||
this.aggregator.setReaperInterval(10);
|
||||
this.aggregator.setSendPartialResultOnTimeout(true);
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(this.aggregator, message1, latch);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(this.aggregator, message2, latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
this.taskExecutor.execute(task2);
|
||||
latch.await(3000, TimeUnit.MILLISECONDS);
|
||||
assertEquals("handlers should have been invoked within time limit", 0, latch.getCount());
|
||||
Message<?> reply = replyChannel.receive(3000);
|
||||
assertNotNull("A reply message should have been received", reply);
|
||||
assertEquals(15, reply.getPayload());
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleGroupsSimultaneously() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel1 = new QueueChannel();
|
||||
QueueChannel replyChannel2 = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel1, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel1, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel1, null);
|
||||
Message<?> message4 = createMessage(11, "XYZ", 3, 1, replyChannel2, null);
|
||||
Message<?> message5 = createMessage(13, "XYZ", 3, 2, replyChannel2, null);
|
||||
Message<?> message6 = createMessage(17, "XYZ", 3, 3, replyChannel2, null);
|
||||
CountDownLatch latch = new CountDownLatch(6);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message6, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message5, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
|
||||
assertNotNull(reply1);
|
||||
assertThat(reply1.getPayload(), is(105));
|
||||
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
|
||||
assertNotNull(reply2);
|
||||
assertThat(reply2.getPayload(), is(2431));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityAtLimit() {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
//this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
|
||||
assertEquals(1, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel, null));
|
||||
assertEquals(3, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel, null));
|
||||
assertEquals(4, replyChannel.receive(100).getPayload());
|
||||
//next message with same correlation ID is discarded
|
||||
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel, null));
|
||||
assertEquals(2, discardChannel.receive(100).getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
//dropped backwards compatibility for setting capacity limit (it's always Integer.MAX_VALUE)
|
||||
public void testTrackedCorrelationIdsCapacityPassesLimit() {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
//this.aggregator.setTrackedCorrelationIdCapacity(3);
|
||||
this.aggregator.setDiscardChannel(discardChannel);
|
||||
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
|
||||
assertEquals(1, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel, null));
|
||||
assertEquals(2, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel, null));
|
||||
assertEquals(3, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel, null));
|
||||
assertEquals(4, replyChannel.receive(100).getPayload());
|
||||
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel, null));
|
||||
assertEquals(5, replyChannel.receive(100).getPayload());
|
||||
assertNull(discardChannel.receive(0));
|
||||
}
|
||||
|
||||
@Test(expected = MessageHandlingException.class)
|
||||
public void testExceptionThrownIfNoCorrelationId() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
Message<?> message = createMessage(3, null, 2, 1, new QueueChannel(), null);
|
||||
this.aggregator.handleMessage(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdditionalMessageAfterCompletion() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(4);
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message1, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message2, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message3, latch));
|
||||
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator, message4, latch));
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
Message<?> reply = replyChannel.receive(100);
|
||||
assertNotNull("A message should be aggregated", reply);
|
||||
assertThat(((Integer) reply.getPayload()), is(105));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullReturningAggregator() throws InterruptedException {
|
||||
this.aggregator.start();
|
||||
this.aggregator = new CorrelatingMessageHandler(new SimpleMessageStore(50), new NullReturningMessageProcessor());
|
||||
this.aggregator.setTaskScheduler(this.taskScheduler);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
|
||||
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
|
||||
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
AggregatorTestTask task1 = new AggregatorTestTask(aggregator, message1, latch);
|
||||
this.taskExecutor.execute(task1);
|
||||
AggregatorTestTask task2 = new AggregatorTestTask(aggregator, message2, latch);
|
||||
this.taskExecutor.execute(task2);
|
||||
AggregatorTestTask task3 = new AggregatorTestTask(aggregator, message3, latch);
|
||||
this.taskExecutor.execute(task3);
|
||||
latch.await(1000, TimeUnit.MILLISECONDS);
|
||||
assertNull(task1.getException());
|
||||
assertNull(task2.getException());
|
||||
assertNull(task3.getException());
|
||||
Message<?> reply = replyChannel.receive(500);
|
||||
assertNull(reply);
|
||||
}
|
||||
|
||||
|
||||
private static Message<?> createMessage(Object payload, Object correlationId,
|
||||
int sequenceSize, int sequenceNumber, MessageChannel replyChannel, String predefinedId) {
|
||||
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload)
|
||||
.setCorrelationId(correlationId)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setSequenceNumber(sequenceNumber)
|
||||
.setReplyChannel(replyChannel);
|
||||
if (predefinedId != null) {
|
||||
builder.setHeader(MessageHeaders.ID, predefinedId);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
private static class AggregatorTestTask implements Runnable {
|
||||
|
||||
private MessageHandler aggregator;
|
||||
|
||||
private Message<?> message;
|
||||
|
||||
private Exception exception;
|
||||
|
||||
private CountDownLatch latch;
|
||||
|
||||
|
||||
AggregatorTestTask(MessageHandler aggregator, Message<?> message, CountDownLatch latch) {
|
||||
this.aggregator = aggregator;
|
||||
this.message = message;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
this.aggregator.handleMessage(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.exception = e;
|
||||
}
|
||||
finally {
|
||||
this.latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopTaskScheduler() {
|
||||
if (this.taskScheduler != null) this.taskScheduler.destroy();
|
||||
if (this.aggregator != null) this.aggregator.stop();
|
||||
}
|
||||
|
||||
private class MultiplyingProcessor implements MessageGroupProcessor {
|
||||
public void processAndSend(MessageGroup group,
|
||||
MessageChannelTemplate channelTemplate, MessageChannel outputChannel
|
||||
) {
|
||||
Integer product = 1;
|
||||
for (Message<?> message : group.getMessages()) {
|
||||
product *= (Integer) message.getPayload();
|
||||
}
|
||||
channelTemplate.send(MessageBuilder.withPayload(product).build(), outputChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private class NullReturningMessageProcessor implements MessageGroupProcessor {
|
||||
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
|
||||
//noop
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,10 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -24,178 +23,157 @@ import java.util.List;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class CompletionStrategyAdapterTests {
|
||||
|
||||
private SimpleCompletionStrategy simpleCompletionStrategy;
|
||||
public class ReleaseStrategyAdapterTests {
|
||||
|
||||
private SimpleReleaseStrategy simpleReleaseStrategy;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
simpleCompletionStrategy = new SimpleCompletionStrategy();
|
||||
simpleReleaseStrategy = new SimpleReleaseStrategy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTrueConvertedProperly() {
|
||||
CompletionStrategyAdapter adapter = new CompletionStrategyAdapter(new AlwaysTrueCompletionStrategy(),
|
||||
ReleaseStrategyAdapter adapter = new ReleaseStrategyAdapter(new AlwaysTrueReleaseStrategy(),
|
||||
"checkCompleteness");
|
||||
Assert.assertTrue(adapter.isComplete(new ArrayList<Message<?>>()));
|
||||
Assert.assertTrue(adapter.canRelease(createListOfMessages(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalseConvertedProperly() {
|
||||
CompletionStrategyAdapter adapter = new CompletionStrategyAdapter(new AlwaysFalseCompletionStrategy(),
|
||||
ReleaseStrategyAdapter adapter = new ReleaseStrategyAdapter(new AlwaysFalseReleaseStrategy(),
|
||||
"checkCompleteness");
|
||||
Assert.assertTrue(!adapter.isComplete(new ArrayList<Message<?>>()));
|
||||
Assert.assertTrue(!adapter.canRelease(createListOfMessages(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdapterWithNonParameterizedMessageListBasedMethod() {
|
||||
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
|
||||
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
|
||||
"checkCompletenessOnNonParameterizedListOfMessages");
|
||||
List<Message<?>> messages = createListOfMessages();
|
||||
Assert.assertTrue(adapter.isComplete(messages));
|
||||
MessageGroup messages = createListOfMessages(3);
|
||||
Assert.assertTrue(adapter.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdapterWithWildcardParametrizedMessageBasedMethod() {
|
||||
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
|
||||
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
|
||||
"checkCompletenessOnListOfMessagesParametrizedWithWildcard");
|
||||
List<Message<?>> messages = createListOfMessages();
|
||||
Assert.assertTrue(adapter.isComplete(messages));
|
||||
MessageGroup messages = createListOfMessages(3);
|
||||
Assert.assertTrue(adapter.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdapterWithTypeParametrizedMessageBasedMethod() {
|
||||
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
|
||||
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy,
|
||||
"checkCompletenessOnListOfMessagesParametrizedWithString");
|
||||
List<Message<?>> messages = createListOfMessages();
|
||||
Assert.assertTrue(adapter.isComplete(messages));
|
||||
MessageGroup messages = createListOfMessages(3);
|
||||
Assert.assertTrue(adapter.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdapterWithPojoBasedMethod() {
|
||||
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
|
||||
"checkCompletenessOnListOfStrings");
|
||||
List<Message<?>> messages = createListOfMessages();
|
||||
Assert.assertTrue(adapter.isComplete(messages));
|
||||
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy, "checkCompletenessOnListOfStrings");
|
||||
MessageGroup messages = createListOfMessages(3);
|
||||
Assert.assertTrue(adapter.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdapterWithPojoBasedMethodReturningObject() {
|
||||
CompletionStrategy adapter = new CompletionStrategyAdapter(simpleCompletionStrategy,
|
||||
"checkCompletenessOnListOfStrings");
|
||||
List<Message<?>> messages = createListOfMessages();
|
||||
Assert.assertTrue(adapter.isComplete(messages));
|
||||
ReleaseStrategy adapter = new ReleaseStrategyAdapter(simpleReleaseStrategy, "checkCompletenessOnListOfStrings");
|
||||
MessageGroup messages = createListOfMessages(3);
|
||||
Assert.assertTrue(adapter.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAdapterWithWrongMethodName() {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "methodThatDoesNotExist");
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "methodThatDoesNotExist");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testInvalidParameterTypeUsingMethodName() {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "invalidParameterType");
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "invalidParameterType");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testTooManyParametersUsingMethodName() {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "tooManyParameters");
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "tooManyParameters");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNotEnoughParametersUsingMethodName() {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "notEnoughParameters");
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "notEnoughParameters");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testListSubclassParameterUsingMethodName() {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "ListSubclassParameter");
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "ListSubclassParameter");
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWrongReturnType() throws SecurityException, NoSuchMethodError {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, "wrongReturnType");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testInvalidParameterTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new MethodInvokingAggregator(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
|
||||
"invalidParameterType", String.class));
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, "wrongReturnType");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testTooManyParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
|
||||
"tooManyParameters", List.class, List.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNotEnoughParametersUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
|
||||
"notEnoughParameters", new Class[] {}));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testListSubclassParameterUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod(
|
||||
"ListSubclassParameter", new Class[] { LinkedList.class }));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWrongReturnTypeUsingMethodObject() throws SecurityException, NoSuchMethodException {
|
||||
new CompletionStrategyAdapter(simpleCompletionStrategy, simpleCompletionStrategy.getClass().getMethod(
|
||||
"wrongReturnType", new Class[] { List.class }));
|
||||
new ReleaseStrategyAdapter(simpleReleaseStrategy, simpleReleaseStrategy.getClass().getMethod("wrongReturnType",
|
||||
new Class[] { List.class }));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNullObject() {
|
||||
new MethodInvokingAggregator(null, "doesNotMatter");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNullMethodName() {
|
||||
String methodName = null;
|
||||
new MethodInvokingAggregator(simpleCompletionStrategy, methodName);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNullMethodObject() {
|
||||
Method method = null;
|
||||
new MethodInvokingAggregator(simpleCompletionStrategy, method);
|
||||
}
|
||||
|
||||
|
||||
private static List<Message<?>> createListOfMessages() {
|
||||
private static MessageGroup createListOfMessages(int size) {
|
||||
List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
messages.add(new GenericMessage<String>("123"));
|
||||
messages.add(new GenericMessage<String>("456"));
|
||||
messages.add(new GenericMessage<String>("789"));
|
||||
return messages;
|
||||
if (size > 0) {
|
||||
messages.add(new GenericMessage<String>("123"));
|
||||
}
|
||||
if (size > 1) {
|
||||
messages.add(new GenericMessage<String>("456"));
|
||||
}
|
||||
if (size > 2) {
|
||||
messages.add(new GenericMessage<String>("789"));
|
||||
}
|
||||
return new MessageGroup(messages, "ABC");
|
||||
}
|
||||
|
||||
private static class AlwaysTrueCompletionStrategy {
|
||||
@SuppressWarnings("unused")
|
||||
private static class AlwaysTrueReleaseStrategy {
|
||||
public boolean checkCompleteness(List<Message<?>> messages) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AlwaysFalseCompletionStrategy {
|
||||
@SuppressWarnings("unused")
|
||||
private static class AlwaysFalseReleaseStrategy {
|
||||
public boolean checkCompleteness(List<Message<?>> messages) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleCompletionStrategy {
|
||||
@SuppressWarnings("unused")
|
||||
private static class SimpleReleaseStrategy {
|
||||
|
||||
public boolean checkCompletenessOnNonParameterizedListOfMessages(List<Message<?>> messages) {
|
||||
Assert.assertTrue(messages.size() > 0);
|
||||
@@ -207,13 +185,13 @@ public class CompletionStrategyAdapterTests {
|
||||
return messages.size() > messages.iterator().next().getHeaders().getSequenceSize();
|
||||
}
|
||||
|
||||
public boolean checkCompletenessOnListOfMessagesParametrizedWithString(
|
||||
List<Message<String>> messages) {
|
||||
public boolean checkCompletenessOnListOfMessagesParametrizedWithString(List<Message<String>> messages) {
|
||||
Assert.assertTrue(messages.size() > 0);
|
||||
return messages.size() > messages.iterator().next().getHeaders().getSequenceSize();
|
||||
}
|
||||
|
||||
// Example for the case when completeness is checked on the structure of the data
|
||||
// Example for the case when completeness is checked on the structure of
|
||||
// the data
|
||||
public boolean checkCompletenessOnListOfStrings(List<String> messages) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (String content : messages) {
|
||||
@@ -16,22 +16,23 @@
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.integration.store.MessageStore;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
@@ -39,23 +40,20 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
*/
|
||||
public class ResequencerTests {
|
||||
|
||||
private Resequencer resequencer;
|
||||
|
||||
private ThreadPoolTaskScheduler taskScheduler;
|
||||
private CorrelatingMessageHandler resequencer;
|
||||
|
||||
private Resequencer processor = new Resequencer();
|
||||
|
||||
private MessageStore store = new SimpleMessageStore();
|
||||
|
||||
|
||||
@Before
|
||||
public void configureResequencer() {
|
||||
this.resequencer = new Resequencer();
|
||||
this.taskScheduler = TestUtils.createTaskScheduler(10);
|
||||
this.resequencer.setTaskScheduler(taskScheduler);
|
||||
this.taskScheduler.afterPropertiesSet();
|
||||
this.resequencer.start();
|
||||
this.resequencer = new CorrelatingMessageHandler(store, processor, processor, processor);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBasicResequencing() throws InterruptedException {
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
|
||||
@@ -76,7 +74,7 @@ public class ResequencerTests {
|
||||
|
||||
@Test
|
||||
public void testResequencingWithDuplicateMessages() {
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 3, 3, replyChannel);
|
||||
Message<?> message2 = createMessage("456", "ABC", 3, 1, replyChannel);
|
||||
@@ -96,11 +94,9 @@ public class ResequencerTests {
|
||||
assertEquals(new Integer(3), reply3.getHeaders().getSequenceNumber());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testResequencingWithIncompleteSequenceRelease() throws InterruptedException {
|
||||
this.resequencer.setReleasePartialSequences(true);
|
||||
this.processor.setReleasePartialSequences(true);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
|
||||
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
|
||||
@@ -127,7 +123,7 @@ public class ResequencerTests {
|
||||
assertNotNull(reply4);
|
||||
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResequencingWithDiscard() throws InterruptedException {
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
@@ -135,59 +131,60 @@ public class ResequencerTests {
|
||||
Message<?> message2 = createMessage("456", "ABC", 4, 1, null);
|
||||
Message<?> message3 = createMessage("789", "ABC", 4, 4, null);
|
||||
this.resequencer.setSendPartialResultOnTimeout(false);
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
this.resequencer.setDiscardChannel(discardChannel);
|
||||
this.resequencer.setTimeout(90000);
|
||||
this.resequencer.handleMessage(message1);
|
||||
this.resequencer.handleMessage(message2);
|
||||
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
this.resequencer.forceComplete("ABC");
|
||||
Message<?> reply1 = discardChannel.receive(0);
|
||||
Message<?> reply2 = discardChannel.receive(0);
|
||||
Message<?> reply3 = discardChannel.receive(0);
|
||||
// only messages 1 and 2 should have been received by now
|
||||
assertNotNull(reply1);
|
||||
assertEquals(new Integer(1), reply1.getHeaders().getSequenceNumber());
|
||||
assertNotNull(reply2);
|
||||
assertEquals(new Integer(2), reply2.getHeaders().getSequenceNumber());
|
||||
assertNull(reply3);
|
||||
ArrayList<Integer> sequence = new ArrayList<Integer>(Arrays.asList(reply1.getHeaders().getSequenceNumber(), reply2.getHeaders()
|
||||
.getSequenceNumber()));
|
||||
Collections.sort(sequence);
|
||||
assertEquals("[1, 2]", sequence.toString());
|
||||
// when sending the last message, the whole sequence must have been sent
|
||||
this.resequencer.handleMessage(message3);
|
||||
reply3 = discardChannel.receive(0);
|
||||
assertNull(reply3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testResequencingWithDifferentSequenceSizes() throws InterruptedException {
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 4, 2, null);
|
||||
Message<?> message2 = createMessage("456", "ABC", 5, 1, null);
|
||||
this.resequencer.setSendPartialResultOnTimeout(false);
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
this.resequencer.setDiscardChannel(discardChannel);
|
||||
this.resequencer.setTimeout(90000);
|
||||
this.resequencer.handleMessage(message1);
|
||||
this.resequencer.handleMessage(message2);
|
||||
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
Message<?> reply1 = discardChannel.receive(0);
|
||||
Message<?> reply2 = discardChannel.receive(0);
|
||||
// only messages 1 - with sequence number 2 - should have been received by now
|
||||
// the other has been discarded
|
||||
assertNotNull(reply1);
|
||||
assertEquals(new Integer(2), reply1.getHeaders().getSequenceNumber());
|
||||
assertNull(reply2);
|
||||
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
Message<?> discard1 = discardChannel.receive(0);
|
||||
Message<?> discard2 = discardChannel.receive(0);
|
||||
// message2 has been discarded because it came in with the wrong sequence size
|
||||
assertNotNull(discard1);
|
||||
assertEquals(new Integer(1), discard1.getHeaders().getSequenceNumber());
|
||||
assertNull(discard2);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResequencingWithWrongSequenceSizeAndNumber() throws InterruptedException {
|
||||
QueueChannel discardChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 2, 4, null);
|
||||
this.resequencer.setSendPartialResultOnTimeout(false);
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
this.resequencer.setDiscardChannel(discardChannel);
|
||||
this.resequencer.setTimeout(90000);
|
||||
this.resequencer.handleMessage(message1);
|
||||
this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
// this.resequencer.discardBarrier(this.resequencer.barriers.get("ABC"));
|
||||
Message<?> reply1 = discardChannel.receive(0);
|
||||
// No message has been received - the message has been rejected.
|
||||
assertNull(reply1);
|
||||
@@ -195,7 +192,7 @@ public class ResequencerTests {
|
||||
|
||||
@Test
|
||||
public void testResequencingWithCompleteSequenceRelease() throws InterruptedException {
|
||||
this.resequencer.setReleasePartialSequences(false);
|
||||
this.processor.setReleasePartialSequences(false);
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
Message<?> message1 = createMessage("123", "ABC", 4, 2, replyChannel);
|
||||
Message<?> message2 = createMessage("456", "ABC", 4, 1, replyChannel);
|
||||
@@ -226,32 +223,20 @@ public class ResequencerTests {
|
||||
assertNotNull(reply4);
|
||||
assertEquals(new Integer(4), reply4.getHeaders().getSequenceNumber());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemovalOfBarrierWhenLastMessageOfSequenceArrives() {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
String correlationId = "ABC";
|
||||
Message<?> message1 = createMessage("123", correlationId, 1, 1,
|
||||
replyChannel);
|
||||
Message<?> message1 = createMessage("123", correlationId, 1, 1, replyChannel);
|
||||
resequencer.handleMessage(message1);
|
||||
assertThat(resequencer.barriers.containsKey(correlationId), is(false));
|
||||
assertTrue(store.list(correlationId).isEmpty());
|
||||
}
|
||||
|
||||
|
||||
private static Message<?> createMessage(String payload, Object correlationId,
|
||||
int sequenceSize, int sequenceNumber, MessageChannel replyChannel) {
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setCorrelationId(correlationId)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setSequenceNumber(sequenceNumber)
|
||||
.setReplyChannel(replyChannel)
|
||||
.build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopTaskScheduler() {
|
||||
this.resequencer.stop();
|
||||
this.taskScheduler.destroy();
|
||||
private static Message<?> createMessage(String payload, Object correlationId, int sequenceSize, int sequenceNumber,
|
||||
MessageChannel replyChannel) {
|
||||
return MessageBuilder.withPayload(payload).setCorrelationId(correlationId).setSequenceSize(sequenceSize)
|
||||
.setSequenceNumber(sequenceNumber).setReplyChannel(replyChannel).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,28 +19,23 @@ package org.springframework.integration.aggregator;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.aggregator.SequenceSizeCompletionStrategy;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class SequenceSizeCompletionStrategyTests {
|
||||
public class SequenceSizeReleaseStrategyTests {
|
||||
|
||||
@Test
|
||||
public void testIncompleteList() {
|
||||
Message<String> message = MessageBuilder.withPayload("test1")
|
||||
.setSequenceSize(2).build();
|
||||
List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
MessageGroup messages = new MessageGroup("FOO");
|
||||
messages.add(message);
|
||||
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
assertFalse(completionStrategy.isComplete(messages));
|
||||
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
|
||||
assertFalse(ReleaseStrategy.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -49,23 +44,17 @@ public class SequenceSizeCompletionStrategyTests {
|
||||
.setSequenceSize(2).build();
|
||||
Message<String> message2 = MessageBuilder.withPayload("test2")
|
||||
.setSequenceSize(2).build();
|
||||
List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
MessageGroup messages = new MessageGroup("FOO");
|
||||
messages.add(message1);
|
||||
messages.add(message2);
|
||||
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
assertTrue(completionStrategy.isComplete(messages));
|
||||
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
|
||||
assertTrue(ReleaseStrategy.canRelease(messages));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyList() {
|
||||
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
assertFalse(completionStrategy.isComplete(new ArrayList<Message<?>>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullList() {
|
||||
SequenceSizeCompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy();
|
||||
assertFalse(completionStrategy.isComplete(null));
|
||||
SequenceSizeReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
|
||||
assertTrue(ReleaseStrategy.canRelease(new MessageGroup("FOO")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<beans:bean id="pojoCorrelationStrategy"
|
||||
class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$PojoCorrelationStrategy"/>
|
||||
|
||||
<beans:bean id="completionStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountCompletionStrategy">
|
||||
<beans:bean id="releaseStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountReleaseStrategy">
|
||||
<beans:constructor-arg value="3"/>
|
||||
</beans:bean>
|
||||
|
||||
@@ -31,13 +31,13 @@
|
||||
</channel>
|
||||
|
||||
<aggregator ref="aggregator"
|
||||
completion-strategy="completionStrategy"
|
||||
release-strategy="releaseStrategy"
|
||||
correlation-strategy="correlationStrategy"
|
||||
input-channel="inputChannel"
|
||||
output-channel="outputChannel"/>
|
||||
|
||||
<aggregator ref="aggregator"
|
||||
completion-strategy="completionStrategy"
|
||||
release-strategy="releaseStrategy"
|
||||
correlation-strategy="pojoCorrelationStrategy" correlation-strategy-method="correlate"
|
||||
input-channel="pojoInputChannel"
|
||||
output-channel="pojoOutputChannel"/>
|
||||
|
||||
@@ -21,7 +21,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.aggregator.CompletionStrategy;
|
||||
import org.springframework.integration.aggregator.MessageGroup;
|
||||
import org.springframework.integration.aggregator.ReleaseStrategy;
|
||||
import org.springframework.integration.aggregator.CorrelationStrategy;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.channel.PollableChannel;
|
||||
@@ -31,7 +32,6 @@ import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -45,108 +45,125 @@ import static org.junit.matchers.JUnitMatchers.containsString;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class AggregatorWithCorrelationStrategyTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("inputChannel")
|
||||
MessageChannel inputChannel;
|
||||
@Autowired
|
||||
@Qualifier("inputChannel")
|
||||
MessageChannel inputChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("outputChannel")
|
||||
PollableChannel outputChannel;
|
||||
@Autowired
|
||||
@Qualifier("outputChannel")
|
||||
PollableChannel outputChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("pojoInputChannel")
|
||||
MessageChannel pojoInputChannel;
|
||||
@Autowired
|
||||
@Qualifier("pojoInputChannel")
|
||||
MessageChannel pojoInputChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("pojoOutputChannel")
|
||||
PollableChannel pojoOutputChannel;
|
||||
@Autowired
|
||||
@Qualifier("pojoOutputChannel")
|
||||
PollableChannel pojoOutputChannel;
|
||||
|
||||
@Test
|
||||
public void testCorrelationAndCompletion() {
|
||||
inputChannel.send(MessageBuilder.withPayload("A1").setSequenceNumber(0).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B2").setSequenceNumber(0).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C3").setSequenceNumber(0).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("A4").setSequenceNumber(1).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B5").setSequenceNumber(1).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C6").setSequenceNumber(1).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("A7").setSequenceNumber(2).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B8").setSequenceNumber(2).setSequenceSize(3)
|
||||
.build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C9").setSequenceNumber(2).setSequenceSize(3)
|
||||
.build());
|
||||
receiveAndCompare(outputChannel, "A1", "A4", "A7");
|
||||
receiveAndCompare(outputChannel, "B2", "B5", "B8");
|
||||
receiveAndCompare(outputChannel, "C3", "C6", "C9");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrelationAndCompletion() {
|
||||
inputChannel.send(MessageBuilder.withPayload("A1").setSequenceNumber(0).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B2").setSequenceNumber(0).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C3").setSequenceNumber(0).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("A4").setSequenceNumber(1).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B5").setSequenceNumber(1).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C6").setSequenceNumber(1).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("A7").setSequenceNumber(2).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("B8").setSequenceNumber(2).build());
|
||||
inputChannel.send(MessageBuilder.withPayload("C9").setSequenceNumber(2).build());
|
||||
receiveAndCompare(outputChannel, "A1","A4","A7");
|
||||
receiveAndCompare(outputChannel, "B2","B5","B8");
|
||||
receiveAndCompare(outputChannel, "C3","C6","C9");
|
||||
}
|
||||
@Test
|
||||
public void testCorrelationAndCompletionWithPojo() {
|
||||
// the test verifies how a pojo strategy is applied
|
||||
// Strings are correlated by their first letter, integers are correlated
|
||||
// by the last digit
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X1")
|
||||
.setSequenceNumber(0).setSequenceSize(3).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(93).setSequenceNumber(
|
||||
0).setSequenceSize(3).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X4")
|
||||
.setSequenceNumber(1).setSequenceSize(3).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(113)
|
||||
.setSequenceNumber(1).setSequenceSize(3).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X7")
|
||||
.setSequenceNumber(2).setSequenceSize(3).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(213)
|
||||
.setSequenceNumber(2).setSequenceSize(3).build());
|
||||
receiveAndCompare(pojoOutputChannel, "X1", "X4", "X7");
|
||||
receiveAndCompare(pojoOutputChannel, "93", "113", "213");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrelationAndCompletionWithPojo() {
|
||||
// the test verifies how a pojo strategy is applied
|
||||
// Strings are correlated by their first letter, integers are correlated by the last digit
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X1").setSequenceNumber(0).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(93).setSequenceNumber(0).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X4").setSequenceNumber(1).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(113).setSequenceNumber(1).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload("X7").setSequenceNumber(2).build());
|
||||
pojoInputChannel.send(MessageBuilder.withPayload(213).setSequenceNumber(2).build());
|
||||
receiveAndCompare(pojoOutputChannel, "X1","X4","X7");
|
||||
receiveAndCompare(pojoOutputChannel, "93","113","213");
|
||||
}
|
||||
private void receiveAndCompare(PollableChannel outputChannel,
|
||||
String... expectedValues) {
|
||||
Message<?> message = outputChannel.receive(500);
|
||||
Assert.assertNotNull(message);
|
||||
for (String expectedValue : expectedValues) {
|
||||
assertThat((String) message.getPayload(),
|
||||
containsString(expectedValue));
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveAndCompare(PollableChannel outputChannel, String... expectedValues) {
|
||||
Message<?> message = outputChannel.receive(500);
|
||||
Assert.assertNotNull(message);
|
||||
for (String expectedValue : expectedValues) {
|
||||
assertThat((String)message.getPayload(), containsString(expectedValue));
|
||||
}
|
||||
}
|
||||
public static class MessageCountReleaseStrategy implements
|
||||
ReleaseStrategy {
|
||||
|
||||
private final int expectedSize;
|
||||
|
||||
public static class MessageCountCompletionStrategy implements CompletionStrategy {
|
||||
public MessageCountReleaseStrategy(int expectedSize) {
|
||||
this.expectedSize = expectedSize;
|
||||
}
|
||||
|
||||
private final int expectedSize;
|
||||
public boolean canRelease(MessageGroup messages) {
|
||||
return messages.size() == expectedSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public MessageCountCompletionStrategy(int expectedSize) {
|
||||
this.expectedSize = expectedSize;
|
||||
}
|
||||
public static class FirstLetterCorrelationStrategy implements
|
||||
CorrelationStrategy {
|
||||
|
||||
public boolean isComplete(Collection<? extends Message<?>> messages) {
|
||||
return messages.size() == expectedSize;
|
||||
}
|
||||
public Object getCorrelationKey(Message<?> message) {
|
||||
return message.getPayload().toString().subSequence(0, 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class FirstLetterCorrelationStrategy implements CorrelationStrategy {
|
||||
public static class PojoCorrelationStrategy {
|
||||
|
||||
public Object getCorrelationKey(Message<?> message) {
|
||||
return message.getPayload().toString().subSequence(0,1);
|
||||
}
|
||||
public String correlate(String message) {
|
||||
return message.substring(0, 1);
|
||||
}
|
||||
|
||||
}
|
||||
public String correlate(Integer message) {
|
||||
return Integer.toString(message % 10);
|
||||
}
|
||||
|
||||
public static class PojoCorrelationStrategy {
|
||||
}
|
||||
|
||||
public String correlate(String message) {
|
||||
return message.substring(0,1);
|
||||
}
|
||||
public static class SimpleAggregator {
|
||||
|
||||
public String correlate(Integer message) {
|
||||
return Integer.toString(message % 10);
|
||||
}
|
||||
@Aggregator
|
||||
public String concatenate(List<Object> payloads) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (Object payload : payloads) {
|
||||
buffer.append(payload.toString());
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SimpleAggregator {
|
||||
|
||||
@Aggregator
|
||||
public String concatenate(List<Object> payloads) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (Object payload: payloads) {
|
||||
buffer.append(payload.toString());
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<aggregator id="aggregator" ref="adderBean" method="add" completion-strategy="testCompletionStrategy"
|
||||
<aggregator id="aggregator" ref="adderBean" method="add" release-strategy="testReleaseStrategy"
|
||||
input-channel="input-channel" output-channel="replyChannel">
|
||||
</aggregator>
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
|
||||
<beans:bean id="adderBean" class="org.springframework.integration.config.Adder"/>
|
||||
|
||||
<beans:bean id="completionStrategyBean" class="org.springframework.integration.config.TestCompletionStrategy"/>
|
||||
<beans:bean id="ReleaseStrategyBean" class="org.springframework.integration.config.TestReleaseStrategy"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -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());
|
||||
|
||||
@@ -16,17 +16,16 @@
|
||||
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.integration.aggregator.CompletionStrategy;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.aggregator.MessageGroup;
|
||||
import org.springframework.integration.aggregator.ReleaseStrategy;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class TestCompletionStrategy implements CompletionStrategy {
|
||||
public class TestReleaseStrategy implements ReleaseStrategy {
|
||||
|
||||
public boolean isComplete(Collection<? extends Message<?>> messages) {
|
||||
public boolean canRelease(MessageGroup messages) {
|
||||
throw new UnsupportedOperationException("This is not intended to be implemented, but to verify injection into an <aggregator>");
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
output-channel="outputChannel"
|
||||
discard-channel="discardChannel"
|
||||
ref="aggregatorBean"
|
||||
completion-strategy="completionStrategy"
|
||||
release-strategy="releaseStrategy"
|
||||
correlation-strategy="correlationStrategy"
|
||||
send-timeout="86420000"
|
||||
send-partial-result-on-timeout="true"
|
||||
@@ -39,14 +39,14 @@
|
||||
input-channel="aggregatorWithReferenceAndMethodInput"
|
||||
output-channel="outputChannel"/>
|
||||
|
||||
<channel id="aggregatorWithPojoCompletionStrategyInput"/>
|
||||
<aggregator id="aggregatorWithPojoCompletionStrategy"
|
||||
input-channel="aggregatorWithPojoCompletionStrategyInput"
|
||||
<channel id="aggregatorWithPojoReleaseStrategyInput"/>
|
||||
<aggregator id="aggregatorWithPojoReleaseStrategy"
|
||||
input-channel="aggregatorWithPojoReleaseStrategyInput"
|
||||
output-channel="outputChannel"
|
||||
ref="adderBean"
|
||||
method="add"
|
||||
completion-strategy="pojoCompletionStrategy"
|
||||
completion-strategy-method="checkCompleteness"/>
|
||||
release-strategy="pojoReleaseStrategy"
|
||||
release-strategy-method="checkCompleteness"/>
|
||||
|
||||
<beans:bean id="aggregatorBean"
|
||||
class="org.springframework.integration.config.TestAggregatorBean" />
|
||||
@@ -54,13 +54,13 @@
|
||||
<beans:bean id="adderBean"
|
||||
class="org.springframework.integration.config.Adder" />
|
||||
|
||||
<beans:bean id="completionStrategy"
|
||||
class="org.springframework.integration.config.TestCompletionStrategy" />
|
||||
<beans:bean id="releaseStrategy"
|
||||
class="org.springframework.integration.config.TestReleaseStrategy" />
|
||||
|
||||
<beans:bean id="correlationStrategy" class="org.springframework.integration.config.TestCorrelationStrategy"/>
|
||||
|
||||
<beans:bean id="pojoCompletionStrategy"
|
||||
class="org.springframework.integration.config.MaxValueCompletionStrategy">
|
||||
<beans:bean id="pojoReleaseStrategy"
|
||||
class="org.springframework.integration.config.MaxValueReleaseStrategy">
|
||||
<beans:constructor-arg value="10" />
|
||||
</beans:bean>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.CompletionStrategy;
|
||||
import org.springframework.integration.annotation.ReleaseStrategy;
|
||||
import org.springframework.integration.annotation.CorrelationStrategy;
|
||||
|
||||
/**
|
||||
@@ -38,7 +38,7 @@ public class TestAnnotatedEndpointWithCorrelationStrategy {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@CompletionStrategy
|
||||
@ReleaseStrategy
|
||||
public boolean isComplete(List<String> payloads) {
|
||||
return payloads.size() == 3;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.integration.aggregator.MessageSequenceComparator;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.CompletionStrategy;
|
||||
import org.springframework.integration.annotation.ReleaseStrategy;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.StringMessage;
|
||||
@@ -32,8 +32,8 @@ import org.springframework.integration.message.StringMessage;
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@MessageEndpoint("endpointWithDefaultAnnotationAndCustomCompletionStrategy")
|
||||
public class TestAnnotatedEndpointWithCompletionStrategy {
|
||||
@MessageEndpoint("endpointWithDefaultAnnotationAndCustomReleaseStrategy")
|
||||
public class TestAnnotatedEndpointWithReleaseStrategy {
|
||||
|
||||
private final ConcurrentMap<Object, Message<?>> aggregatedMessages = new ConcurrentHashMap<Object, Message<?>>();
|
||||
|
||||
@@ -54,7 +54,7 @@ public class TestAnnotatedEndpointWithCompletionStrategy {
|
||||
return returnedMessage;
|
||||
}
|
||||
|
||||
@CompletionStrategy
|
||||
@ReleaseStrategy
|
||||
public boolean completionChecker(List<Message<?>> messages) {
|
||||
return true;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<beans:bean id="correlationStrategy" class="org.springframework.integration.config.CorrelationStrategyInvalidConfigurationTests$VoidReturningCorrelationStrategy"/>
|
||||
|
||||
<beans:bean id="completionStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountCompletionStrategy">
|
||||
<beans:bean id="releaseStrategy" class="org.springframework.integration.config.AggregatorWithCorrelationStrategyTests$MessageCountReleaseStrategy">
|
||||
<beans:constructor-arg value="3"/>
|
||||
</beans:bean>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
</channel>
|
||||
|
||||
<aggregator ref="aggregator"
|
||||
completion-strategy="completionStrategy"
|
||||
correlation-strategy="correlationStrategy" completion-strategy-method="invalidCorrelationMethod"
|
||||
release-strategy="releaseStrategy" release-strategy-method="invalidCorrelationMethod"
|
||||
correlation-strategy="correlationStrategy"
|
||||
input-channel="inputChannel"
|
||||
output-channel="outputChannel"/>
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<aggregator id="aggregatorWithPojoCompletionStrategy"
|
||||
completion-strategy="completionStrategy"
|
||||
<aggregator id="aggregatorWithPojoReleaseStrategy"
|
||||
ref="adderBean" method="add"
|
||||
input-channel="inputChannel"
|
||||
output-channel="replyChannel"
|
||||
completion-strategy-method="invalidMethodName"/>
|
||||
release-strategy="releaseStrategy"
|
||||
release-strategy-method="invalidMethodName"/>
|
||||
|
||||
<channel id="inputChannel"/>
|
||||
<channel id="replyChannel"/>
|
||||
@@ -20,11 +20,11 @@
|
||||
<beans:bean id="adderBean"
|
||||
class="org.springframework.integration.config.Adder" />
|
||||
|
||||
<beans:bean id="completionStrategy"
|
||||
class="org.springframework.integration.config.TestCompletionStrategy" />
|
||||
<beans:bean id="releaseStrategy"
|
||||
class="org.springframework.integration.config.TestReleaseStrategy" />
|
||||
|
||||
<beans:bean id="pojoCompletionStrategy"
|
||||
class="org.springframework.integration.config.MaxValueCompletionStrategy">
|
||||
<beans:bean id="pojoReleaseStrategy"
|
||||
class="org.springframework.integration.config.MaxValueReleaseStrategy">
|
||||
<beans:constructor-arg value="10" />
|
||||
</beans:bean>
|
||||
|
||||
@@ -20,8 +20,6 @@ import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessagingException;
|
||||
@@ -68,13 +66,4 @@ public class SimpleMessageStoreTests {
|
||||
assertEquals(1, store.list("bar").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldListByCorrelationAfterAddAll() throws Exception {
|
||||
SimpleMessageStore store = new SimpleMessageStore();
|
||||
Message<String> testMessage1 = MessageBuilder.withPayload("foo").build();
|
||||
Message<String> testMessage2 = MessageBuilder.withPayload("bar").build();
|
||||
store.put("bar", Arrays.<Message<?>>asList(testMessage1, testMessage2));
|
||||
assertEquals(2, store.list("bar").size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user