Merging INT-2152 into master

This commit is contained in:
Mark Fisher
2011-10-07 16:00:11 -04:00
73 changed files with 2105 additions and 1483 deletions

2
.gitignore vendored
View File

@@ -22,4 +22,4 @@ si.java.hsp
spring-integration-jms/activemq-data/
spring-integration-samples/loanshark/application.log*
target
vf.gf.dmn-*.cfg
vf.gf.dmn-*

View File

@@ -126,7 +126,7 @@ configure(javaprojects) {
springAmqpVersion = '1.0.0.RELEASE'
springDataMongoVersion = '1.0.0.M4'
springDataRedisVersion = '1.0.0.M4'
springGemfireVersion = '1.1.0.M2'
springGemfireVersion = '1.1.0.M3'
springSecurityVersion = '3.0.6.RELEASE'
springWsVersion = '2.0.2.RELEASE'

View File

@@ -41,6 +41,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
public final Object processMessageGroup(MessageGroup group) {
Assert.notNull(group, "MessageGroup must not be null");
Map<String, Object> headers = this.aggregateHeaders(group);
Object payload = this.aggregatePayloads(group, headers);
MessageBuilder<?> builder;
@@ -50,6 +51,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
else {
builder = MessageBuilder.withPayload(payload).copyHeadersIfAbsent(headers);
}
return builder.popSequenceDetails().build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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
@@ -13,7 +13,10 @@
package org.springframework.integration.aggregator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -34,34 +37,36 @@ import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Message handler that holds a buffer of correlated messages in a
* Abstract Message handler that holds a buffer of correlated messages in a
* {@link 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.
* that can be completed in batches. It is useful for custom implementation of MessageHandlers that require correlation
* and is used as a base class for Aggregator - {@link AggregatingMessageHandler} and
* Resequencer - {@link ResequencingMessageHandler},
* or custom implementations requiring correlation.
* <p/>
* 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 ReleaseStrategy will be a
* SequenceSizeReleaseStrategy.
* By default the {@link CorrelationStrategy} will be a
* {@link HeaderAttributeCorrelationStrategy} and the {@link ReleaseStrategy} will be a
* {@link SequenceSizeReleaseStrategy}.
*
* @author Iwein Fuld
* @author Dave Syer
* @author Oleg Zhurakousky
* @since 2.0
*/
public class CorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer {
public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageHandler implements MessageProducer {
private static final Log logger = LogFactory.getLog(CorrelatingMessageHandler.class);
private static final Log logger = LogFactory.getLog(AbstractCorrelatingMessageHandler.class);
public static final long DEFAULT_SEND_TIMEOUT = 1000L;
private MessageGroupStore messageStore;
protected volatile MessageGroupStore messageStore;
private final MessageGroupProcessor outputProcessor;
@@ -80,9 +85,15 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
private final Object correlationLocksMonitor = new Object();
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
protected volatile boolean keepReleasedMessages = true;
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
public void setKeepReleasedMessages(boolean keepReleasedMessages) {
this.keepReleasedMessages = keepReleasedMessages;
}
public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
Assert.notNull(processor);
Assert.notNull(store);
@@ -94,11 +105,11 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
this.messagingTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) {
public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) {
this(processor, store, null, null);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor) {
public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor) {
this(processor, new SimpleMessageStore(0), null, null);
}
@@ -159,72 +170,59 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
public String getComponentType() {
return "aggregator";
}
protected MessageGroupStore getMessageStore() {
return messageStore;
}
@SuppressWarnings("rawtypes")
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Object correlationKey = correlationStrategy.getCorrelationKey(message);
Assert.state(correlationKey!=null, "Null correlation not allowed. Maybe the CorrelationStrategy is failing?");
if (logger.isDebugEnabled()) {
logger.debug("Handling message with correlationKey ["
+ correlationKey + "]: " + message);
logger.debug("Handling message with correlationKey [" + correlationKey + "]: " + message);
}
// TODO: INT-1117 - make the lock global?
Object lock = getLock(correlationKey);
synchronized (lock) {
MessageGroup group = messageStore.getMessageGroup(correlationKey);
if (group.canAdd(message)) {
MessageGroup messageGroup = messageStore.getMessageGroup(correlationKey);
if (!messageGroup.isComplete() && messageGroup.canAdd(message)) {
if (logger.isTraceEnabled()) {
logger.trace("Adding message to group [ " + group + "]");
logger.trace("Adding message to group [ " + messageGroup + "]");
}
group = store(correlationKey, message);
if (releaseStrategy.canRelease(group)) {
Collection<Message> completedMessages = null;
messageGroup = store(correlationKey, message);
if (releaseStrategy.canRelease(messageGroup)) {
Collection<Message<?>> completedMessages = null;
try {
completedMessages = completeGroup(message, correlationKey, group);
completedMessages = completeGroup(message, correlationKey, messageGroup);
}
finally {
// Always clean up even if there was an exception
// processing messages
cleanUpForReleasedGroup(group, completedMessages);
}
} else if (group.isComplete()) {
try {
// If not releasing any messages the group might still
// be complete
for (Message<?> discard : group.getUnmarked()) {
discardChannel.send(discard);
// processing messages
this.afterRelease(messageGroup, completedMessages);
synchronized(correlationLocksMonitor){
locks.remove(messageGroup.getGroupId());
}
}
finally {
remove(group);
}
}
} else {
}
}
else {
discardChannel.send(message);
}
}
}
@SuppressWarnings("rawtypes")
private void cleanUpForReleasedGroup(MessageGroup group, Collection<Message> completedMessages) {
if (group.isComplete() || group.getSequenceSize() == 0) {
// The group is complete or else there is no
// sequence so there is no more state to track
remove(group);
} else {
// Mark these messages as processed, but do not
// remove the group from store
if (completedMessages == null) {
mark(group);
} else {
mark(group, completedMessages);
}
}
}
/**
* Allows you to provide additional logic that needs to be performed after the MessageGroup was released.
* @param group
* @param completedMessages
*/
protected abstract void afterRelease(MessageGroup group, Collection<Message<?>> completedMessages);
private final boolean forceComplete(MessageGroup group) {
@@ -235,13 +233,14 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
if (group.size() > 0) {
try {
if (releaseStrategy.canRelease(group)) {
completeGroup(correlationKey, group);
} else {
expireGroup(group, correlationKey);
this.completeGroup(correlationKey, group);
}
else {
this.expireGroup(correlationKey, group);
}
}
finally {
remove(group);
this.remove(group);
}
return true;
}
@@ -256,31 +255,25 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
}
}
private void mark(MessageGroup group) {
messageStore.markMessageGroup(group);
}
@SuppressWarnings("rawtypes")
private void mark(MessageGroup group, Collection<Message> partialSequence) {
Object id = group.getGroupId();
for (Message message : partialSequence) {
messageStore.markMessageFromGroup(id, message);
}
}
private void remove(MessageGroup group) {
void remove(MessageGroup group) {
Object correlationKey = group.getGroupId();
messageStore.removeMessageGroup(correlationKey);
synchronized(correlationLocksMonitor){
locks.remove(correlationKey);
}
}
protected int findLastReleasedSequenceNumber(Object groupId, Collection<Message<?>> partialSequence){
List<Message<?>> sorted = new ArrayList<Message<?>>((Collection<? extends Message<?>>)partialSequence);
Collections.sort(sorted, new SequenceNumberComparator());
Message<?> lastReleasedMessage = sorted.get(partialSequence.size()-1);
return lastReleasedMessage.getHeaders().getSequenceNumber();
}
private MessageGroup store(Object correlationKey, Message<?> message) {
return messageStore.addMessageToGroup(correlationKey, message);
}
private void expireGroup(MessageGroup group, Object correlationKey) {
private void expireGroup(Object correlationKey, MessageGroup group) {
if (logger.isInfoEnabled()) {
logger.info("Expiring MessageGroup with correlationKey[" + correlationKey + "]");
}
@@ -309,21 +302,25 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
completeGroup(first, correlationKey, group);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Collection<Message> completeGroup(Message<?> message, Object correlationKey, MessageGroup group) {
@SuppressWarnings("unchecked")
private Collection<Message<?>> completeGroup(Message<?> message, Object correlationKey, MessageGroup group) {
if (logger.isDebugEnabled()) {
logger.debug("Completing group with correlationKey ["
+ correlationKey + "]");
logger.debug("Completing group with correlationKey [" + correlationKey + "]");
}
Object result = outputProcessor.processMessageGroup(group);
Collection<Message> partialSequence = null;
Collection<Message<?>> partialSequence = null;
if (result instanceof Collection<?>) {
//Taking a risk here because of Type Erasure. This is covered in the processor contract
partialSequence = (Collection<Message>) result;
this.verifyResultCollectionConsistsOfMessages((Collection<?>) result);
partialSequence = (Collection<Message<?>>) result;
}
this.sendReplies(result, message);
return partialSequence;
}
private void verifyResultCollectionConsistsOfMessages(Collection<?> elements){
Class<?> commonElementType = CollectionUtils.findCommonElementType(elements);
Assert.isAssignable(Message.class, commonElementType, "The expected collection of Messages contains non-Message element: " + commonElementType);
}
@SuppressWarnings("rawtypes")
private void sendReplies(Object processorResult, Message message) {

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2011 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.Iterator;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
/**
* Aggregator specific implementation of {@link AbstractCorrelatingMessageHandler}.
* Will remove {@link MessageGroup}s only if 'expireGroupsUponCompletion' flag is set to 'true'.
*
* @author Oleg Zhurakousky
* @since 2.1
*/
public class AggregatingMessageHandler extends AbstractCorrelatingMessageHandler {
private volatile boolean expireGroupsUponCompletion = false;
public AggregatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
super(processor, store, correlationStrategy, releaseStrategy);
}
public AggregatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) {
super(processor, store);
}
public AggregatingMessageHandler(MessageGroupProcessor processor) {
super(processor);
}
/**
* Will set the 'expireGroupsUponCompletion' flag and if it is
* set to 'true' it will also remove all 'complete' {@link MessageGroup}s
* @param expireGroupsUponCompletion
*/
public void setExpireGroupsUponCompletion(boolean expireGroupsUponCompletion) {
this.expireGroupsUponCompletion = expireGroupsUponCompletion;
if (expireGroupsUponCompletion) {
Iterator<MessageGroup> messageGroups = this.messageStore.iterator();
while (messageGroups.hasNext()) {
MessageGroup messageGroup = messageGroups.next();
if (messageGroup.isComplete()) {
remove(messageGroup);
}
}
}
}
@Override
protected void afterRelease(MessageGroup messageGroup, Collection<Message<?>> completedMessages) {
this.messageStore.completeGroup(messageGroup.getGroupId());
if (this.expireGroupsUponCompletion) {
remove(messageGroup);
}
else {
if (this.keepReleasedMessages){
messageStore.markMessageGroup(messageGroup);
}
else {
for (Message<?> message : messageGroup.getMarked()) {
this.messageStore.removeMessageFromGroup(messageGroup.getGroupId(), message);
}
for (Message<?> message : messageGroup.getUnmarked()) {
this.messageStore.removeMessageFromGroup(messageGroup.getGroupId(), message);
}
}
}
}
}

View File

@@ -23,6 +23,7 @@ import java.util.*;
*
* @author Iwein Fuld
* @author Dave Syer
* @author Oleg Zhurakousky
* @since 2.0
*/
public class ResequencingMessageGroupProcessor implements MessageGroupProcessor {
@@ -37,15 +38,14 @@ public class ResequencingMessageGroupProcessor implements MessageGroupProcessor
public void setComparator(Comparator<Message<?>> comparator) {
this.comparator = comparator;
}
@SuppressWarnings("rawtypes")
public Object processMessageGroup(MessageGroup group) {
Collection<Message<?>> messages = group.getUnmarked();
if (messages.size() > 0) {
List<Message<?>> sorted = new ArrayList<Message<?>>(messages);
Collections.sort(sorted, this.comparator);
ArrayList<Message> partialSequence = new ArrayList<Message>();
ArrayList<Message<?>> partialSequence = new ArrayList<Message<?>>();
int previousSequence = extractSequenceNumber(sorted.get(0));
int currentSequence = previousSequence;
for (Message<?> message : sorted) {
@@ -57,6 +57,7 @@ public class ResequencingMessageGroupProcessor implements MessageGroupProcessor
}
partialSequence.add(message);
}
return partialSequence;
}
return null;

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2011 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.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
/**
* Resequencer specific implementation of {@link AbstractCorrelatingMessageHandler}.
* Will remove {@link MessageGroup}s only if 'sequenceSize' is provided and reached.
*
* @author Oleg Zhurakousky
* @since 2.1
*/
public class ResequencingMessageHandler extends AbstractCorrelatingMessageHandler {
public ResequencingMessageHandler(MessageGroupProcessor processor,
MessageGroupStore store, CorrelationStrategy correlationStrategy,
ReleaseStrategy releaseStrategy) {
super(processor, store, correlationStrategy, releaseStrategy);
}
public ResequencingMessageHandler(MessageGroupProcessor processor,
MessageGroupStore store) {
super(processor, store);
}
public ResequencingMessageHandler(MessageGroupProcessor processor) {
super(processor);
}
@Override
protected void afterRelease(MessageGroup messageGroup, Collection<Message<?>> completedMessages) {
int size = messageGroup.getUnmarked().size() + messageGroup.getMarked().size();
int sequenceSize = 0;
Message<?> message = messageGroup.getOne();
if (message != null){
sequenceSize = message.getHeaders().getSequenceSize();
}
// If there is no sequence then it must be incomplete or unbounded
if (sequenceSize > 0 && sequenceSize == size){
remove(messageGroup);
}
else {
if (completedMessages != null){
int lastReleasedSequenceNumber = this.findLastReleasedSequenceNumber(messageGroup.getGroupId(), completedMessages);
messageStore.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), lastReleasedSequenceNumber);
if (this.keepReleasedMessages){
Object id = messageGroup.getGroupId();
for (Message<?> msg : completedMessages) {
messageStore.markMessageFromGroup(id, msg);
}
}
else {
for (Message<?> msg : completedMessages) {
this.messageStore.removeMessageFromGroup(messageGroup.getGroupId(), msg);
}
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2011 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.
@@ -16,17 +16,17 @@
package org.springframework.integration.aggregator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
/**
* An implementation of {@link ReleaseStrategy} that simply compares the current size of the message list to the
* expected 'sequenceSize'.
@@ -35,6 +35,7 @@ import java.util.List;
* @author Marius Bogoevici
* @author Dave Syer
* @author Iwein Fuld
* @author Oleg Zhurakousky
*/
public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
@@ -62,24 +63,42 @@ public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
this.releasePartialSequences = releasePartialSequences;
}
public boolean canRelease(MessageGroup messages) {
if (releasePartialSequences) {
Collection<Message<?>> unmarked = messages.getUnmarked();
if (!unmarked.isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace("Considering partial release of group [" + messages + "]");
}
List<Message<?>> sorted = new ArrayList<Message<?>>(unmarked);
Collections.sort(sorted, comparator);
int tail = sorted.get(0).getHeaders().getSequenceNumber() - 1;
boolean release = tail == messages.getMarked().size();
if (logger.isTraceEnabled() && release) {
logger.trace("Release imminent because tail [" + tail + "] is next in line.");
}
return release;
public boolean canRelease(MessageGroup messageGroup) {
boolean canRelease = false;
Collection<Message<?>> unmarked = messageGroup.getUnmarked();
if (releasePartialSequences && !unmarked.isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace("Considering partial release of group [" + messageGroup + "]");
}
List<Message<?>> sorted = new ArrayList<Message<?>>(unmarked);
Collections.sort(sorted, comparator);
int nextSequenceNumber = sorted.get(0).getHeaders().getSequenceNumber();
int lastReleasedMessageSequence = messageGroup.getLastReleasedMessageSequenceNumber();
if (nextSequenceNumber - lastReleasedMessageSequence == 1){
canRelease = true;;
}
}
return messages.isComplete();
else {
int size = messageGroup.getUnmarked().size();
if (size == 0){
canRelease = true;
}
else {
int sequenceSize = messageGroup.getOne().getHeaders().getSequenceSize();
// If there is no sequence then it must be incomplete....
if (sequenceSize == size){
canRelease = true;
}
}
}
return canRelease;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -22,7 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler;
/**
* Indicates that a method is capable of aggregating messages.
@@ -32,6 +32,7 @@ import org.springframework.integration.aggregator.CorrelatingMessageHandler;
* Message or a single Object to be used as a Message payload.
*
* @author Marius Bogoevici
* @author Oleg Zhurakousky
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -56,7 +57,7 @@ public @interface Aggregator {
/**
* timeout for sending results to the reply target (in milliseconds)
*/
long sendTimeout() default CorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT;
long sendTimeout() default AbstractCorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT;
/**
* indicates whether to send an incomplete aggregate on expiry of the message group

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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.
@@ -23,7 +23,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy;
@@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
* Post-processor for the {@link Aggregator @Aggregator} annotation.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Aggregator> {
@@ -53,7 +54,7 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method);
MethodInvokingReleaseStrategy releaseStrategy = getReleaseStrategy(bean);
MethodInvokingCorrelationStrategy correlationStrategy = getCorrelationStrategy(bean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(processor, new SimpleMessageStore(), correlationStrategy, releaseStrategy);
AggregatingMessageHandler handler = new AggregatingMessageHandler(processor, new SimpleMessageStore(), correlationStrategy, releaseStrategy);
String discardChannelName = annotation.discardChannel();
if (StringUtils.hasText(discardChannelName)) {
MessageChannel discardChannel = this.channelResolver.resolveChannelName(discardChannelName);

View File

@@ -21,6 +21,8 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@@ -60,6 +62,10 @@ public class AggregatorParser extends AbstractConsumerEndpointParser {
private static final String RELEASE_STRATEGY_PROPERTY = "releaseStrategy";
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
private static final String EXPIRE_GROUPS_UPON_COMPLETION = "expire-groups-upon-completion";
private static final String KEEP_RELEASED_MESSAGES = "keep-released-messages";
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
@@ -68,14 +74,12 @@ public class AggregatorParser extends AbstractConsumerEndpointParser {
String ref = element.getAttribute(REF_ATTRIBUTE);
BeanDefinitionBuilder builder;
builder = BeanDefinitionBuilder.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE
+ ".aggregator.CorrelatingMessageHandler");
builder = BeanDefinitionBuilder.genericBeanDefinition(AggregatingMessageHandler.class);
BeanDefinitionBuilder processorBuilder = null;
BeanMetadataElement processor = null;
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE
+ ".aggregator.MethodInvokingMessageGroupProcessor");
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageGroupProcessor.class);
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
if (innerHandlerDefinition != null) {
processor = innerHandlerDefinition;
@@ -110,8 +114,10 @@ public class AggregatorParser extends AbstractConsumerEndpointParser {
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, EXPIRE_GROUPS_UPON_COMPLETION);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, KEEP_RELEASED_MESSAGES);
this.injectPropertyWithAdapter(RELEASE_STRATEGY_REF_ATTRIBUTE, RELEASE_STRATEGY_METHOD_ATTRIBUTE,
RELEASE_STRATEGY_EXPRESSION_ATTRIBUTE, RELEASE_STRATEGY_PROPERTY, "ReleaseStrategy", element, builder,
processor, parserContext);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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
@@ -18,6 +18,8 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor;
import org.springframework.integration.aggregator.ResequencingMessageHandler;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@@ -27,6 +29,7 @@ import org.w3c.dom.Element;
* @author Marius Bogoevici
* @author Dave Syer
* @author Iwein Fuld
* @author Oleg Zhurakousky
*/
public class ResequencerParser extends AbstractConsumerEndpointParser {
@@ -53,15 +56,14 @@ public class ResequencerParser extends AbstractConsumerEndpointParser {
private static final String RELEASE_STRATEGY_EXPRESSION_ATTRIBUTE = "release-strategy-expression";
private static final String RELEASE_PARTIAL_SEQUENCES_ATTRIBUTE = "release-partial-sequences";
private static final String KEEP_RELEASED_MESSAGES = "keep-released-messages";
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
BeanDefinitionBuilder processorBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE
+ ".aggregator.ResequencingMessageGroupProcessor");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ResequencingMessageHandler.class);
BeanDefinitionBuilder processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(ResequencingMessageGroupProcessor.class);
// Comparator
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(processorBuilder, element, COMPARATOR_REF_ATTRIBUTE);
@@ -86,6 +88,7 @@ public class ResequencerParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, RELEASE_PARTIAL_SEQUENCES_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, KEEP_RELEASED_MESSAGES);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder;
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2002-2011 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.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.UUID;
import org.springframework.integration.Message;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.util.Assert;
/**
* Base class for implementations of Key/Value style {@link MessageGroupStore} and {@link MessageStore}
*
* @author Oleg Zhurakousky
* @since 2.1
*/
public abstract class AbstractKeyValueMessageStore extends AbstractMessageGroupStore implements MessageStore{
protected static final String MESSAGE_KEY_PREFIX = "MESSAGE_";
protected static final String MESSAGE_GROUP_KEY_PREFIX = "MESSAGE_GROUP_";
// MessageStore methods
public Message<?> getMessage(UUID id) {
Assert.notNull(id, "'id' must not be null");
Object message = this.doRetrieve(MESSAGE_KEY_PREFIX + id);
if (message != null) {
Assert.isInstanceOf(Message.class, message);
}
return (Message<?>) message;
}
@SuppressWarnings("unchecked")
public <T> Message<T> addMessage(Message<T> message) {
Assert.notNull(message, "'message' must not be null");
UUID messageId = message.getHeaders().getId();
this.doStore(MESSAGE_KEY_PREFIX + messageId, message);
return (Message<T>) this.getMessage(messageId);
}
public Message<?> removeMessage(UUID id) {
Assert.notNull(id, "'id' must not be null");
Object message = this.doRemove(MESSAGE_KEY_PREFIX + id);
if (message != null) {
Assert.isInstanceOf(Message.class, message);
}
return (Message<?>) message;
}
@ManagedAttribute
public long getMessageCount() {
Collection<?> messageIds = this.doListKeys(MESSAGE_KEY_PREFIX + "*");
return (messageIds != null) ? messageIds.size() : 0;
}
// MessageGroupStore methods
/**
* Will create a new instance of SimpleMessageGroup if necessary.
*/
public MessageGroup getMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
Object mgm = this.doRetrieve(MESSAGE_GROUP_KEY_PREFIX + groupId);
if (mgm != null) {
Assert.isInstanceOf(MessageGroupMetadata.class, mgm);
MessageGroupMetadata messageGroupMetadata = (MessageGroupMetadata) mgm;
ArrayList<Message<?>> markedMessages = new ArrayList<Message<?>>();
for (UUID uuid : messageGroupMetadata.getMarkedMessageIds()) {
markedMessages.add(this.getMessage(uuid));
}
ArrayList<Message<?>> unmarkedMessages = new ArrayList<Message<?>>();
for (UUID uuid : messageGroupMetadata.getUnmarkedMessageIds()) {
unmarkedMessages.add(this.getMessage(uuid));
}
SimpleMessageGroup messageGroup = new SimpleMessageGroup(unmarkedMessages, markedMessages,
groupId, messageGroupMetadata.getTimestamp(), messageGroupMetadata.isComplete());
if (messageGroupMetadata.getLastReleasedMessageSequenceNumber() > 0) {
messageGroup.setLastReleasedMessageSequenceNumber(messageGroupMetadata.getLastReleasedMessageSequenceNumber());
}
return messageGroup;
}
else {
return new SimpleMessageGroup(groupId);
}
}
/**
* Add a Message to the group with the provided group ID.
*/
public MessageGroup addMessageToGroup(Object groupId, Message<?> message) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(message, "'message' must not be null");
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(this.getMessageGroup(groupId));
messageGroup.add(message);
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
this.addMessage(message);
return messageGroup;
}
/**
* Mark all messages in the provided group.
*/
public MessageGroup markMessageGroup(MessageGroup group) {
Assert.notNull(group, "'group' must not be null");
Object groupId = group.getGroupId();
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(group);
messageGroup.markAll();
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
return messageGroup;
}
/**
* Remove a Message from the group with the provided group ID.
*/
public MessageGroup removeMessageFromGroup(Object groupId, Message<?> messageToRemove) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(messageToRemove, "'messageToRemove' must not be null");
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(this.getMessageGroup(groupId));
messageGroup.remove(messageToRemove);
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
return messageGroup;
}
/**
* Mark the given Message within the group corresponding to the provided group ID.
*/
public MessageGroup markMessageFromGroup(Object groupId, Message<?> messageToMark) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(messageToMark, "'messageToMark' must not be null");
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(this.getMessageGroup(groupId));
messageGroup.mark(messageToMark);
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
return messageGroup;
}
public void completeGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(this.getMessageGroup(groupId));
messageGroup.complete();
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
}
/**
* Remove the MessageGroup with the provided group ID.
*/
public void removeMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
Object mgm = this.doRemove(MESSAGE_GROUP_KEY_PREFIX + groupId);
if (mgm != null) {
Assert.isInstanceOf(MessageGroupMetadata.class, mgm);
MessageGroupMetadata messageGroupMetadata = (MessageGroupMetadata) mgm;
for (UUID messageId : messageGroupMetadata.getMarkedMessageIds()) {
this.removeMessage(messageId);
}
for (UUID messageId : messageGroupMetadata.getUnmarkedMessageIds()) {
this.removeMessage(messageId);
}
}
}
public void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) {
Assert.notNull(groupId, "'groupId' must not be null");
SimpleMessageGroup messageGroup = this.getSimpleMessageGroup(this.getMessageGroup(groupId));
messageGroup.setLastReleasedMessageSequenceNumber(sequenceNumber);
this.doStore(MESSAGE_GROUP_KEY_PREFIX + groupId, new MessageGroupMetadata(messageGroup));
}
public Iterator<MessageGroup> iterator() {
final Iterator<?> idIterator = this.doListKeys(MESSAGE_GROUP_KEY_PREFIX + "*").iterator();
return new MessageGroupIterator(idIterator);
}
private SimpleMessageGroup getSimpleMessageGroup(MessageGroup messageGroup){
if (messageGroup instanceof SimpleMessageGroup){
return (SimpleMessageGroup) messageGroup;
}
else {
return new SimpleMessageGroup(messageGroup);
}
}
protected abstract Object doRetrieve(Object id);
protected abstract void doStore(Object id, Object objectToStore);
protected abstract Object doRemove(Object id);
protected abstract Collection<?> doListKeys(String keyPattern);
private class MessageGroupIterator implements Iterator<MessageGroup> {
private final Iterator<?> idIterator;
private MessageGroupIterator(Iterator<?> idIterator) {
this.idIterator = idIterator;
}
public boolean hasNext() {
return idIterator.hasNext();
}
public MessageGroup next() {
Object messageGroupId = idIterator.next();
return getMessageGroup(messageGroupId);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -14,7 +14,6 @@
package org.springframework.integration.store;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import org.apache.commons.logging.Log;
@@ -67,8 +66,6 @@ public abstract class AbstractMessageGroupStore implements MessageGroupStore, It
}
return count;
}
public abstract Iterator<MessageGroup> iterator();
@ManagedAttribute
public int getMessageCountForAllMessageGroups() {

View File

@@ -16,10 +16,10 @@
package org.springframework.integration.store;
import org.springframework.integration.Message;
import java.util.Collection;
import org.springframework.integration.Message;
/**
* A group of messages that are correlated with each other and should be processed in the same context. The group is
* divided into marked and unmarked messages. The marked messages are typically already processed, the unmarked messages
@@ -49,11 +49,21 @@ public interface MessageGroup {
* @return the key that links these messages together
*/
Object getGroupId();
/**
* Returns the sequenceNumber of the last released message. Used in Resequencer use cases only
*/
int getLastReleasedMessageSequenceNumber();
/**
* @return true if the group is complete (i.e. no more messages are expected to be added)
*/
boolean isComplete();
/**
*
*/
void complete();
/**
* @return the size of the sequence expected 0 if unknown

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2011 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.store;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.springframework.integration.Message;
import org.springframework.util.Assert;
/**
* Immutable Value Object holding metadata about a MessageGroup.
*
* @author Oleg Zhurakousky
* @since 2.1
*/
public class MessageGroupMetadata implements Serializable{
private static final long serialVersionUID = 1L;
private final Object groupId;
private final List<UUID> markedMessageIds;
private final List<UUID> unmarkedMessageIds;
private final boolean complete;
private final long timestamp;
private final int lastReleasedMessageSequenceNumber;
public MessageGroupMetadata(MessageGroup messageGroup) {
Assert.notNull(messageGroup, "'messageGroup' must not be null");
this.groupId = messageGroup.getGroupId();
this.markedMessageIds = new ArrayList<UUID>();
for (Message<?> message : messageGroup.getMarked()) {
this.markedMessageIds.add(message.getHeaders().getId());
}
this.unmarkedMessageIds = new ArrayList<UUID>();
for (Message<?> message : messageGroup.getUnmarked()) {
this.unmarkedMessageIds.add(message.getHeaders().getId());
}
this.complete = messageGroup.isComplete();
this.timestamp = messageGroup.getTimestamp();
this.lastReleasedMessageSequenceNumber = messageGroup.getLastReleasedMessageSequenceNumber();
}
public Object getGroupId() {
return this.groupId;
}
public List<UUID> getMarkedMessageIds() {
return Collections.unmodifiableList(markedMessageIds);
}
public List<UUID> getUnmarkedMessageIds() {
return Collections.unmodifiableList(this.unmarkedMessageIds);
}
public boolean isComplete() {
return this.complete;
}
public long getTimestamp() {
return this.timestamp;
}
public int getLastReleasedMessageSequenceNumber() {
return this.lastReleasedMessageSequenceNumber;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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
@@ -12,6 +12,8 @@
*/
package org.springframework.integration.store;
import java.util.Iterator;
import org.springframework.integration.Message;
import org.springframework.jmx.export.annotation.ManagedAttribute;
@@ -19,6 +21,7 @@ import org.springframework.jmx.export.annotation.ManagedAttribute;
* Interface for storage operations on groups of messages linked by a group id.
*
* @author Dave Syer
* @author Oleg Zhurakousky
*
* @since 2.0
*
@@ -121,4 +124,22 @@ public interface MessageGroupStore {
* @see #registerMessageGroupExpiryCallback(MessageGroupCallback)
*/
int expireMessageGroups(long timeout);
/**
* Allows you to set the sequence number of the last released Message. Used for Resequencing use cases
* @param sequenceNumber
*/
void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber);
/**
* Returns the iterator of currently accumulated {@link MessageGroup}s
*/
Iterator<MessageGroup> iterator();
/**
* Completes this MessageGroup. Completion of the MessageGroup generally means
* that this group should not be allowing any more mutating operation to be performed on it.
* For example any attempt to add/remove new Message form the group should not be allowed.
*/
void completeGroup(Object groupId);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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
@@ -13,13 +13,13 @@
package org.springframework.integration.store;
import org.springframework.integration.Message;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.integration.Message;
/**
* Represents a mutable group of correlated messages that is bound to a certain {@link MessageStore} and group id. The
* group will grow during its lifetime, when messages are <code>add</code>ed to it. This MessageGroup is thread safe.
@@ -41,22 +41,27 @@ public class SimpleMessageGroup implements MessageGroup {
// @GuardedBy(lock)
public final BlockingQueue<Message<?>> unmarked = new LinkedBlockingQueue<Message<?>>();
private volatile int lastReleasedMessageSequence;
private final long timestamp;
private volatile boolean complete;
public SimpleMessageGroup(Object groupId) {
this(Collections.<Message<?>> emptyList(), Collections.<Message<?>> emptyList(), groupId, System
.currentTimeMillis());
.currentTimeMillis(), false);
}
public SimpleMessageGroup(Collection<? extends Message<?>> unmarked, Object groupId) {
this(unmarked, Collections.<Message<?>> emptyList(), groupId, System.currentTimeMillis());
this(unmarked, Collections.<Message<?>> emptyList(), groupId, System.currentTimeMillis(), false);
}
public SimpleMessageGroup(Collection<? extends Message<?>> unmarked, Collection<? extends Message<?>> marked,
Object groupId, long timestamp) {
Object groupId, long timestamp, boolean complete) {
this.groupId = groupId;
this.timestamp = timestamp;
this.complete = complete;
synchronized (lock) {
for (Message<?> message : unmarked) {
addUnmarked(message);
@@ -69,6 +74,7 @@ public class SimpleMessageGroup implements MessageGroup {
public SimpleMessageGroup(MessageGroup template) {
this.groupId = template.getGroupId();
this.complete = template.isComplete();
synchronized (lock) {
// Explicit iteration to work around bug in JDK (before 1.6.0_20
for (Message<?> message : template.getMarked()) {
@@ -84,6 +90,8 @@ public class SimpleMessageGroup implements MessageGroup {
}
this.timestamp = template.getTimestamp();
}
public long getTimestamp() {
return timestamp;
@@ -103,6 +111,10 @@ public class SimpleMessageGroup implements MessageGroup {
unmarked.remove(message);
}
}
public int getLastReleasedMessageSequenceNumber() {
return lastReleasedMessageSequence;
}
private boolean addUnmarked(Message<?> message) {
if (isMember(message)) {
@@ -127,6 +139,10 @@ public class SimpleMessageGroup implements MessageGroup {
return Collections.unmodifiableCollection(unmarked);
}
}
public void setLastReleasedMessageSequenceNumber(int sequenceNumber){
this.lastReleasedMessageSequence = sequenceNumber;
}
public Collection<Message<?>> getMarked() {
synchronized (lock) {
@@ -139,14 +155,13 @@ public class SimpleMessageGroup implements MessageGroup {
}
public boolean isComplete() {
if (size() == 0) {
return true;
}
int sequenceSize = getSequenceSize();
// If there is no sequence then it must be incomplete....
return sequenceSize > 0 && sequenceSize == size();
return this.complete;
}
public void complete() {
this.complete = true;
}
public int getSequenceSize() {
if (size() == 0) {
return 0;
@@ -183,6 +198,11 @@ public class SimpleMessageGroup implements MessageGroup {
}
return one;
}
public void clear(){
this.marked.clear();
this.unmarked.clear();
}
/**
* This method determines whether messages have been added to this group that supersede the given message based on

View File

@@ -148,10 +148,14 @@ public class SimpleMessageStore extends AbstractMessageGroupStore implements Mes
return group;
}
@Override
public Iterator<MessageGroup> iterator() {
return new HashSet<MessageGroup>(groupIdToMessageGroup.values()).iterator();
}
public void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) {
SimpleMessageGroup group = getMessageGroupInternal(groupId);
group.setLastReleasedMessageSequenceNumber(sequenceNumber);
}
private SimpleMessageGroup getMessageGroupInternal(Object groupId) {
if (!groupIdToMessageGroup.containsKey(groupId)) {
@@ -160,4 +164,9 @@ public class SimpleMessageStore extends AbstractMessageGroupStore implements Mes
return groupIdToMessageGroup.get(groupId);
}
public void completeGroup(Object groupId) {
SimpleMessageGroup group = getMessageGroupInternal(groupId);
group.complete();
}
}

View File

@@ -2692,6 +2692,33 @@ endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expire-groups-upon-completion" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Boolean flag specifying if MessageGroup should be removed once completed. Useful for
handling late arrival use cases where messages arriving with the correlationKey that
is the same as the completed MessageGroup will be discarded. Default is 'false'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="release-strategy-method" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-method type-ref="@release-strategy" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
A method defined on the bean referenced by release-strategy, that implements the completion
decision algorithm.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="release-strategy-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>A SpEL expression to apply to the message group (e.g, payload.size() > 6)</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -2713,22 +2740,9 @@ endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="release-strategy-method" type="xsd:string">
<xsd:attribute name="keep-released-messages" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-method type-ref="@release-strategy" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
A method defined on the bean referenced by release-strategy, that implements the completion
decision algorithm.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="release-strategy-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>A SpEL expression to apply to the message group (e.g, payload.size() > 6)</xsd:documentation>
<xsd:documentation>Will store messages after their release. Mainly used for monitoring purposes. Default is 'true'</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="correlation-strategy" type="xsd:string">

View File

@@ -42,14 +42,14 @@ import org.springframework.integration.support.MessageBuilder;
*/
public class AggregatorTests {
private CorrelatingMessageHandler aggregator;
private AggregatingMessageHandler aggregator;
private SimpleMessageStore store = new SimpleMessageStore(50);
@Before
public void configureAggregator() {
this.aggregator = new CorrelatingMessageHandler(new MultiplyingProcessor(), store);
this.aggregator = new AggregatingMessageHandler(new MultiplyingProcessor(), store);
}
@@ -211,7 +211,7 @@ public class AggregatorTests {
@Test
public void testNullReturningAggregator() throws InterruptedException {
this.aggregator = new CorrelatingMessageHandler(new NullReturningMessageProcessor(), new SimpleMessageStore(50));
this.aggregator = new AggregatingMessageHandler(new NullReturningMessageProcessor(), new SimpleMessageStore(50));
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);

View File

@@ -16,19 +16,12 @@
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.Message;
@@ -42,6 +35,13 @@ import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
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;
/**
* @author Mark Fisher
* @author Marius Bogoevici
@@ -51,7 +51,7 @@ public class ConcurrentAggregatorTests {
private TaskExecutor taskExecutor;
private CorrelatingMessageHandler aggregator;
private AggregatingMessageHandler aggregator;
private MessageGroupStore store = new SimpleMessageStore();
@@ -59,7 +59,7 @@ public class ConcurrentAggregatorTests {
@Before
public void configureAggregator() {
this.taskExecutor = new SimpleAsyncTaskExecutor();
this.aggregator = new CorrelatingMessageHandler(new MultiplyingProcessor(), store);
this.aggregator = new AggregatingMessageHandler(new MultiplyingProcessor(), store);
}
@@ -274,7 +274,7 @@ public class ConcurrentAggregatorTests {
@Test
public void testNullReturningAggregator() throws InterruptedException {
this.aggregator = new CorrelatingMessageHandler(new NullReturningMessageProcessor(), new SimpleMessageStore(
this.aggregator = new AggregatingMessageHandler(new NullReturningMessageProcessor(), new SimpleMessageStore(
50));
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);

View File

@@ -16,21 +16,20 @@
package org.springframework.integration.aggregator;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class CorrelatingMessageHandlerIntegrationTests {
private MessageGroupStore store = new SimpleMessageStore(100);
@@ -39,7 +38,7 @@ public class CorrelatingMessageHandlerIntegrationTests {
private MessageGroupProcessor processor = new PassThroughMessageGroupProcessor();
private CorrelatingMessageHandler defaultHandler = new CorrelatingMessageHandler(processor, store);
private AggregatingMessageHandler defaultHandler = new AggregatingMessageHandler(processor, store);
@Before
public void setupHandler() {

View File

@@ -16,13 +16,6 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -35,7 +28,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.internal.stubbing.answers.ThrowsException;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageHandlingException;
@@ -45,6 +37,14 @@ import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Iwein Fuld
* @author Dave Syer
@@ -52,7 +52,7 @@ import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class CorrelatingMessageHandlerTests {
private CorrelatingMessageHandler handler;
private AggregatingMessageHandler handler;
@Mock
private CorrelationStrategy correlationStrategy;
@@ -70,7 +70,7 @@ public class CorrelatingMessageHandlerTests {
@Before
public void initializeSubject() {
handler = new CorrelatingMessageHandler(processor, store, correlationStrategy, ReleaseStrategy);
handler = new AggregatingMessageHandler(processor, store, correlationStrategy, ReleaseStrategy);
handler.setOutputChannel(outputChannel);
}
@@ -94,7 +94,7 @@ public class CorrelatingMessageHandlerTests {
verify(processor).processMessageGroup(isA(SimpleMessageGroup.class));
}
private void verifyLocks(CorrelatingMessageHandler handler, int lockCount) {
private void verifyLocks(AggregatingMessageHandler handler, int lockCount) {
assertEquals(lockCount, ((Map<?, ?>) ReflectionTestUtils.getField(handler, "locks")).size());
}
@@ -110,6 +110,8 @@ public class CorrelatingMessageHandlerTests {
when(correlationStrategy.getCorrelationKey(isA(Message.class))).thenReturn(correlationKey);
handler.setExpireGroupsUponCompletion(true);
handler.handleMessage(message1);
try {

View File

@@ -479,7 +479,7 @@ public class MethodInvokingMessageGroupProcessorTests {
proxyFactory.setProxyTargetClass(false);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
AggregatingMessageHandler handler = new AggregatingMessageHandler(aggregator);
handler.setReleaseStrategy(new MessageCountReleaseStrategy());
handler.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);
@@ -498,7 +498,7 @@ public class MethodInvokingMessageGroupProcessorTests {
proxyFactory.setProxyTargetClass(true);
testBean = (GreetingService) proxyFactory.getProxy();
MethodInvokingMessageGroupProcessor aggregator = new MethodInvokingMessageGroupProcessor(testBean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(aggregator);
AggregatingMessageHandler handler = new AggregatingMessageHandler(aggregator);
handler.setReleaseStrategy(new MessageCountReleaseStrategy());
handler.setOutputChannel(output);
EventDrivenConsumer endpoint = new EventDrivenConsumer(input, handler);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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.
@@ -16,6 +16,11 @@
package org.springframework.integration.aggregator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.Message;
@@ -25,23 +30,23 @@ import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
/**
* @author Marius Bogoevici
* @author Alex Peters
* @author Dave Syer
* @author Iwein Fuld
* @author Oleg Zhurakousky
*/
public class ResequencerTests {
private CorrelatingMessageHandler resequencer;
private ResequencingMessageHandler resequencer;
private ResequencingMessageGroupProcessor processor = new ResequencingMessageGroupProcessor();
@@ -49,7 +54,7 @@ public class ResequencerTests {
@Before
public void configureResequencer() {
this.resequencer = new CorrelatingMessageHandler(processor, store, null, null);
this.resequencer = new ResequencingMessageHandler(processor, store, null, null);
}
@Test
@@ -71,6 +76,59 @@ public class ResequencerTests {
assertNotNull(reply3);
assertThat( reply3.getHeaders().getSequenceNumber(), is(3));
}
@Test
public void testBasicResequencingA() throws InterruptedException {
SequenceSizeReleaseStrategy releaseStrategy = new SequenceSizeReleaseStrategy();
releaseStrategy.setReleasePartialSequences(true);
this.resequencer = new ResequencingMessageHandler(processor, store, null, releaseStrategy);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage("123", "ABC", 3, 1, replyChannel);
Message<?> message3 = createMessage("789", "ABC", 3, 3, replyChannel);
this.resequencer.handleMessage(message3);
assertNull(replyChannel.receive(0));
this.resequencer.handleMessage(message1);
assertNotNull(replyChannel.receive(0));
assertNull(replyChannel.receive(0));
}
@Test
public void testBasicUnboundedResequencing() throws InterruptedException {
SequenceSizeReleaseStrategy releaseStrategy = new SequenceSizeReleaseStrategy();
releaseStrategy.setReleasePartialSequences(true);
this.resequencer = new ResequencingMessageHandler(processor, store, null, releaseStrategy);
QueueChannel replyChannel = new QueueChannel();
this.resequencer.setCorrelationStrategy(new CorrelationStrategy() {
public Object getCorrelationKey(Message<?> message) {
return "A";
}
});
//Message<?> message0 = MessageBuilder.withPayload("0").setSequenceNumber(0).build();
Message<?> message1 = MessageBuilder.withPayload("1").setSequenceNumber(1).setReplyChannel(replyChannel).build();
Message<?> message2 = MessageBuilder.withPayload("2").setSequenceNumber(2).setReplyChannel(replyChannel).build();
Message<?> message3 = MessageBuilder.withPayload("3").setSequenceNumber(3).setReplyChannel(replyChannel).build();
Message<?> message4 = MessageBuilder.withPayload("4").setSequenceNumber(4).setReplyChannel(replyChannel).build();
Message<?> message5 = MessageBuilder.withPayload("5").setSequenceNumber(5).setReplyChannel(replyChannel).build();
this.resequencer.handleMessage(message3);
assertNull(replyChannel.receive(0));
this.resequencer.handleMessage(message1);
assertNotNull(replyChannel.receive(0));
this.resequencer.handleMessage(message2);
assertNotNull(replyChannel.receive(0));
assertNotNull(replyChannel.receive(0));
assertNull(replyChannel.receive(0));
this.resequencer.handleMessage(message5);
assertNull(replyChannel.receive(0));
this.resequencer.handleMessage(message4);
assertNotNull(replyChannel.receive(0));
}
@Test
public void testBasicResequencingWithCustomComparator() throws InterruptedException {

View File

@@ -5,7 +5,7 @@
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<channel id="input"/>

View File

@@ -5,7 +5,7 @@
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<channel id="input">
<queue capacity="5" />
@@ -20,8 +20,19 @@
<channel id="output">
<queue capacity="5" />
</channel>
<channel id="discard">
<queue capacity="5" />
</channel>
<beans:bean id="summer"
class="org.springframework.integration.aggregator.integration.AggregatorIntegrationTests$SummingAggregator" />
<aggregator id="expiringAggregator" input-channel="expiringAggregatorInput" output-channel="output"
expire-groups-upon-completion="true" discard-channel="discard"/>
<aggregator id="nonExpiringAggregator" input-channel="nonExpiringAggregatorInput" output-channel="output"
expire-groups-upon-completion="false" discard-channel="discard"/>
</beans:beans>

View File

@@ -17,6 +17,8 @@
package org.springframework.integration.aggregator.integration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.HashMap;
import java.util.List;
@@ -45,10 +47,22 @@ public class AggregatorIntegrationTests {
@Autowired
@Qualifier("input")
private MessageChannel input;
@Autowired
@Qualifier("expiringAggregatorInput")
private MessageChannel expiringAggregatorInput;
@Autowired
@Qualifier("nonExpiringAggregatorInput")
private MessageChannel nonExpiringAggregatorInput;
@Autowired
@Qualifier("output")
private PollableChannel output;
@Autowired
@Qualifier("discard")
private PollableChannel discard;
@Test//(timeout=5000)
public void testVanillaAggregation() throws Exception {
@@ -58,6 +72,49 @@ public class AggregatorIntegrationTests {
}
assertEquals(0 + 1 + 2 + 3 + 4, output.receive().getPayload());
}
@Test
public void testNonExpiringAggregator() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNull(output.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
}
@Test
public void testExpiringAggregator() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
}
// configured in context associated with this test
public static class SummingAggregator {

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2002-2011 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.integration;
import java.util.List;
import org.junit.Test;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor;
import org.springframework.integration.aggregator.ReleaseStrategy;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author Oleg Zhurakousky
*
*/
public class AggregatorSupportedUseCasesTests {
private MessageGroupStore store = new SimpleMessageStore(100);
private DefaultAggregatingMessageGroupProcessor processor = new DefaultAggregatingMessageGroupProcessor();
private AggregatingMessageHandler defaultHandler = new AggregatingMessageHandler(processor, store);
@Test
public void waitForAllDefaultReleaseStrategyWithLateArrivals(){
QueueChannel outputChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
defaultHandler.setOutputChannel(outputChannel);
defaultHandler.setDiscardChannel(discardChannel);
defaultHandler.setKeepReleasedMessages(false);
for (int i = 0; i < 5; i++) {
defaultHandler.handleMessage(MessageBuilder.withPayload(i).setSequenceSize(5).setCorrelationId("A").setSequenceNumber(i).build());
}
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertNull(discardChannel.receive(0));
assertEquals(0, store.getMessageGroup("A").getUnmarked().size());
assertEquals(0, store.getMessageGroup("A").getMarked().size());
// send another message with the same correlation id and see it in the discard channel
defaultHandler.handleMessage(MessageBuilder.withPayload("foo").setSequenceSize(5).setCorrelationId("A").setSequenceNumber(3).build());
assertNotNull(discardChannel.receive(0));
// set 'expireGroupsUponCompletion' to 'true' and the messages should start accumulating again
defaultHandler.setExpireGroupsUponCompletion(true);
defaultHandler.handleMessage(MessageBuilder.withPayload("foo").setSequenceSize(5).setCorrelationId("A").setSequenceNumber(3).build());
assertNull(discardChannel.receive(0));
assertEquals(1, store.getMessageGroup("A").getUnmarked().size());
}
@Test
public void waitForAllCustomReleaseStrategyWithLateArrivals(){
QueueChannel outputChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
defaultHandler.setOutputChannel(outputChannel);
defaultHandler.setDiscardChannel(discardChannel);
defaultHandler.setReleaseStrategy(new SampleSizeReleaseStrategy());
defaultHandler.setKeepReleasedMessages(false);
for (int i = 0; i < 5; i++) {
defaultHandler.handleMessage(MessageBuilder.withPayload(i).setCorrelationId("A").build());
}
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertNull(discardChannel.receive(0));
assertEquals(0, store.getMessageGroup("A").getUnmarked().size());
assertEquals(0, store.getMessageGroup("A").getMarked().size());
// send another message with the same correlation id and see it in the discard channel
defaultHandler.handleMessage(MessageBuilder.withPayload("foo").setCorrelationId("A").build());
assertNotNull(discardChannel.receive(0));
// set 'expireGroupsUponCompletion' to 'true' and the messages should start accumulating again
defaultHandler.setExpireGroupsUponCompletion(true);
defaultHandler.handleMessage(MessageBuilder.withPayload("foo").setCorrelationId("A").build());
assertNull(discardChannel.receive(0));
assertEquals(1, store.getMessageGroup("A").getUnmarked().size());
}
@Test
public void firstBest(){
QueueChannel outputChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
defaultHandler.setOutputChannel(outputChannel);
defaultHandler.setDiscardChannel(discardChannel);
defaultHandler.setReleaseStrategy(new FirstBestReleaseStrategy());
for (int i = 0; i < 5; i++) {
defaultHandler.handleMessage(MessageBuilder.withPayload(i).setCorrelationId("A").build());
}
assertEquals(1, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertNotNull(discardChannel.receive(0));
assertNotNull(discardChannel.receive(0));
assertNotNull(discardChannel.receive(0));
assertNotNull(discardChannel.receive(0));
}
@Test
public void batchingWithoutLeftovers(){
QueueChannel outputChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
defaultHandler.setOutputChannel(outputChannel);
defaultHandler.setDiscardChannel(discardChannel);
defaultHandler.setReleaseStrategy(new SampleSizeReleaseStrategy());
defaultHandler.setExpireGroupsUponCompletion(true);
for (int i = 0; i < 10; i++) {
defaultHandler.handleMessage(MessageBuilder.withPayload(i).setCorrelationId("A").build());
}
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertNull(discardChannel.receive(0));
}
@Test
public void batchingWithLeftovers(){
QueueChannel outputChannel = new QueueChannel();
QueueChannel discardChannel = new QueueChannel();
defaultHandler.setOutputChannel(outputChannel);
defaultHandler.setDiscardChannel(discardChannel);
defaultHandler.setReleaseStrategy(new SampleSizeReleaseStrategy());
defaultHandler.setExpireGroupsUponCompletion(true);
for (int i = 0; i < 12; i++) {
defaultHandler.handleMessage(MessageBuilder.withPayload(i).setCorrelationId("A").build());
}
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertEquals(5, ((List<?>)outputChannel.receive(0).getPayload()).size());
assertNull(discardChannel.receive(0));
assertEquals(2, store.getMessageGroup("A").getUnmarked().size());
}
private class SampleSizeReleaseStrategy implements ReleaseStrategy {
public boolean canRelease(MessageGroup group) {
return group.getUnmarked().size() == 5;
}
}
private class FirstBestReleaseStrategy implements ReleaseStrategy {
public boolean canRelease(MessageGroup group) {
return true;
}
}
}

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<channel id="output">
<queue/>

View File

@@ -5,7 +5,7 @@
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<annotation-config />

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<channel id="pojoOutput">
<queue/>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<channel id="input" />
<resequencer correlation-strategy-expression="headers['foo']" release-strategy-expression="size()>2" input-channel="input" output-channel="output" />
<channel id="output">
<queue capacity="5" />
</channel>
</beans:beans>

View File

@@ -1,78 +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.integration;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Iwein Fuld
* @author Alex Peters
* @author Oleg Zhurakousky
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ResequencerExpressionIntegrationTests {
@Autowired
@Qualifier("input")
private MessageChannel input;
@Autowired
@Qualifier("output")
private PollableChannel output;
@Test//(timeout=5000)
public void testVanillaAggregation() throws Exception {
List<Message<?>> messages = new ArrayList<Message<?>>();
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
messages.add(new GenericMessage<Integer>(i, headers));
}
input.send(messages.get(2));
input.send(messages.get(1));
input.send(messages.get(0));
assertEquals(0, output.receive().getPayload());
assertEquals(1, output.receive().getPayload());
assertEquals(2, output.receive().getPayload());
}
private Map<String, Object> stubHeaders(int sequenceNumber, int sequenceSize, int correllationId) {
Map<String, Object> headers = new HashMap<String, Object>();
headers.put(MessageHeaders.SEQUENCE_NUMBER, sequenceNumber);
headers.put(MessageHeaders.SEQUENCE_SIZE, sequenceSize);
headers.put("foo", correllationId);
return headers;
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<int:resequencer id="resequencerLight" input-channel="resequencerLightInput" output-channel="outputChannel" release-partial-sequences="true"
keep-released-messages="false"/>
<int:channel id="outputChannel">
<int:queue/>
</int:channel>
<int:resequencer id="resequencerDeep" input-channel="resequencerDeepInput" output-channel="outputChannel" release-partial-sequences="true"/>
</beans>

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2011 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.integration;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.aggregator.ResequencingMessageHandler;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author Oleg Zhurakousky
*/
public class ResequencerIntegrationTest {
@Test
public void validateUnboundedResequencerLight(){
ApplicationContext context = new ClassPathXmlApplicationContext("ResequencerIntegrationTest-context.xml", ResequencerIntegrationTest.class);
MessageChannel inputChannel = context .getBean("resequencerLightInput", MessageChannel.class);
QueueChannel outputChannel = context .getBean("outputChannel", QueueChannel.class);
EventDrivenConsumer edc = context.getBean("resequencerLight", EventDrivenConsumer.class);
ResequencingMessageHandler handler = TestUtils.getPropertyValue(edc, "handler", ResequencingMessageHandler.class);
MessageGroupStore store = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
Message<?> message1 = MessageBuilder.withPayload("1").setCorrelationId("A").setSequenceNumber(1).build();
Message<?> message2 = MessageBuilder.withPayload("2").setCorrelationId("A").setSequenceNumber(2).build();
Message<?> message3 = MessageBuilder.withPayload("3").setCorrelationId("A").setSequenceNumber(3).build();
Message<?> message4 = MessageBuilder.withPayload("4").setCorrelationId("A").setSequenceNumber(4).build();
Message<?> message5 = MessageBuilder.withPayload("5").setCorrelationId("A").setSequenceNumber(5).build();
Message<?> message6 = MessageBuilder.withPayload("6").setCorrelationId("A").setSequenceNumber(6).build();
inputChannel.send(message3);
assertNull(outputChannel.receive(0));
inputChannel.send(message1);
message1 = outputChannel.receive(0);
assertNotNull(message1);
assertEquals((Integer)1, message1.getHeaders().getSequenceNumber());
inputChannel.send(message2);
message2 = outputChannel.receive(0);
message3 = outputChannel.receive(0);
assertNotNull(message2);
assertNotNull(message3);
assertEquals((Integer)2, message2.getHeaders().getSequenceNumber());
assertEquals((Integer)3, message3.getHeaders().getSequenceNumber());
inputChannel.send(message5);
assertNull(outputChannel.receive(0));
inputChannel.send(message6);
assertNull(outputChannel.receive(0));
inputChannel.send(message4);
message4 = outputChannel.receive(0);
message5 = outputChannel.receive(0);
message6 = outputChannel.receive(0);
assertNotNull(message4);
assertNotNull(message5);
assertNotNull(message6);
assertEquals((Integer)4, message4.getHeaders().getSequenceNumber());
assertEquals((Integer)5, message5.getHeaders().getSequenceNumber());
assertEquals((Integer)6, message6.getHeaders().getSequenceNumber());
assertEquals(0, store.getMessageGroup("A").getUnmarked().size());
assertEquals(0, store.getMessageGroup("A").getMarked().size());
}
@Test
public void validateUnboundedResequencerDeep(){
ApplicationContext context = new ClassPathXmlApplicationContext("ResequencerIntegrationTest-context.xml", ResequencerIntegrationTest.class);
MessageChannel inputChannel = context .getBean("resequencerDeepInput", MessageChannel.class);
QueueChannel outputChannel = context .getBean("outputChannel", QueueChannel.class);
EventDrivenConsumer edc = context.getBean("resequencerDeep", EventDrivenConsumer.class);
ResequencingMessageHandler handler = TestUtils.getPropertyValue(edc, "handler", ResequencingMessageHandler.class);
MessageGroupStore store = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
Message<?> message1 = MessageBuilder.withPayload("1").setCorrelationId("A").setSequenceNumber(1).build();
Message<?> message2 = MessageBuilder.withPayload("2").setCorrelationId("A").setSequenceNumber(2).build();
Message<?> message3 = MessageBuilder.withPayload("3").setCorrelationId("A").setSequenceNumber(3).build();
inputChannel.send(message3);
assertNull(outputChannel.receive(0));
inputChannel.send(message1);
assertNotNull(outputChannel.receive(0));
inputChannel.send(message2);
assertNotNull(outputChannel.receive(0));
assertNotNull(outputChannel.receive(0));
assertEquals(0, store.getMessageGroup("A").getUnmarked().size());
assertEquals(3, store.getMessageGroup("A").getMarked().size());
}
}

View File

@@ -16,11 +16,6 @@
package org.springframework.integration.config;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -38,7 +33,7 @@ import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageDeliveryException;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy;
import org.springframework.integration.aggregator.ReleaseStrategy;
@@ -49,6 +44,12 @@ import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* @author Marius Bogoevici
* @author Mark Fisher
@@ -111,7 +112,7 @@ public class AggregatorParserTests {
MessageChannel outputChannel = (MessageChannel) context.getBean("outputChannel");
MessageChannel discardChannel = (MessageChannel) context.getBean("discardChannel");
Object consumer = new DirectFieldAccessor(endpoint).getPropertyValue("handler");
assertThat(consumer, is(CorrelatingMessageHandler.class));
assertThat(consumer, is(AggregatingMessageHandler.class));
DirectFieldAccessor accessor = new DirectFieldAccessor(consumer);
Map<?, ?> map = (Map<?, ?>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
.getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate"))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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
@@ -13,21 +13,27 @@
package org.springframework.integration.config;
import java.util.Comparator;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.aggregator.*;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor;
import org.springframework.integration.aggregator.ResequencingMessageHandler;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import java.util.Comparator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
/**
@@ -47,8 +53,8 @@ public class ResequencerParserTests {
@Test
public void testDefaultResequencerProperties() {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("defaultResequencer");
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.class);
assertNull(getPropertyValue(resequencer, "outputChannel"));
assertTrue(getPropertyValue(resequencer, "discardChannel") instanceof NullChannel);
assertEquals("The ResequencerEndpoint is not set with the appropriate timeout value", 1000l, getPropertyValue(
@@ -65,8 +71,8 @@ public class ResequencerParserTests {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("completelyDefinedResequencer");
MessageChannel outputChannel = (MessageChannel) context.getBean("outputChannel");
MessageChannel discardChannel = (MessageChannel) context.getBean("discardChannel");
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.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", discardChannel,
@@ -84,8 +90,8 @@ public class ResequencerParserTests {
public void testCorrelationStrategyRefOnly() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context
.getBean("resequencerWithCorrelationStrategyRefOnly");
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.class);
assertEquals("The ResequencerEndpoint is not configured with the appropriate CorrelationStrategy", context
.getBean("testCorrelationStrategy"), getPropertyValue(resequencer, "correlationStrategy"));
}
@@ -93,8 +99,8 @@ public class ResequencerParserTests {
@Test
public void shouldSetReleasePartialSequencesFlag(){
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("completelyDefinedResequencer");
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.class);
assertEquals("The ResequencerEndpoint is not configured with the appropriate 'release partial sequences' flag",
true, getPropertyValue(getPropertyValue(resequencer, "releaseStrategy"), "releasePartialSequences"));
}
@@ -103,8 +109,8 @@ public class ResequencerParserTests {
public void testCorrelationStrategyRefAndMethod() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context
.getBean("resequencerWithCorrelationStrategyRefAndMethod");
CorrelatingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler resequencer = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.class);
Object correlationStrategy = getPropertyValue(resequencer, "correlationStrategy");
assertEquals("The ResequencerEndpoint is not configured with a CorrelationStrategy adapter",
MethodInvokingCorrelationStrategy.class, correlationStrategy.getClass());
@@ -115,8 +121,8 @@ public class ResequencerParserTests {
@Test
public void testComparator() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("resequencerWithComparator");
CorrelatingMessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
ResequencingMessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler",
ResequencingMessageHandler.class);
ResequencingMessageGroupProcessor resequencer = TestUtils.getPropertyValue(handler, "outputProcessor",
ResequencingMessageGroupProcessor.class);
Object comparator = getPropertyValue(resequencer, "comparator");
@@ -124,16 +130,6 @@ public class ResequencerParserTests {
.getClass());
}
@Test
public void testReleaseStrategy() throws Exception {
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("resequencerWithReleaseStrategy");
CorrelatingMessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler",
CorrelatingMessageHandler.class);
Object releaseStrategy = getPropertyValue(handler, "releaseStrategy");
assertEquals("The Resequencer is not configured with an adapter", MethodInvokingReleaseStrategy.class, releaseStrategy
.getClass());
}
@SuppressWarnings("unused")
private static <T> Message<T> createMessage(T payload, Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel outputChannel) {

View File

@@ -16,12 +16,6 @@
package org.springframework.integration.config.annotation;
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;
import java.util.Map;
@@ -30,9 +24,9 @@ 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.MethodInvokingReleaseStrategy;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy;
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.core.MessageHandler;
@@ -41,6 +35,13 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve
import org.springframework.integration.support.channel.ChannelResolver;
import org.springframework.integration.test.util.TestUtils;
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;
/**
* @author Marius Bogoevici
* @author Mark Fisher
@@ -56,7 +57,7 @@ public class AggregatorAnnotationTests {
assertTrue(getPropertyValue(aggregator, "releaseStrategy") instanceof SequenceSizeReleaseStrategy);
assertNull(getPropertyValue(aggregator, "outputChannel"));
assertTrue(getPropertyValue(aggregator, "discardChannel") instanceof NullChannel);
assertEquals(CorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT, getPropertyValue(aggregator,
assertEquals(AggregatingMessageHandler.DEFAULT_SEND_TIMEOUT, getPropertyValue(aggregator,
"messagingTemplate.sendTimeout"));
assertEquals(false, getPropertyValue(aggregator, "sendPartialResultOnExpiry"));
}

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">
<channel id="inputChannel"/>
@@ -50,10 +50,10 @@
input-channel="inputChannel5"
comparator="testComparator"/>
<resequencer id="resequencerWithReleaseStrategy"
input-channel="inputChannel6"
release-strategy="pojoReleaseStrategy"
release-strategy-method="checkCompletenessAsList"/>
<!-- <resequencer id="resequencerWithReleaseStrategy" -->
<!-- input-channel="inputChannel6" -->
<!-- release-strategy="pojoReleaseStrategy" -->
<!-- release-strategy-method="checkCompletenessAsList"/> -->
<beans:bean id="testComparator"
class="org.springframework.integration.config.ResequencerParserTests$TestComparator"/>
@@ -64,9 +64,9 @@
<beans:bean id="testCorrelationStrategyPojo"
class="org.springframework.integration.config.ResequencerParserTests$TestCorrelationStrategyPojo"/>
<beans:bean id="pojoReleaseStrategy"
class="org.springframework.integration.config.MaxValueReleaseStrategy">
<beans:constructor-arg value="10" />
</beans:bean>
<!-- <beans:bean id="pojoReleaseStrategy" -->
<!-- class="org.springframework.integration.config.MaxValueReleaseStrategy"> -->
<!-- <beans:constructor-arg value="10" /> -->
<!-- </beans:bean> -->
</beans:beans>

View File

@@ -16,21 +16,20 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
import java.util.List;
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.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
/**
* Validates the "p:namespace" is working for inner "bean" definition within SI components.
*
@@ -92,7 +91,7 @@ public class PNamespaceTests {
@Test
public void testPNamespaceChain() {
List<?> handlers = (List<?>) TestUtils.getPropertyValue(sampleChain, "handler.handlers");
CorrelatingMessageHandler handler = (CorrelatingMessageHandler) handlers.get(0);
AggregatingMessageHandler handler = (AggregatingMessageHandler) handlers.get(0);
SampleAggregator aggregator =
(SampleAggregator) TestUtils.getPropertyValue(handler, "outputProcessor.processor.delegate.targetObject");
assertEquals("Bill", aggregator.getName());

View File

@@ -87,7 +87,6 @@ public class MessageStoreTests {
private boolean removed = false;
@Override
public Iterator<MessageGroup> iterator() {
return Arrays.asList(testMessages).iterator();
}
@@ -118,6 +117,15 @@ public class MessageStoreTests {
}
}
public void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) {
throw new UnsupportedOperationException();
}
public void completeGroup(Object groupId) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -92,16 +92,10 @@
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data.gemfire</groupId>
<artifactId>spring-gemfire</artifactId>
<version>1.1.0.M2</version>
<version>1.1.0.M3</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@@ -118,6 +112,18 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -144,9 +150,9 @@
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<artifactId>spring-integration-stream</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<scope>compile</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
@@ -160,12 +166,6 @@
<version>2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2011 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.gemfire.store;
import org.springframework.integration.Message;
import com.gemstone.gemfire.cache.Region;
/**
* Provides GemFire specific support as a backing key-value based {@link org.springframework.integration.store.MessageGroupStore}.
* Currently, this support is limited to explicitly depending on GemFire {@link com.gemstone.gemfire.cache.Region}s, but
* might conceptually also support optimized key traversal (using a {@link com.gemstone.gemfire.cache.query.Query}, for example).
*
* @author Josh Long
* @since 2.1
* @see {@link org.springframework.integration.gemfire.store.KeyValueMessageGroupStore}
*/
public class GemfireMessageGroupStore extends KeyValueMessageGroupStore {
public GemfireMessageGroupStore(
Region<Object, KeyValueMessageGroup> groupIdToMessageGroup,
Region<String, Message<?>> marked,
Region<String, Message<?>> unmarked ) {
super(groupIdToMessageGroup, marked, unmarked);
}
}

View File

@@ -16,44 +16,97 @@
package org.springframework.integration.gemfire.store;
import java.util.UUID;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.integration.Message;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.integration.store.AbstractKeyValueMessageStore;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.Region;
/**
* Gemfire implementation of the key/value style {@link MessageStore} and {@link MessageGroupStore}
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @since 2.1
*/
public class GemfireMessageStore implements MessageStore {
public class GemfireMessageStore extends AbstractKeyValueMessageStore implements InitializingBean {
private final Region<UUID, Message<?>> region;
private volatile Region<Object, Object> messageStoreRegion;
public GemfireMessageStore(Region<UUID, Message<?>> region) {
Assert.notNull(region, "region must not be null");
this.region = region;
private final Cache cache;
private volatile boolean ignoreJta = true;
public GemfireMessageStore(Cache cache) {
Assert.notNull(cache, "'cache' must not be null");
this.cache = cache;
}
public Message<?> getMessage(UUID id) {
return this.region.get(id);
public void setIgnoreJta(boolean ignoreJta) {
this.ignoreJta = ignoreJta;
}
public <T> Message<T> addMessage(Message<T> message) {
this.region.put(message.getHeaders().getId(), message);
return message;
@SuppressWarnings("unchecked")
public void afterPropertiesSet() {
try {
RegionAttributesFactoryBean attributesFactoryBean = new RegionAttributesFactoryBean();
attributesFactoryBean.setIgnoreJTA(this.ignoreJta);
attributesFactoryBean.afterPropertiesSet();
RegionFactoryBean<Object, Object> messageRegionFactoryBean = new RegionFactoryBean<Object, Object>();
messageRegionFactoryBean.setBeanName("messageStoreRegion");
messageRegionFactoryBean.setAttributes(attributesFactoryBean.getObject());
messageRegionFactoryBean.setCache(cache);
messageRegionFactoryBean.afterPropertiesSet();
this.messageStoreRegion = messageRegionFactoryBean.getObject();
}
catch (Exception e) {
throw new IllegalArgumentException("Failed to initialize Gemfire Region", e);
}
}
public Message<?> removeMessage(UUID id) {
return this.region.remove(id);
@Override
protected Object doRetrieve(Object id) {
Assert.notNull(id, "'id' must not be null");
return this.messageStoreRegion.get(id);
}
@ManagedAttribute
public long getMessageCount() {
return this.region.size();
@Override
protected void doStore(Object id, Object objectToStore) {
Assert.notNull(id, "'id' must not be null");
Assert.notNull(objectToStore, "'objectToStore' must not be null");
this.messageStoreRegion.put(id, objectToStore);
}
@Override
protected Object doRemove(Object id) {
Assert.notNull(id, "'id' must not be null");
return this.messageStoreRegion.remove(id);
}
@Override
protected Collection<?> doListKeys(String keyPattern) {
Assert.hasText(keyPattern, "'keyPattern' must not be empty");
Collection<Object> keys = this.messageStoreRegion.keySet();
List<Object> keyList = new ArrayList<Object>();
for (Object key : keys) {
String keyValue = key.toString();
if (PatternMatchUtils.simpleMatch(keyPattern, keyValue)){
keyList.add(keyValue);
}
}
return keyList;
}
}

View File

@@ -1,340 +0,0 @@
/*
* Copyright 2002-2011 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.gemfire.store;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
/**
* A {@link org.springframework.integration.store.MessageGroup} that manipulates keys and values to provide persistence.
* Responsible for managing one group's messages as a {@link org.springframework.integration.store.MessageGroup}.
*
* @author Josh Long
* @since 2.1
*/
@SuppressWarnings("serial")
public class KeyValueMessageGroup implements MessageGroup, Serializable {
/**
* this should not be persisted. it's passed in through {@link KeyValueMessageGroupStore}, which has the reference to the {@link java.util.concurrent.ConcurrentMap} instance that should be set here
*/
private transient Map<String, Message<?>> marked;
/**
* this should not be persisted. it's passed in through {@link KeyValueMessageGroupStore}, which has the reference to the {@link java.util.concurrent.ConcurrentMap} instance that should be set here
*/
private transient Map<String, Message<?>> unmarked;
/**
* the #groupId is the unique ID to associate this aggregation of {@link org.springframework.integration.Message}s
*/
private Object groupId;
/**
* passed in through the {@link org.springframework.integration.store.MessageGroupStore}
*/
private long timestamp;
/**
* default javabean ctor (so that this object plays well as a {@link java.io.Serializable} object)
*/
public KeyValueMessageGroup() {
}
public KeyValueMessageGroup(Object groupId) {
this(groupId, System.currentTimeMillis(), null, null);
}
public KeyValueMessageGroup(Object groupId, long timestamp,
ConcurrentMap<String, Message<?>> marked,
ConcurrentMap<String, Message<?>> unmarked) {
this.groupId = groupId;
this.timestamp = timestamp;
this.marked = marked;
this.unmarked = unmarked;
}
public KeyValueMessageGroup(Object groupId,
ConcurrentMap<String, Message<?>> marked,
ConcurrentMap<String, Message<?>> unmarked) {
this(groupId, System.currentTimeMillis(), marked, unmarked);
}
@Override
public int hashCode() {
return groupId.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof KeyValueMessageGroup) {
Object otherGroupId = ((KeyValueMessageGroup) obj).getGroupId();
return getGroupId().equals(otherGroupId);
}
return false;
}
public void setUnmarked(Map<String, Message<?>> unmarked) {
this.unmarked = unmarked;
}
public void setMarked( Map<String, Message<?>> marked) {
this.marked = marked;
}
/**
* @return the timestamp (milliseconds since epoch) associated with the creation of this group
*/
public long getTimestamp() {
return timestamp;
}
/**
* Query if the message can be added.
*/
public boolean canAdd(Message<?> message) {
return !isMember(message);
}
/**
* Add this {@link org.springframework.integration.Message} to the
* {@link org.springframework.integration.store.MessageGroup}, delegating in this case to the {@link #unmarked} field
*
* @param message the {@link org.springframework.integration.Message} you are adding to the {@link java.util.Map}
*/
public void add(Message<?> message) {
if (isMember(message)) {
return;
}
String unmarkedKey = this.unmarkedKey(message);
this.unmarked.put(unmarkedKey, (Message<?>) message);
}
/**
* the only reason we differentiate the keys is so that conceptually you could use the <em>same</em> {@link java.util.Map} instance for both <em>marked</em> and <em>unmarked</em> messages.
*
* This method simply differentiates the key, building on {@link #baseKey(org.springframework.integration.Message)}'s return value
*
* @param msg the {@link org.springframework.integration.Message} from which the key should be generated.
* @return a String to be used as a key
*/
protected String markedKey(Message<?> msg) {
return baseKey(msg) + "-m";
}
/**
* the only reason we differentiate the keys is so that conceptually you could use the <em>same</em> {@link java.util.Map} instance for both <em>marked</em> and <em>unmarked</em> messages.
*
* This method simply differentiates the key, building on {@link #baseKey(org.springframework.integration.Message)}'s return value
*
* @param msg the {@link org.springframework.integration.Message} from which the key should be generated.
* @return a String to be used as a key
*/
protected String unmarkedKey(Message<?> msg) {
return baseKey(msg) + "-u";
}
/**
* Removes this {@link org.springframework.integration.Message} from this {@link org.springframework.integration.store.MessageGroup}'s memory
*
* @param message the message to remove
*/
public void remove(Message<?> message) {
if (unmarked.containsValue(message)) {
unmarked.remove(unmarkedKey(message));
}
if (marked.containsValue(message)) {
marked.remove(markedKey(message));
}
}
/**
* the groupKey is based on the groupID and it sits at the beginning of all the keys for this {@link org.springframework.integration.store.MessageGroup}s keys
*
* @return a string based on {@link #getGroupId()}
*/
protected String groupKey() {
return (getGroupId()).toString();
}
protected String baseKey(Message<?> msg) {
String groupKey = groupKey();
UUID id = msg.getHeaders().getId();
Integer sn = msg.getHeaders().getSequenceNumber();
Integer ss = msg.getHeaders().getSequenceSize();
return String.format("%s-%s-%s-%s", groupKey, id.toString(),
sn.toString(), ss.toString());
}
public Collection<Message<?>> getUnmarked() {
return getMessagesForMessageGroup(this.unmarked);
}
/**
* this method will be used to discover all the messages for a given group in a {@link com.gemstone.gemfire.cache.Region}
*
* @param region the region from which we're hoping to discover these {@link org.springframework.integration.Message}s
* @return a collection of messages
*/
protected Collection<Message<?>> getMessagesForMessageGroup(
Map<String, Message<?>> region) {
try {
String groupMsgKey = groupKey();
Collection<Message<?>> msgs = new ArrayList<Message<?>>();
for (String k : region.keySet()) {
if (k.startsWith(groupMsgKey)) {
msgs.add(region.get(k));
}
}
return msgs;
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
public Collection<Message<?>> getMarked() {
return getMessagesForMessageGroup(this.marked);
}
/**
* @return the key that links these messages together
*/
public Object getGroupId() {
return groupId;
}
/**
* @return true if the group is complete (i.e. no more messages are expected to be added)
*/
public boolean isComplete() {
if (size() == 0) {
return true;
}
int sequenceSize = getSequenceSize();
return (sequenceSize > 0) && (sequenceSize == size());
}
public int getSequenceSize() {
if (size() == 0) {
return 0;
}
return getOne().getHeaders().getSequenceSize();
}
/**
* Mark the given message in this group. If the message is not part of this group then this call has no effect.
*
* @param messageToMark the message that should be marked
*/
public void mark(Message<?> messageToMark) {
if (this.unmarked.containsValue(messageToMark)) {
this.unmarked.remove(baseKey(messageToMark));
}
this.marked.put(baseKey(messageToMark), messageToMark);
}
public void markAll() {
for (Message<?> msg : getUnmarked())
mark(msg);
}
/**
* @return the total number of messages (marked and unmarked) in this group
*/
public int size() {
return getMarked().size() + getUnmarked().size();
}
/**
* @return a single message from the group
*/
public Message<?> getOne() {
if (!this.unmarked.isEmpty()) {
String aKey = this.unmarked.keySet().iterator().next();
return this.unmarked.get(aKey);
}
return null;
}
/**
* 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.
*
* @param message the message to test for candidacy
*
* @return whether or not the message is a member of the group
*
*/
protected 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())) {
return true;
} else {
if (containsSequenceNumber(getUnmarked(), messageSequenceNumber) ||
containsSequenceNumber(getUnmarked(),
messageSequenceNumber)) {
return true;
}
}
}
return false;
}
protected boolean containsSequenceNumber(Collection<Message<?>> messages,
Integer messageSequenceNumber) {
for (Message<?> member : messages) {
Integer memberSequenceNumber = member.getHeaders()
.getSequenceNumber();
if (messageSequenceNumber.equals(memberSequenceNumber)) {
return true;
}
}
return false;
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2002-2011 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.gemfire.store;
import org.springframework.integration.Message;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* Provides an implementation of {@link org.springframework.integration.store.MessageGroupStore} that delegates to a backend Gemfire instance.
* Gemfire holds keys and values. This class provides a strategy to hold objects.
*
* @author Josh Long
* @since 2.1
*/
public class KeyValueMessageGroupStore extends AbstractMessageGroupStore {
/**
* Required {@link com.gemstone.gemfire.cache.Region} to managed the association of groups => {@link KeyValueMessageGroup}
*/
protected Map<Object, KeyValueMessageGroup> groupIdToMessageGroup;
/**
* Required {@link com.gemstone.gemfire.cache.Region} to manage the #unmarked data
*/
protected Map<String, Message<?>> unmarked;
/**
* Required {@link com.gemstone.gemfire.cache.Region} to manage the #marked data
*/
protected Map<String, Message<?>> marked;
/**
* Create a KeyValueMessageGroupStore with two backing regions to handle the state management.
*
* @param groupIdToMessageGroup the region to associate
* @param marked the collection that will hold which messages are marked (delivered)
* @param unmarked the collection that holds which messages are unmarked (not yet delivered)
*/
public KeyValueMessageGroupStore(Map<Object, KeyValueMessageGroup> groupIdToMessageGroup, Map<String, Message<?>> marked, Map<String, Message<?>> unmarked) {
this.marked = marked;
this.unmarked = unmarked;
this.groupIdToMessageGroup = groupIdToMessageGroup;
}
public MessageGroup getMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
return this.getMessageGroupInternal(groupId);
}
public MessageGroup addMessageToGroup(Object groupId, Message<?> message) {
KeyValueMessageGroup group = getMessageGroupInternal(groupId);
group.add(message);
return group;
}
public MessageGroup markMessageGroup(MessageGroup group) {
Object groupId = group.getGroupId();
KeyValueMessageGroup internal = getMessageGroupInternal(groupId);
internal.markAll();
return internal;
}
public void removeMessageGroup(Object groupId) {
groupIdToMessageGroup.remove(groupId);
}
public MessageGroup removeMessageFromGroup(Object key, Message<?> messageToRemove) {
KeyValueMessageGroup group = getMessageGroupInternal(key);
group.remove(messageToRemove);
return group;
}
public MessageGroup markMessageFromGroup(Object key, Message<?> messageToMark) {
KeyValueMessageGroup group = getMessageGroupInternal(key);
group.mark(messageToMark);
return group;
}
@Override
public Iterator<MessageGroup> iterator() {
return new HashSet<MessageGroup>(groupIdToMessageGroup.values()).iterator();
}
protected KeyValueMessageGroup ensureMessageGroupHasReferencesToRegions(KeyValueMessageGroup keyValueMessageGroup) {
if (keyValueMessageGroup == null) {
return null;
}
keyValueMessageGroup.setMarked(this.marked);
keyValueMessageGroup.setUnmarked(this.unmarked);
return keyValueMessageGroup;
}
protected KeyValueMessageGroup getMessageGroupInternal(Object groupId) {
if (!groupIdToMessageGroup.containsKey(groupId)) {
groupIdToMessageGroup.put(groupId, new KeyValueMessageGroup(groupId));
}
return ensureMessageGroupHasReferencesToRegions(groupIdToMessageGroup.get( groupId));
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright 2007-2011 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.gemfire.store;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import junit.framework.AssertionFailedError;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.SimpleMessageGroup;
import org.springframework.integration.support.MessageBuilder;
import com.gemstone.gemfire.cache.Cache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @author Oleg Zhurakousky
*
*/
public class GemfireGroupStoreTests {
private Cache cache;
@Test
public void testNonExistingEmptyMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
assertNotNull(messageGroup);
assertTrue(messageGroup instanceof SimpleMessageGroup);
assertEquals(0, messageGroup.size());
}
@Test
public void testMessageGroupWithAddedMessage() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
messageGroup = store.addMessageToGroup(1, message);
assertEquals(1, messageGroup.size());
// make sure the store is properly rebuild from Gemfire
store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
messageGroup = store.getMessageGroup(1);
assertEquals(1, messageGroup.size());
}
@Test
public void testRemoveMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message);
assertEquals(1, messageGroup.size());
store.removeMessageGroup(1);
MessageGroup messageGroupA = store.getMessageGroup(1);
assertNotSame(messageGroup, messageGroupA);
assertEquals(0, messageGroupA.getMarked().size());
assertEquals(0, messageGroupA.getUnmarked().size());
assertEquals(0, messageGroupA.size());
// make sure the store is properly rebuild from Gemfire
store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
messageGroup = store.getMessageGroup(1);
assertEquals(0, messageGroup.getMarked().size());
assertEquals(0, messageGroup.getUnmarked().size());
assertEquals(0, messageGroup.size());
}
@Test
public void testRemoveMessageFromTheGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("2");
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
store.addMessageToGroup(messageGroup.getGroupId(), message);
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("3"));
assertEquals(3, messageGroup.size());
messageGroup = store.removeMessageFromGroup(1, message);
assertEquals(2, messageGroup.size());
// make sure the store is properly rebuild from Gemfire
store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
public void testMarkAllMessagesInMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("2"));
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("3"));
assertEquals(3, messageGroup.getUnmarked().size());
assertEquals(0, messageGroup.getMarked().size());
messageGroup = store.markMessageGroup(messageGroup);
assertEquals(0, messageGroup.getUnmarked().size());
assertEquals(3, messageGroup.getMarked().size());
// make sure the store is properly rebuild from Gemfire
store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
messageGroup = store.getMessageGroup(1);
assertEquals(0, messageGroup.getUnmarked().size());
assertEquals(3, messageGroup.getMarked().size());
}
@Test
public void testRemoveNonExistingMessageFromTheGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@Test
public void testRemoveNonExistingMessageFromNonExistingTheGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@Test
public void testMarkMessageInMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> messageToMark = new GenericMessage<String>("1");
store.addMessageToGroup(messageGroup.getGroupId(), messageToMark);
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("2"));
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("3"));
assertEquals(3, messageGroup.getUnmarked().size());
assertEquals(0, messageGroup.getMarked().size());
messageGroup = store.markMessageFromGroup(1, messageToMark);
assertEquals(2, messageGroup.getUnmarked().size());
assertEquals(1, messageGroup.getMarked().size());
// make sure the store is properly rebuild from Gemfire
store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.getUnmarked().size());
assertEquals(1, messageGroup.getMarked().size());
}
@Test
public void testCompleteMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> messageToMark = new GenericMessage<String>("1");
store.addMessageToGroup(messageGroup.getGroupId(), messageToMark);
store.completeGroup(messageGroup.getGroupId());
messageGroup = store.getMessageGroup(1);
assertTrue(messageGroup.isComplete());
}
@Test
public void testLastReleasedSequenceNumber() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
store.afterPropertiesSet();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> messageToMark = new GenericMessage<String>("1");
store.addMessageToGroup(messageGroup.getGroupId(), messageToMark);
store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
messageGroup = store.getMessageGroup(1);
assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
}
@Test
public void testMultipleInstancesOfGroupStore() throws Exception{
GemfireMessageStore store1 = new GemfireMessageStore(this.cache);
store1.afterPropertiesSet();
GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
store2.afterPropertiesSet();
Message<?> message = new GenericMessage<String>("1");
store1.addMessageToGroup(1, message);
MessageGroup messageGroup = store2.addMessageToGroup(1, new GenericMessage<String>("2"));
assertEquals(2, messageGroup.getUnmarked().size());
assertEquals(0, messageGroup.getMarked().size());
GemfireMessageStore store3 = new GemfireMessageStore(this.cache);
store3.afterPropertiesSet();
messageGroup = store3.markMessageFromGroup(1, message);
assertEquals(1, messageGroup.getUnmarked().size());
assertEquals(1, messageGroup.getMarked().size());
}
@Test
public void testIteratorOfMessageGroups() throws Exception{
GemfireMessageStore store1 = new GemfireMessageStore(this.cache);
store1.afterPropertiesSet();
GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
store2.afterPropertiesSet();
store1.addMessageToGroup(1, new GenericMessage<String>("1"));
store2.addMessageToGroup(2, new GenericMessage<String>("2"));
store1.addMessageToGroup(3, new GenericMessage<String>("3"));
store2.addMessageToGroup(3, new GenericMessage<String>("3A"));
Iterator<MessageGroup> messageGroups = store1.iterator();
int counter = 0;
while (messageGroups.hasNext()) {
messageGroups.next();
counter++;
}
assertEquals(3, counter);
store2.removeMessageGroup(3);
messageGroups = store1.iterator();
counter = 0;
while (messageGroups.hasNext()) {
messageGroups.next();
counter++;
}
assertEquals(2, counter);
}
@Test
@Ignore
public void testConcurrentModifications() throws Exception{
final GemfireMessageStore store1 = new GemfireMessageStore(this.cache);
store1.afterPropertiesSet();
final GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
store2.afterPropertiesSet();
final Message<?> message = new GenericMessage<String>("1");
ExecutorService executor = null;
final List<Object> failures = new ArrayList<Object>();
for (int i = 0; i < 100; i++) {
executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
public void run() {
MessageGroup group = store1.addMessageToGroup(1, message);
if (group.getUnmarked().size() != 1){
failures.add("ADD");
throw new AssertionFailedError("Failed on ADD");
}
}
});
executor.execute(new Runnable() {
public void run() {
MessageGroup group = store2.removeMessageFromGroup(1, message);
if (group.getUnmarked().size() != 0){
failures.add("REMOVE");
throw new AssertionFailedError("Failed on Remove");
}
}
});
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
store2.removeMessageFromGroup(1, message); // ensures that if ADD thread executed after REMOVE, the store is empty for the next cycle
}
assertTrue(failures.size() == 0);
}
@Test
public void testWithAggregatorWithShutdown(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("gemfire-aggregator-config.xml", this.getClass());
MessageChannel input = context.getBean("inputChannel", MessageChannel.class);
QueueChannel output = context.getBean("outputChannel", QueueChannel.class);
Message<?> m1 = MessageBuilder.withPayload("1").setSequenceNumber(1).setSequenceSize(3).setCorrelationId(1).build();
Message<?> m2 = MessageBuilder.withPayload("2").setSequenceNumber(2).setSequenceSize(3).setCorrelationId(1).build();
input.send(m1);
assertNull(output.receive(1000));
input.send(m2);
assertNull(output.receive(1000));
context = new ClassPathXmlApplicationContext("gemfire-aggregator-config-a.xml", this.getClass());
MessageChannel inputA = context.getBean("inputChannel", MessageChannel.class);
QueueChannel outputA = context.getBean("outputChannel", QueueChannel.class);
Message<?> m3 = MessageBuilder.withPayload("3").setSequenceNumber(3).setSequenceSize(3).setCorrelationId(1).build();
inputA.send(m3);
assertNotNull(outputA.receive(1000));
}
@Before
public void init() throws Exception{
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.afterPropertiesSet();
this.cache = (Cache)cacheFactoryBean.getObject();
}
@After
public void cleanup(){
this.cache.close();
}
}

View File

@@ -1,213 +0,0 @@
/*
* Copyright 2002-2011 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.gemfire.store;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy;
import org.springframework.integration.aggregator.ReleaseStrategy;
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.gemfire.store.KeyValueMessageGroup;
import org.springframework.integration.gemfire.store.KeyValueMessageGroupStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.Region;
/**
* Our aggregator needs a
* {@link org.springframework.integration.gemfire.store.KeyValueMessageGroupStore}
* . This handles configuration of the ancillary objects.
*
* @author Josh Long
* @since 2.1
*/
@Configuration
public class GemfireMessageGroupStoreTestConfiguration {
public static List<String> LIST_OF_STRINGS = Arrays.asList("1,2,3,4,5".split(","));
static private Log log = LogFactory.getLog(GemfireMessageGroupStoreTestConfiguration.class);
@Value("${correlation-header}")
private String correlationHeader;
@Bean
public Cache cache() throws Throwable {
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.afterPropertiesSet();
return (Cache)cacheFactoryBean.getObject();
}
@Bean
public Region<Object, KeyValueMessageGroup> messageGroupRegion() throws Throwable {
RegionFactoryBean<Object, KeyValueMessageGroup> regionFactoryBean = new RegionFactoryBean<Object, KeyValueMessageGroup>();
regionFactoryBean.setName("messageGroupRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean
public Region<String, Message<?>> unmarkedRegion() throws Throwable {
RegionFactoryBean<String, Message<?>> regionFactoryBean = new RegionFactoryBean<String, Message<?>>();
regionFactoryBean.setName("unmarkedRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean
public Region<String, Message<?>> markedRegion() throws Throwable {
RegionFactoryBean<String, Message<?>> regionFactoryBean = new RegionFactoryBean<String, Message<?>>();
regionFactoryBean.setName("markedRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean(name = "messageGroupStoreActivator")
public FakeMessageConsumer serviceActivator() {
return new FakeMessageConsumer();
}
@Bean
public ReleaseStrategy releaseStrategy() {
return new SequenceSizeReleaseStrategy(false);
}
@Bean
public CorrelationStrategy correlationStrategy() {
return new HeaderAttributeCorrelationStrategy(this.correlationHeader);
}
@Bean
public KeyValueMessageGroupStore gemfireMessageGroupStore() throws Throwable {
return new KeyValueMessageGroupStore(messageGroupRegion(), markedRegion(), unmarkedRegion());
}
@Bean
public FakeMessageProducer producer() {
return new FakeMessageProducer();
}
static public class FakeMessageConsumer {
private List<Collection<Object>> batches = new ArrayList<Collection<Object>>();
public List<Collection<Object>> getBatches() {
return this.batches;
}
@ServiceActivator
public void activateAsMessagesArriveInBatches(Message<Collection<Object>> msg) throws Throwable {
Collection<Object> payloads = msg.getPayload();
batches.add(payloads);
if (log.isDebugEnabled()) {
log.debug(payloads);
}
}
}
static public class FakeMessageProducer implements InitializingBean, SmartLifecycle {
public boolean isAutoStartup() {
return false;
}
public void stop(Runnable callback) {
stop();
callback.run();
}
public int getPhase() {
return 0;
}
@Autowired
@Qualifier("i")
private MessageChannel messageChannel;
private MessagingTemplate messagingTemplate = new MessagingTemplate();
private volatile boolean running = false;
@Value("${correlation-header}")
private String correlationHeader;
public void sendManyMessages(int correlationValue, Collection<String> lines) throws Throwable {
Assert.notNull(lines, "the collection must be non-null");
Assert.notEmpty(lines, "the collection must not be empty");
int ctr = 0;
int size = lines.size();
for (String l : lines) {
Message<?> msg = MessageBuilder.withPayload(l).setCorrelationId(this.correlationHeader)
.setHeader(this.correlationHeader, correlationValue).setSequenceNumber(++ctr)
.setSequenceSize(size).build();
this.messagingTemplate.send(msg);
}
}
public void afterPropertiesSet() throws Exception {
this.messagingTemplate.setDefaultChannel(this.messageChannel);
}
public void start() {
running = true;
for (int i = 0; i < 10; i++) {
try {
sendManyMessages(i, LIST_OF_STRINGS);
}
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}
public void stop() {
running = false;
}
public boolean isRunning() {
return running;
}
}
}

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.integration.gemfire.store.GemfireMessageGroupStoreTestConfiguration"/>
<context:property-placeholder location="org/springframework/integration/gemfire/store/common.properties"/>
<int:channel id="i"/>
<int:aggregator release-strategy="releaseStrategy" correlation-strategy="correlationStrategy" message-store="gemfireMessageGroupStore" input-channel="i" output-channel="o" />
<int:channel id="o"/>
<int:service-activator input-channel="o" ref="messageGroupStoreActivator" />
<util:properties id="props" location="org/springframework/integration/gemfire/store/gfe-cache.properties"/>
</beans>

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2002-2011 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.gemfire.store;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests the Gemfire
* {@link org.springframework.integration.store.MessageGroupStore}
* implementation,
* {@link org.springframework.integration.gemfire.store.GemfireMessageGroupStore}
* .
* <p/>
* It tests the {@link org.springframework.integration.store.MessageGroupStore}
* by sending 10 batches of letters (all of the same width), and then counting
* on the other end that indeed all 10 batches arrived and that all letters
* expected are there. *
*
* @author Josh Long
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class GemfireMessageGroupStoreTests {
@Autowired
private GemfireMessageGroupStoreTestConfiguration.FakeMessageConsumer consumer;
@Autowired
private GemfireMessageGroupStoreTestConfiguration.FakeMessageProducer producer;
private List<String> letters = GemfireMessageGroupStoreTestConfiguration.LIST_OF_STRINGS;
private int maxSize = 10;
@Test
public void testGemfireMessageGroupStore() throws Exception {
producer.afterPropertiesSet();
producer.start();
List<Collection<Object>> batches = consumer.getBatches();
assertEquals(maxSize, batches.size());
for (Collection<Object> collection : batches) {
Assert.assertTrue(letters.size() == collection.size());
for (String c : this.letters) {
Assert.assertTrue(collection.contains(c));
}
for (Object o : collection) {
Assert.assertTrue(o instanceof String);
}
}
producer.stop();
}
}

View File

@@ -16,20 +16,14 @@
package org.springframework.integration.gemfire.store;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import org.junit.Test;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.support.MessageBuilder;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.Region;
import static org.junit.Assert.assertEquals;
/**
* @author Mark Fisher
@@ -42,12 +36,9 @@ public class GemfireMessageStoreTests {
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.afterPropertiesSet();
Cache cache = (Cache)cacheFactoryBean.getObject();
RegionFactoryBean<UUID, Message<?>> regionFactoryBean = new RegionFactoryBean<UUID, Message<?>>();
regionFactoryBean.setName("test.addAndGetMessage");
regionFactoryBean.setCache(cache);
regionFactoryBean.afterPropertiesSet();
Region<UUID, Message<?>> region = regionFactoryBean.getObject();
MessageStore store = new GemfireMessageStore(region);
GemfireMessageStore store = new GemfireMessageStore(cache);
store.afterPropertiesSet();
Message<?> message = MessageBuilder.withPayload("test").build();
store.addMessage(message);
Message<?> retrieved = store.getMessage(message.getHeaders().getId());

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd">
<int:aggregator input-channel="inputChannel" output-channel="outputChannel" message-store="gemfireStore"/>
<int:channel id="outputChannel">
<int:queue/>
</int:channel>
<bean id="gemfireStore" class="org.springframework.integration.gemfire.store.GemfireMessageStore">
<constructor-arg ref="cache"/>
</bean>
<bean id="cache" class="org.springframework.data.gemfire.CacheFactoryBean"/>
</beans>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd">
<int:aggregator input-channel="inputChannel" output-channel="outputChannel" message-store="gemfireStore"/>
<int:channel id="outputChannel">
<int:queue/>
</int:channel>
<bean id="gemfireStore" class="org.springframework.integration.gemfire.store.GemfireMessageStore">
<constructor-arg ref="myCache"/>
</bean>
<bean id="myCache" class="org.springframework.data.gemfire.CacheFactoryBean"/>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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
@@ -59,6 +59,7 @@ import org.springframework.util.StringUtils;
* target database type.
*
* @author Dave Syer
* @author Oleg Zhurakousky
* @since 2.0
*/
@ManagedResource
@@ -80,7 +81,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
private static final String CREATE_MESSAGE = "INSERT into %PREFIX%MESSAGE(MESSAGE_ID, REGION, CREATED_DATE, MESSAGE_BYTES)"
+ " values (?, ?, ?, ?)";
private static final String LIST_MESSAGES_BY_GROUP_KEY = "SELECT MESSAGE_ID, CREATED_DATE, GROUP_KEY, MESSAGE_BYTES, MARKED from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? order by CREATED_DATE";
private static final String LIST_MESSAGES_BY_GROUP_KEY = "SELECT MESSAGE_ID, CREATED_DATE, GROUP_KEY, MESSAGE_BYTES, MARKED, COMPLETE, LAST_RELEASED_SEQUENCE from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? order by CREATED_DATE";
private static final String COUNT_ALL_GROUPS = "SELECT COUNT(GROUP_KEY) from %PREFIX%MESSAGE_GROUP where REGION=?";
@@ -91,13 +92,17 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
private static final String MARK_MESSAGES_IN_GROUP = "UPDATE %PREFIX%MESSAGE_GROUP set UPDATED_DATE=?, MARKED=1 where MARKED=0 and GROUP_KEY=? and REGION=?";
private static final String MARK_MESSAGE_IN_GROUP = "UPDATE %PREFIX%MESSAGE_GROUP set UPDATED_DATE=?, MARKED=1 where MESSAGE_ID=? and MARKED=0 and GROUP_KEY=? and REGION=?";
private static final String COMPLETE_GROUP = "UPDATE %PREFIX%MESSAGE_GROUP set UPDATED_DATE=?, COMPLETE=1 where GROUP_KEY=? and REGION=?";
private static final String UPDATE_LAST_RELEASED_SEQUENCE = "UPDATE %PREFIX%MESSAGE_GROUP set UPDATED_DATE=?, LAST_RELEASED_SEQUENCE=? where GROUP_KEY=? and REGION=?";
private static final String REMOVE_MESSAGE_FROM_GROUP = "DELETE from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? and MESSAGE_ID=?";
private static final String DELETE_MESSAGE_GROUP = "DELETE from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=?";
private static final String CREATE_MESSAGE_IN_GROUP = "INSERT into %PREFIX%MESSAGE_GROUP(MESSAGE_ID, REGION, CREATED_DATE, GROUP_KEY, MARKED, MESSAGE_BYTES)"
+ " values (?, ?, ?, ?, 0, ?)";
private static final String CREATE_MESSAGE_IN_GROUP = "INSERT into %PREFIX%MESSAGE_GROUP(MESSAGE_ID, REGION, CREATED_DATE, GROUP_KEY, MARKED, COMPLETE, LAST_RELEASED_SEQUENCE, MESSAGE_BYTES)"
+ " values (?, ?, ?, ?, 0, 0, 0, ?)";
private static final String LIST_GROUP_KEYS = "SELECT distinct GROUP_KEY as CREATED from %PREFIX%MESSAGE_GROUP where REGION=?";
@@ -332,6 +337,9 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
final List<Message<?>> marked = new ArrayList<Message<?>>();
final List<Message<?>> unmarked = new ArrayList<Message<?>>();
final AtomicReference<Date> date = new AtomicReference<Date>();
final AtomicReference<Boolean> completeFlag = new AtomicReference<Boolean>();
final AtomicReference<Integer> lastReleasedSequenceRef = new AtomicReference<Integer>();
jdbcTemplate.query(getQuery(LIST_MESSAGES_BY_GROUP_KEY), new Object[] { key, region },
new RowCallbackHandler() {
int count = 0;
@@ -346,6 +354,10 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
unmarked.add(message);
}
date.set(rs.getTimestamp("CREATED_DATE"));
completeFlag.set(rs.getInt("COMPLETE") > 0);
lastReleasedSequenceRef.set(rs.getInt("LAST_RELEASED_SEQUENCE"));
}
});
if (marked.isEmpty() && unmarked.isEmpty()) {
@@ -353,7 +365,13 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
}
Assert.state(date.get() != null, "Could not locate created date for groupId=" + groupId);
long timestamp = date.get().getTime();
return new SimpleMessageGroup(unmarked, marked, groupId, timestamp);
boolean complete = completeFlag.get().booleanValue();
SimpleMessageGroup messageGroup = new SimpleMessageGroup(unmarked, marked, groupId, timestamp, complete);
int lastReleasedSequenceNumber = lastReleasedSequenceRef.get();
if (lastReleasedSequenceNumber > 0){
messageGroup.setLastReleasedMessageSequenceNumber(lastReleasedSequenceNumber);
}
return messageGroup;
}
public MessageGroup markMessageGroup(MessageGroup group) {
@@ -421,10 +439,38 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
ps.setString(2, region);
}
});
}
public void completeGroup(Object groupId) {
final long updatedDate = System.currentTimeMillis();
final String groupKey = getKey(groupId);
jdbcTemplate.update(getQuery(COMPLETE_GROUP), new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
logger.debug("Completing MessageGroup: " + groupKey);
ps.setTimestamp(1, new Timestamp(updatedDate));
ps.setString(2, groupKey);
ps.setString(3, region);
}
});
}
public void setLastReleasedSequenceNumberForGroup(Object groupId, final int sequenceNumber) {
Assert.notNull(groupId, "'groupId' must not be null");
final long updatedDate = System.currentTimeMillis();
final String groupKey = getKey(groupId);
jdbcTemplate.update(getQuery(UPDATE_LAST_RELEASED_SEQUENCE), new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
logger.debug("Updating the sequence number of the last released Message in the MessageGroup: " + groupKey);
ps.setTimestamp(1, new Timestamp(updatedDate));
ps.setInt(2, sequenceNumber);
ps.setString(3, groupKey);
ps.setString(4, region);
}
});
}
@Override
public Iterator<MessageGroup> iterator() {
final Iterator<String> iterator = jdbcTemplate.query(getQuery(LIST_GROUP_KEYS), new Object[] { region },
@@ -465,5 +511,4 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
return message;
}
}
}

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES BLOB,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES BLOB,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES LONGVARBINARY,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES LONGVARBINARY,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE DATETIME NOT NULL,
UPDATED_DATE DATETIME DEFAULT NULL,
MESSAGE_BYTES BLOB,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR2(100),
MARKED NUMBER(19,0),
COMPLETE NUMBER(19,0),
LAST_RELEASED_SEQUENCE NUMBER(19,0),
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES BLOB,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP DEFAULT NULL,
MESSAGE_BYTES BYTEA,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE DATETIME NOT NULL,
UPDATED_DATE DATETIME DEFAULT NULL,
MESSAGE_BYTES IMAGE,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION VARCHAR(100),
MARKED BIGINT,
COMPLETE BIGINT,
LAST_RELEASED_SEQUENCE BIGINT,
CREATED_DATE DATETIME NOT NULL,
UPDATED_DATE DATETIME DEFAULT NULL,
MESSAGE_BYTES IMAGE,

View File

@@ -12,6 +12,8 @@ CREATE TABLE INT_MESSAGE_GROUP (
GROUP_KEY CHAR(36) NOT NULL,
REGION ${VARCHAR}(100),
MARKED ${BIGINT},
COMPLETE ${BIGINT},
LAST_RELEASED_SEQUENCE ${BIGINT},
CREATED_DATE ${TIMESTAMP} NOT NULL,
UPDATED_DATE ${TIMESTAMP} DEFAULT NULL,
MESSAGE_BYTES ${BLOB},

View File

@@ -199,6 +199,29 @@ public class JdbcMessageStoreTests {
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
}
@Test
@Transactional
public void testCompleteMessageGroup() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessageToGroup(groupId, message);
messageStore.completeGroup(groupId);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertTrue(group.isComplete());
assertEquals(1, group.size());
}
@Test
@Transactional
public void testUpdateLastReleasedSequence() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessageToGroup(groupId, message);
messageStore.setLastReleasedSequenceNumberForGroup(groupId, 5);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(5, group.getLastReleasedMessageSequenceNumber());
}
@Test
@Transactional

View File

@@ -68,6 +68,12 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
private final static String GROUP_ID_KEY = "_groupId";
private final static String MARKED_KEY = "_marked";
private final static String GROUP_COMPLETE_KEY = "_group_complete";
private final static String LAST_RELEASED_SEQUENCE_NUMBER = "_last_released_sequence";
private final static String GROUP_TIMESTAMP_KEY = "_group_timestamp";
private final static String PAYLOAD_TYPE_KEY = "_payloadType";
@@ -105,7 +111,7 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
public <T> Message<T> addMessage(Message<T> message) {
Assert.notNull(message, "'message' must not be null");
this.template.insert(new MessageWrapper(message, null, false), this.collectionName);
this.template.insert(new MessageWrapper(message), this.collectionName);
return message;
}
@@ -131,6 +137,16 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
List<MessageWrapper> messageWrappers = this.template.find(whereGroupIdIs(groupId), MessageWrapper.class, this.collectionName);
List<Message<?>> unmarkedMessages = new ArrayList<Message<?>>();
List<Message<?>> markedMessages = new ArrayList<Message<?>>();
long timestamp = 0;
int lastReleasedSequenceNumber = 0;
boolean completeGroup = false;
if (messageWrappers.size() > 0){
MessageWrapper messageWrapper = messageWrappers.get(0);
timestamp = messageWrapper.getGroupTimestamp();
completeGroup = messageWrapper.isCompletedGroup();
lastReleasedSequenceNumber = messageWrapper.getLastReleasedSequenceNumber();
}
for (MessageWrapper messageWrapper : messageWrappers) {
if (messageWrapper.isMarked()) {
markedMessages.add(messageWrapper.getMessage());
@@ -139,13 +155,24 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
unmarkedMessages.add(messageWrapper.getMessage());
}
}
return new SimpleMessageGroup(unmarkedMessages, markedMessages, groupId, System.currentTimeMillis());
SimpleMessageGroup messageGroup = new SimpleMessageGroup(unmarkedMessages, markedMessages, groupId, timestamp, completeGroup);
if (lastReleasedSequenceNumber > 0){
messageGroup.setLastReleasedMessageSequenceNumber(lastReleasedSequenceNumber);
}
return messageGroup;
}
public MessageGroup addMessageToGroup(Object groupId, Message<?> message) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(message, "'message' must not be null");
MessageWrapper wrapper = new MessageWrapper(message, groupId, false);
MessageGroup messageGroup = this.getMessageGroup(groupId);
MessageWrapper wrapper = new MessageWrapper(message);
wrapper.setGroupId(groupId);
wrapper.setGroupTimestamp(messageGroup.getTimestamp());
wrapper.setCompletedGroup(messageGroup.isComplete());
wrapper.setLastReleasedSequenceNumber(messageGroup.getLastReleasedMessageSequenceNumber());
this.template.insert(wrapper, this.collectionName);
return this.getMessageGroup(groupId);
}
@@ -181,7 +208,6 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
}
}
@Override
public Iterator<MessageGroup> iterator() {
List<MessageWrapper> groupedMessages = this.template.find(whereGroupIdExists(), MessageWrapper.class, this.collectionName);
Map<Object, MessageGroup> messageGroups = new HashMap<Object, MessageGroup>();
@@ -193,7 +219,18 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
}
return messageGroups.values().iterator();
}
public void completeGroup(Object groupId) {
Update update = Update.update(GROUP_COMPLETE_KEY, true);
Query q = whereGroupIdIs(groupId);
this.template.updateFirst(q, update, this.collectionName);
}
public void setLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) {
Update update = Update.update(LAST_RELEASED_SEQUENCE_NUMBER, sequenceNumber);
Query q = whereGroupIdIs(groupId);
this.template.updateFirst(q, update, this.collectionName);
}
/*
* Common Queries
@@ -236,11 +273,17 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
Message<?> message = null;
Object groupId = null;
boolean marked = false;
boolean groupComplete = false;
long groupTimestamp = 0;
int lastReleasedSequenceNumber = 0;
if (source instanceof MessageWrapper) {
MessageWrapper wrapper = (MessageWrapper) source;
message = wrapper.getMessage();
groupId = wrapper.getGroupId();
marked = wrapper.isMarked();
groupComplete = wrapper.isCompletedGroup();
lastReleasedSequenceNumber = wrapper.getLastReleasedSequenceNumber();
groupTimestamp = wrapper.getGroupTimestamp();
}
else {
Class<?> sourceType = (source != null) ? source.getClass() : null;
@@ -249,6 +292,9 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
target.put(PAYLOAD_TYPE_KEY, message.getPayload().getClass().getName());
if (groupId != null) {
target.put(GROUP_ID_KEY, groupId);
target.put(GROUP_COMPLETE_KEY, groupComplete);
target.put(LAST_RELEASED_SEQUENCE_NUMBER, lastReleasedSequenceNumber);
target.put(GROUP_TIMESTAMP_KEY, groupTimestamp);
}
if (marked) {
target.put(MARKED_KEY, marked);
@@ -280,7 +326,26 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
// using reflection to set ID and TIMESTAMP since they are immutable through MessageHeaders
innerMap.put(MessageHeaders.ID, UUID.fromString((String) headers.get(MessageHeaders.ID)));
innerMap.put(MessageHeaders.TIMESTAMP, headers.get(MessageHeaders.TIMESTAMP));
MessageWrapper wrapper = new MessageWrapper(message, source.get(GROUP_ID_KEY), source.get(MARKED_KEY) != null);
Long groupTimestamp = (Long)source.get(GROUP_TIMESTAMP_KEY);
Integer lastReleasedSequenceNumber = (Integer)source.get(LAST_RELEASED_SEQUENCE_NUMBER);
Boolean completeGroup = (Boolean)source.get(GROUP_COMPLETE_KEY);
MessageWrapper wrapper = new MessageWrapper(message);
if (source.containsField(GROUP_ID_KEY)){
wrapper.setGroupId(source.get(GROUP_ID_KEY));
}
if (groupTimestamp != null){
wrapper.setGroupTimestamp(groupTimestamp);
}
if (lastReleasedSequenceNumber != null){
wrapper.setLastReleasedSequenceNumber(lastReleasedSequenceNumber);
}
wrapper.setMarked(source.get(MARKED_KEY) != null);
wrapper.setCompletedGroup(completeGroup.booleanValue());
return (S) wrapper;
}
return null;
@@ -307,16 +372,32 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
*/
private static final class MessageWrapper {
private final Object groupId;
private volatile Object groupId;
private final boolean marked;
private volatile boolean marked;
private final Message<?> message;
private volatile long groupTimestamp;
private volatile int lastReleasedSequenceNumber;
public MessageWrapper(Message<?> message, Object groupId, boolean marked) {
this.marked = marked;
private volatile boolean completedGroup;
public MessageWrapper(Message<?> message) {
this.message = message;
this.groupId = groupId;
}
public int getLastReleasedSequenceNumber() {
return lastReleasedSequenceNumber;
}
public long getGroupTimestamp() {
return groupTimestamp;
}
public boolean isCompletedGroup() {
return completedGroup;
}
public Object getGroupId() {
@@ -330,6 +411,25 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore implements Me
public Message<?> getMessage() {
return message;
}
}
public void setGroupId(Object groupId) {
this.groupId = groupId;
}
public void setMarked(boolean marked) {
this.marked = marked;
}
public void setGroupTimestamp(long groupTimestamp) {
this.groupTimestamp = groupTimestamp;
}
public void setLastReleasedSequenceNumber(int lastReleasedSequenceNumber) {
this.lastReleasedSequenceNumber = lastReleasedSequenceNumber;
}
public void setCompletedGroup(boolean completedGroup) {
this.completedGroup = completedGroup;
}
}
}

View File

@@ -15,15 +15,8 @@
*/
package org.springframework.integration.mongodb.store;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import junit.framework.AssertionFailedError;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -128,6 +121,34 @@ public class MongoDbMessageGroupStoreTests extends MongoDbAvailableTests {
}
@Test
@MongoDbAvailable
public void testCompleteMessageGroup() throws Exception{
MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
store.addMessageToGroup(messageGroup.getGroupId(), message);
store.completeGroup(messageGroup.getGroupId());
messageGroup = store.getMessageGroup(1);
assertTrue(messageGroup.isComplete());
}
@Test
@MongoDbAvailable
public void testLastReleasedSequenceNumber() throws Exception{
MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
MongoDbMessageStore store = new MongoDbMessageStore(mongoDbFactory);
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
store.addMessageToGroup(messageGroup.getGroupId(), message);
store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
messageGroup = store.getMessageGroup(1);
assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
}
@Test
@MongoDbAvailable
public void testRemoveMessageFromTheGroup() throws Exception{

View File

@@ -1,5 +1,5 @@
# minimal config
daemonize yes
#daemonize yes
bind 127.0.0.1
loglevel notice
port 7379

View File

@@ -16,278 +16,82 @@
package org.springframework.integration.redis.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.Message;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.AbstractKeyValueMessageStore;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.MessageStoreException;
import org.springframework.integration.store.SimpleMessageGroup;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.util.Assert;
/**
* An implementation of both the {@link MessageStore} and {@link MessageGroupStore}
* strategies that relies upon Redis for persistence.
* Redis implementation of the key/value style {@link MessageStore} and {@link MessageGroupStore}
*
* @author Oleg Zhurakousky
* @since 2.1
*/
public class RedisMessageStore extends AbstractMessageGroupStore implements MessageStore {
public class RedisMessageStore extends AbstractKeyValueMessageStore {
private static final String MESSAGE_GROUPS_KEY = "MESSAGE_GROUPS";
private static final String MARKED_PREFIX = "MARKED_";
private static final String UNMARKED_PREFIX = "UNMARKED_";
private final RedisTemplate<String, Object> redisTemplate;
private final RedisTemplate<Object, Object> redisTemplate;
public RedisMessageStore(RedisConnectionFactory connectionFactory) {
this.redisTemplate = new RedisTemplate<String, Object>();
this.redisTemplate = new RedisTemplate<Object, Object>();
this.redisTemplate.setConnectionFactory(connectionFactory);
this.redisTemplate.setKeySerializer(new StringRedisSerializer());
this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
}
public void setValueSerializer(RedisSerializer<?> valueSerializer) {
Assert.notNull(valueSerializer, "'valueSerializer' must not be null");
this.redisTemplate.setValueSerializer(valueSerializer);
}
public Message<?> getMessage(final UUID id) {
@Override
protected Object doRetrieve(Object id){
Assert.notNull(id, "'id' must not be null");
if (this.redisTemplate.hasKey(id.toString())) {
BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(id.toString());
Object result = ops.get();
Assert.isInstanceOf(Message.class, result, "Return value is not an instace of Message");
return (Message<?>) result;
}
return null;
BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps(id);
return ops.get();
}
@SuppressWarnings("unchecked")
public <T> Message<T> addMessage(Message<T> message) {
Assert.notNull(message, "'message' must not be null");
BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(message.getHeaders().getId().toString());
try {
ops.set(message);
}
catch (SerializationException e) {
throw new MessageStoreException(message, "If relying on the default RedisSerializer (JdkSerializationRedisSerializer) " +
"the Message must be Serializable. Either make it Serializable or provide your own implementation of " +
"RedisSerializer via 'setValueSerializer(..)'", e);
}
Object result = ops.get();
Assert.isInstanceOf(Message.class, result, "Return value is not an instace of Message");
return (Message<T>) result;
}
public Message<?> removeMessage(UUID id) {
Assert.notNull(id, "'id' must not be null");
Message<?> message = this.getMessage(id);
if (message != null) {
this.redisTemplate.delete(id.toString());
}
return message;
}
@ManagedAttribute
public long getMessageCount() {
return redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.dbSize();
}
});
}
// MESSAGE GROUP methods
/**
* Will create a new instance of SimpleMessageGroup initializing it with
* data collected from the Redis Message Store.
*/
public MessageGroup getMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
long timestamp = System.currentTimeMillis();
Collection<Message<?>> unmarkedMessages = this.buildMessageList(this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId));
Collection<Message<?>> markedMessages = this.buildMessageList(this.redisTemplate.boundListOps(MARKED_PREFIX + groupId));
this.doCreateMessageGroupIfNecessary(groupId);
return new SimpleMessageGroup(unmarkedMessages, markedMessages, groupId, timestamp);
}
/**
* Add a Message to the group with the provided group ID.
*/
public MessageGroup addMessageToGroup(Object groupId, Message<?> message) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(message, "'message' must not be null");
synchronized (groupId) {
this.doAddMessageToGroup(message, groupId);
this.addMessage(message);
return this.getMessageGroup(groupId);
}
}
/**
* Mark all messages in the provided group.
*/
public MessageGroup markMessageGroup(MessageGroup group) {
Assert.notNull(group, "'group' must not be null");
Object groupId = group.getGroupId();
synchronized (groupId) {
this.doMarkMessageGroup(groupId);
return this.getMessageGroup(groupId);
}
}
/**
* Remove a Message from the group with the provided group ID.
*/
public MessageGroup removeMessageFromGroup(Object groupId, Message<?> messageToRemove) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(messageToRemove, "'messageToRemove' must not be null");
UUID messageId = messageToRemove.getHeaders().getId();
synchronized (groupId) {
this.doRemoveMessageFromGroup(groupId, messageId);
this.removeMessage(messageId);
return this.getMessageGroup(groupId);
}
}
/**
* Mark the given Message within the group corresponding to the provided group ID.
*/
public MessageGroup markMessageFromGroup(Object groupId, Message<?> messageToMark) {
Assert.notNull(groupId, "'groupId' must not be null");
Assert.notNull(messageToMark, "'messageToMark' must not be null");
String messageIdAsString = messageToMark.getHeaders().getId().toString();
synchronized (groupId) {
this.doMarkMessageFromGroup(messageIdAsString, groupId);
return this.getMessageGroup(groupId);
}
}
/**
* Remove the MessageGroup with the provided group ID.
*/
public void removeMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
synchronized (groupId) {
this.doRemoveMessageGroup(groupId);
}
}
@Override
public Iterator<MessageGroup> iterator() {
BoundSetOperations<String, Object> mGroupsOps = this.redisTemplate.boundSetOps(MESSAGE_GROUPS_KEY);
Set<Object> messageGroupIds = mGroupsOps.members();
List<MessageGroup> messageGroups = new ArrayList<MessageGroup>();
for (Object messageGroupId : messageGroupIds) {
messageGroups.add(this.getMessageGroup(messageGroupId));
protected void doStore(Object id, Object objectToStore) {
Assert.notNull(id, "'id' must not be null");
Assert.notNull(objectToStore, "'objectToStore' must not be null");
BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps(id);
try {
ops.set(objectToStore);
}
return messageGroups.iterator();
}
private Collection<Message<?>> buildMessageList(BoundListOperations<String, Object> messageGroupOps) {
List<Message<?>> messages = new LinkedList<Message<?>>();
if (messageGroupOps.size() == 0) {
return Collections.emptyList();
}
List<Object> messageIds = messageGroupOps.range(0, messageGroupOps.size() - 1);
for (Object messageId : messageIds) {
Message<?> message = this.getMessage(UUID.fromString(messageId.toString()));
if (message != null) {
messages.add((Message<?>) message);
}
}
return messages;
}
/* candidates for future abstract methods */
private void doCreateMessageGroupIfNecessary(Object groupId) {
BoundSetOperations<String, Object> messageGroupsOps = this.redisTemplate.boundSetOps(MESSAGE_GROUPS_KEY);
if (!messageGroupsOps.members().contains(groupId)) {
messageGroupsOps.add(groupId);
catch (SerializationException e) {
throw new IllegalArgumentException("If relying on the default RedisSerializer (JdkSerializationRedisSerializer) " +
"the Object must be Serializable. Either make it Serializable or provide your own implementation of " +
"RedisSerializer via 'setValueSerializer(..)'", e);
}
}
private void doAddMessageToGroup(Message<?> message, Object groupId) {
String messageId = message.getHeaders().getId().toString();
BoundListOperations<String, Object> unmarkedOps = this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId);
unmarkedOps.rightPush(messageId);
@Override
protected Object doRemove(Object id) {
Assert.notNull(id, "'id' must not be null");
Object removedObject = this.doRetrieve(id);
if (removedObject != null){
redisTemplate.delete(id);
}
return removedObject;
}
private void doMarkMessageGroup(Object groupId) {
BoundListOperations<String, Object> unmarkedOps = this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId);
unmarkedOps.rename(MARKED_PREFIX + groupId);
}
private void doRemoveMessageFromGroup(Object groupId, UUID messageId) {
BoundListOperations<String, Object> unmarkedOps = this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId);
BoundListOperations<String, Object> markedOps = this.redisTemplate.boundListOps(MARKED_PREFIX + groupId);
unmarkedOps.remove(0, messageId.toString());
markedOps.remove(0, messageId.toString());
@Override
protected Collection<?> doListKeys(String keyPattern) {
Assert.hasText(keyPattern, "'keyPattern' must not be empty");
Set<Object> keys = redisTemplate.keys(keyPattern);
return keys;
}
private void doMarkMessageFromGroup(String messageIdAsString, Object groupId) {
BoundListOperations<String, Object> unmarkedOps = this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId);
if (unmarkedOps.size() > 0) {
List<Object> messageIds = unmarkedOps.range(0, unmarkedOps.size() - 1);
int objectIndex = messageIds.indexOf(messageIdAsString);
if (objectIndex > -1) {
BoundListOperations<String, Object> markedOps = this.redisTemplate.boundListOps(MARKED_PREFIX + groupId);
markedOps.rightPush(messageIdAsString);
unmarkedOps.remove(0, messageIdAsString);
}
}
}
private void doRemoveMessageGroup(Object groupId) {
BoundListOperations<String, Object> unmarkedOps = this.redisTemplate.boundListOps(UNMARKED_PREFIX + groupId);
if (unmarkedOps.size() > 0) {
List<Object> messageIds = unmarkedOps.range(0, unmarkedOps.size() - 1);
for (Object messageId : messageIds) {
this.removeMessage(UUID.fromString(messageId.toString()));
}
this.redisTemplate.delete(UNMARKED_PREFIX + groupId);
}
BoundListOperations<String, Object> markedOps = this.redisTemplate.boundListOps(MARKED_PREFIX + groupId);
if (markedOps.size() > 0) {
List<Object> messageIds = markedOps.range(0, markedOps.size() - 1);
for (Object messageId : messageIds) {
this.removeMessage(UUID.fromString(messageId.toString()));
}
this.redisTemplate.delete(MARKED_PREFIX + groupId);
}
BoundSetOperations<String, Object> messageGroupsOps = this.redisTemplate.boundSetOps(MESSAGE_GROUPS_KEY);
messageGroupsOps.remove(groupId);
}
}

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import junit.framework.AssertionFailedError;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
@@ -107,6 +108,34 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
assertEquals(0, messageGroup.size());
}
@Test
@RedisAvailable
public void testCompleteMessageGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message);
store.completeGroup(messageGroup.getGroupId());
messageGroup = store.getMessageGroup(1);
assertTrue(messageGroup.isComplete());
}
@Test
@RedisAvailable
public void testLastReleasedSequenceNumber() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message);
store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
messageGroup = store.getMessageGroup(1);
assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
}
@Test
@RedisAvailable
public void testRemoveMessageFromTheGroup() throws Exception{
@@ -128,7 +157,24 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
@RedisAvailable
public void testRemoveNonExistingMessageFromTheGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"));
store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@Test
@RedisAvailable
public void testRemoveNonExistingMessageFromNonExistingTheGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@Test
@@ -183,6 +229,8 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
assertEquals(1, messageGroup.getMarked().size());
}
@Test
@RedisAvailable
public void testMultipleInstancesOfGroupStore() throws Exception{
@@ -239,7 +287,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
}
@Test
@RedisAvailable
@RedisAvailable @Ignore
public void testConcurrentModifications() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
final RedisMessageStore store1 = new RedisMessageStore(jcf);

View File

@@ -24,7 +24,6 @@ import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.store.MessageStoreException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -80,7 +79,7 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
assertEquals("Barak Obama", storedMessage.getPayload().getName());
}
@Test(expected=MessageStoreException.class)
@Test(expected=IllegalArgumentException.class)
@RedisAvailable
public void testAddNonSerializableObjectMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();