Merging INT-2152 into master
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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-*
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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/>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# minimal config
|
||||
daemonize yes
|
||||
#daemonize yes
|
||||
bind 127.0.0.1
|
||||
loglevel notice
|
||||
port 7379
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user