Merge branch 'work'

This commit is contained in:
David Syer
2010-05-03 08:58:21 +00:00
parent 9850551ddd
commit 8c2c2ca416
35 changed files with 290 additions and 222 deletions

View File

@@ -23,6 +23,7 @@ import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import java.util.*;

View File

@@ -13,7 +13,6 @@
package org.springframework.integration.aggregator;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -28,6 +27,8 @@ import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.scheduling.TaskScheduler;
@@ -58,7 +59,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
public static final long DEFAULT_TIMEOUT = 60000L;
private final MessageStore store;
private final MessageGroupStore store;
private final MessageGroupProcessor outputProcessor;
@@ -76,7 +77,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageStore store,
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
Assert.notNull(store);
Assert.notNull(processor);
@@ -88,7 +89,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageStore store) {
public CorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) {
this(processor, store, null, null);
}
@@ -160,8 +161,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
Object lock = getLock(correlationKey);
synchronized (lock) {
Collection<Message<?>> messages = store.list(correlationKey);
MessageGroup group = new MessageGroup(messages, correlationKey);
MessageGroup group = store.getMessageGroup(correlationKey);
if (group.add(message)) {
@@ -209,12 +209,11 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
Object lock = getLock(correlationKey);
synchronized (lock) {
Collection<Message<?>> all = store.list(correlationKey);
MessageGroup group = new MessageGroup(all, correlationKey);
if (all.size() > 0) {
MessageGroup group = store.getMessageGroup(correlationKey);
if (group.size() > 0) {
// last chance for normal completion
if (releaseStrategy.canRelease(group)) {
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator().next(),
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(group.getOne(),
this.outputChannel));
remove(group);
}
@@ -224,15 +223,15 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
logger.info("Processing partially complete messages for key [" + correlationKey + "] to: "
+ outputChannel);
}
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(all.iterator()
.next(), this.outputChannel));
outputProcessor.processAndSend(group, channelTemplate, resolveReplyChannel(group.getOne(),
this.outputChannel));
}
else {
if (logger.isInfoEnabled()) {
logger.info("Discarding partially complete messages for key [" + correlationKey + "] to: "
+ discardChannel);
}
for (Message<?> message : all) {
for (Message<?> message : group.getUnmarked()) {
discardChannel.send(message);
}
}
@@ -250,19 +249,17 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
}
private void mark(MessageGroup group) {
for (Message<?> message : group.getUnmarked()) {
store.mark(group.getCorrelationKey(), message.getHeaders().getId());
}
store.mark(group);
}
private void remove(MessageGroup group) {
Object correlationKey = group.getCorrelationKey();
store.deleteAll(correlationKey);
store.deleteMessageGroup(correlationKey);
locks.remove(correlationKey);
}
private void store(Object correlationKey, Message<?> message) {
store.put(correlationKey, message);
store.addMessageToGroup(correlationKey, message);
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
/**

View File

@@ -13,6 +13,8 @@
package org.springframework.integration.aggregator;
import org.springframework.integration.store.MessageGroup;
/**
* A {@link ReleaseStrategy} that releases only the first <code>n</code> messages, where <code>n</code> is a threshold.
*

View File

@@ -2,6 +2,7 @@ package org.springframework.integration.aggregator;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* A processor for <i>correlated</i> groups of messages. When a message group is <i>complete</i> it is passed to the

View File

@@ -29,6 +29,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

View File

@@ -3,6 +3,7 @@ package org.springframework.integration.aggregator;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* This implementation of MessageGroupProcessor will forward all messages inside the group to the given output channel.

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.aggregator;
import org.springframework.integration.store.MessageGroup;
/**

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.aggregator;
import java.lang.reflect.Method;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
/**

View File

@@ -25,6 +25,7 @@ import java.util.List;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* This class implements all the strategy interfaces needed for a default

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.aggregator;
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'.

View File

@@ -0,0 +1,43 @@
package org.springframework.integration.store;
import java.util.Collection;
import org.springframework.integration.core.Message;
public interface MessageGroup {
/**
* Add a message to the internal list. This is needed to avoid hitting the
* underlying store or copying the internal list. Use with care.
*/
boolean add(Message<?> message);
/**
* @return internal message list, modification is allowed, but not
* recommended
*/
Collection<Message<?>> getUnmarked();
/**
* @return internal message list, modification is allowed, but not
* recommended
*/
Collection<Message<?>> getMarked();
/**
* @return the correlation key that links these messages together according
* to a particular CorrelationStrategy
*/
Object getCorrelationKey();
boolean isComplete();
int getSequenceSize();
int size();
void mark();
public Message<?> getOne();
}

View File

@@ -0,0 +1,66 @@
/*
* 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.store;
import java.util.Collection;
import org.springframework.integration.core.Message;
/**
* Interface for storage operations on groups of messages linked by a correlation key.
*
* @author Dave Syer
*
* @since 2.0
*
*/
public interface MessageGroupStore {
/**
* Return all Messages currently in the MessageStore that were stored using
* {@link #addMessageToGroup(Object, Collection)} with this correlation id.
*
* @return a group of messages, empty if none exists for this key
*
* @see org.springframework.integration.core.MessageHeaders#getCorrelationId()
*/
MessageGroup getMessageGroup(Object correlationId);
/**
* Store a message with an association to a correlation id. This can be used to group messages together instead of
* storing them just under their id.
*
* @param correlationId the correlation id to store the message under
* @param message a message
*/
void addMessageToGroup(Object correlationId, Message<?> message);
/**
* Persist the mark on all the messages from the group. The group is modified in the process as all its unmarked
* messages become marked.
*
* @param group a MessageGroup with no unmarked messages
*/
void mark(MessageGroup group);
/**
* Delete all the messages from the association with this correlation id.
*
* @param correlationId the correlation id to remove
*/
void deleteMessageGroup(Object correlationId);
}

View File

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

View File

@@ -11,18 +11,18 @@
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.aggregator;
package org.springframework.integration.store;
import java.util.Collection;
import java.util.HashSet;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageStore;
/**
* Represents a mutable group of correlated messages that is bound to a certain {@link MessageStore} and correlation
* key. The group will grow during its lifetime, when messages are <code>add</code>ed to it. <strong>This is not thread
* safe and should not be used for long running aggregations</strong>.
* Represents a mutable group of correlated messages that is bound to a certain
* {@link MessageStore} and correlation key. The group will grow during its
* lifetime, when messages are <code>add</code>ed to it. <strong>This is not
* thread safe and should not be used for long running aggregations</strong>.
*
* @author Iwein Fuld
* @author Oleg Zhurakousky
@@ -30,59 +30,48 @@ import org.springframework.integration.store.MessageStore;
*
* @since 2.0
*/
public class MessageGroup {
public class SimpleMessageGroup implements MessageGroup {
private final Object correlationKey;
private final Collection<Message<?>> marked = new HashSet<Message<?>>();
public final Collection<Message<?>> marked = new HashSet<Message<?>>();
private final Collection<Message<?>> unmarked = new HashSet<Message<?>>();
public final Collection<Message<?>> unmarked = new HashSet<Message<?>>();
public MessageGroup(Object correlationKey) {
public SimpleMessageGroup(Object correlationKey) {
this.correlationKey = correlationKey;
}
public MessageGroup(Collection<? extends Message<?>> originalMessages, Object correlationKey) {
public SimpleMessageGroup(MessageGroup template) {
this.correlationKey = template.getCorrelationKey();
this.marked.addAll(template.getMarked());
this.unmarked.addAll(template.getUnmarked());
}
public SimpleMessageGroup(Collection<? extends Message<?>> originalMessages,
Object correlationKey) {
this(correlationKey);
for (Message<?> message : originalMessages) {
add(message);
}
}
/**
* Add a message to the internal list. This is needed to avoid hitting the underlying store or copying the internal
* list. Use with care.
*/
public boolean add(Message<?> message) {
if (isMember(message)) {
return false;
}
if (message.getHeaders().containsKey(MessageStore.PROCESSED)
&& message.getHeaders().get(MessageStore.PROCESSED, Boolean.class)) {
this.marked.add(message);
} else {
this.unmarked.add(message);
}
this.unmarked.add(message);
return true;
}
/**
* @return internal message list, modification is allowed, but not recommended
*/
public Collection<Message<?>> getUnmarked() {
return unmarked;
}
/**
* @return internal message list, modification is allowed, but not recommended
*/
public Collection<Message<?>> getMarked() {
return marked;
}
/**
* @return the correlation key that links these messages together according to a particular CorrelationStrategy
*/
public Object getCorrelationKey() {
return correlationKey;
}
@@ -103,26 +92,35 @@ public class MessageGroup {
return getOne().getHeaders().getSequenceSize();
}
private Message<?> getOne() {
return unmarked.isEmpty() ? (marked.isEmpty() ? null : marked.iterator().next()) : unmarked.iterator().next();
public void mark() {
marked.addAll(unmarked);
unmarked.clear();
}
public int size() {
return marked.size() + unmarked.size();
}
public Message<?> getOne() {
return unmarked.isEmpty() ? (marked.isEmpty() ? null : marked
.iterator().next()) : unmarked.iterator().next();
}
/**
* 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.
* This method determines whether messages have been added to this group
* that supersede the given message based on its sequence id. This can be
* helpful to avoid ending up with sequences larger than their required
* sequence size or sequences that are missing certain sequence numbers.
*/
private boolean isMember(Message<?> message) {
if (size() == 0) {
return false;
}
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
Integer messageSequenceNumber = message.getHeaders()
.getSequenceNumber();
if (messageSequenceNumber != null && messageSequenceNumber > 0) {
Integer messageSequenceSize = message.getHeaders().getSequenceSize();
Integer messageSequenceSize = message.getHeaders()
.getSequenceSize();
if (!messageSequenceSize.equals(getSequenceSize())
|| containsSequenceNumber(unmarked, messageSequenceNumber)
|| containsSequenceNumber(marked, messageSequenceNumber)) {
@@ -132,9 +130,11 @@ public class MessageGroup {
return false;
}
private boolean containsSequenceNumber(Collection<Message<?>> messages, Integer messageSequenceNumber) {
private boolean containsSequenceNumber(Collection<Message<?>> messages,
Integer messageSequenceNumber) {
for (Message<?> member : messages) {
Integer memberSequenceNumber = member.getHeaders().getSequenceNumber();
Integer memberSequenceNumber = member.getHeaders()
.getSequenceNumber();
if (messageSequenceNumber.equals(memberSequenceNumber)) {
return true;
}

View File

@@ -16,43 +16,40 @@
package org.springframework.integration.store;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.util.UpperBound;
import org.springframework.util.Assert;
/**
* Map-based implementation of {@link MessageStore} that enforces a maximum
* capacity.
* Map-based implementation of {@link MessageStore} and {@link MessageGroupStore}. Enforces a maximum capacity for the
* store.
*
* @author Iwein Fuld
* @author Mark Fisher
* @author Dave Syer
*
* @since 2.0
*/
public class SimpleMessageStore implements MessageStore {
public class SimpleMessageStore implements MessageStore, MessageGroupStore {
private final ConcurrentMap<UUID, Message<?>> idToMessage;
private final ConcurrentMap<Object, Collection<Message<?>>> correlationToMessage;
private final ConcurrentMap<Object, SimpleMessageGroup> correlationToMessageGroup;
private final UpperBound upperBound;
/**
* Creates a SimpleMessageStore with a maximum size limited by the given
* capacity, or unlimited size if the given capacity is less than 1.
* Creates a SimpleMessageStore with a maximum size limited by the given capacity, or unlimited size if the given
* capacity is less than 1.
*/
public SimpleMessageStore(int capacity) {
this.idToMessage = new ConcurrentHashMap<UUID, Message<?>>();
this.correlationToMessage = new ConcurrentHashMap<Object, Collection<Message<?>>>();
this.correlationToMessageGroup = new ConcurrentHashMap<Object, SimpleMessageGroup>();
this.upperBound = new UpperBound(capacity);
}
@@ -64,23 +61,23 @@ public class SimpleMessageStore implements MessageStore {
}
@SuppressWarnings("unchecked")
public <T> Message<T> put(Message<T> message) {
public <T> Message<T> addMessage(Message<T> message) {
if (!upperBound.tryAcquire(0)) {
throw new MessagingException(this.getClass().getSimpleName()
+ " was out of capacity at, try constructing it with a larger capacity.");
}
Object correlationId = message.getHeaders().getCorrelationId();
if (correlationId!=null) {
getMessagesInternal(correlationId).add(message);
if (correlationId != null) {
getMessageGroupInternal(correlationId).add(message);
}
return (Message<T>) this.idToMessage.put(message.getHeaders().getId(), message);
}
public Message<?> get(UUID key) {
public Message<?> getMessage(UUID key) {
return (key != null) ? this.idToMessage.get(key) : null;
}
public Message<?> delete(UUID key) {
public Message<?> removeMessage(UUID key) {
if (key != null) {
upperBound.release();
return this.idToMessage.remove(key);
@@ -93,46 +90,35 @@ public class SimpleMessageStore implements MessageStore {
return this.idToMessage.size();
}
public Collection<Message<?>> list(Object correlationId) {
public MessageGroup getMessageGroup(Object correlationId) {
Assert.notNull(correlationId, "'correlationKey' must not be null");
Collection<Message<?>> collection = correlationToMessage.get(correlationId);
if (collection==null) {
return Collections.emptySet();
MessageGroup collection = correlationToMessageGroup.get(correlationId);
if (collection == null) {
return new SimpleMessageGroup(correlationId);
}
return new HashSet<Message<?>>(collection);
return new SimpleMessageGroup(collection);
}
public void put(Object correlationId, Message<?> message) {
getMessagesInternal(correlationId).add(message);
public void addMessageToGroup(Object correlationId, Message<?> message) {
getMessageGroupInternal(correlationId).add(message);
}
public void mark(MessageGroup group) {
Object correlationId = group.getCorrelationKey();
MessageGroup internal = getMessageGroupInternal(correlationId);
internal.mark();
group.mark();
}
public Message<?> mark(Object correlationId, UUID messageId) {
if (!correlationToMessage.containsKey(correlationId)) {
return null;
}
Collection<Message<?>> messages = getMessagesInternal(correlationId);
Message<?> result = null;
for (Iterator<Message<?>> iterator = messages.iterator(); iterator.hasNext();) {
Message<?> message = (Message<?>) iterator.next();
if (message.getHeaders().getId().equals(messageId)) {
iterator.remove();
result = MessageBuilder.fromMessage(message).setHeader(PROCESSED, true).build();
}
}
messages.add(result);
return result;
public void deleteMessageGroup(Object correlationId) {
correlationToMessageGroup.remove(correlationId);
}
public void deleteAll(Object correlationId) {
correlationToMessage.remove(correlationId);
}
private Collection<Message<?>> getMessagesInternal(Object correlationId) {
if (!correlationToMessage.containsKey(correlationId)) {
correlationToMessage.putIfAbsent(correlationId, new HashSet<Message<?>>());
private MessageGroup getMessageGroupInternal(Object correlationId) {
if (!correlationToMessageGroup.containsKey(correlationId)) {
correlationToMessageGroup.putIfAbsent(correlationId, new SimpleMessageGroup(correlationId));
}
Collection<Message<?>> collection = correlationToMessage.get(correlationId);
return collection;
return correlationToMessageGroup.get(correlationId);
}
}

View File

@@ -59,13 +59,13 @@ public class ClaimCheckTransformer extends AbstractTransformer {
private Message<?> retrieveMessage(UUID id) {
Message<?> result = this.messageStore.get(id);
Message<?> result = this.messageStore.getMessage(id);
Assert.notNull(result, "unable to locate Message for claim check ID: " + id);
return result;
}
private void storeMessage(Message<?> message) {
this.messageStore.put(message);
this.messageStore.addMessage(message);
}
}