parent issue for INT-2134, INT-2135, INT-2142, INT-2155, INT-2158
This commit is contained in:
Oleg Zhurakousky
2011-09-20 15:48:07 -04:00
committed by Mark Fisher
parent 86e59c8d3f
commit 946b9e2b82
70 changed files with 2050 additions and 1468 deletions

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.gemfire.store;
import org.springframework.integration.Message;
import com.gemstone.gemfire.cache.Region;
/**
* Provides GemFire specific support as a backing key-value based {@link org.springframework.integration.store.MessageGroupStore}.
* Currently, this support is limited to explicitly depending on GemFire {@link com.gemstone.gemfire.cache.Region}s, but
* might conceptually also support optimized key traversal (using a {@link com.gemstone.gemfire.cache.query.Query}, for example).
*
* @author Josh Long
* @since 2.1
* @see {@link org.springframework.integration.gemfire.store.KeyValueMessageGroupStore}
*/
public class GemfireMessageGroupStore extends KeyValueMessageGroupStore {
public GemfireMessageGroupStore(
Region<Object, KeyValueMessageGroup> groupIdToMessageGroup,
Region<String, Message<?>> marked,
Region<String, Message<?>> unmarked ) {
super(groupIdToMessageGroup, marked, unmarked);
}
}

View File

@@ -16,44 +16,73 @@
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.integration.store.MessageStore;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.integration.store.AbstractKeyValueMessageStore;
import org.springframework.integration.store.MessageGroupStore;
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{
private final Region<UUID, Message<?>> region;
public GemfireMessageStore(Region<UUID, Message<?>> region) {
Assert.notNull(region, "region must not be null");
this.region = region;
private final Region<Object, Object> messageStoreRegion;
public GemfireMessageStore(Cache cache) {
Assert.notNull(cache, "'cache' must not be null");
try {
RegionFactoryBean<Object, Object> messageRegionFactoryBean = new RegionFactoryBean<Object, Object>();
messageRegionFactoryBean.setBeanName("messageStoreRegion");
messageRegionFactoryBean.setCache(cache);
messageRegionFactoryBean.afterPropertiesSet();
this.messageStoreRegion = messageRegionFactoryBean.getObject();
} catch (Exception e) {
throw new IllegalArgumentException("Failed to initialize Gemfire Region");
}
}
public Message<?> getMessage(UUID id) {
return this.region.get(id);
@Override
protected Object doRetrieve(Object id) {
Assert.notNull(id, "'id' must not be null");
return this.messageStoreRegion.get(id);
}
public <T> Message<T> addMessage(Message<T> message) {
this.region.put(message.getHeaders().getId(), message);
return message;
@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);
}
public Message<?> removeMessage(UUID id) {
return this.region.remove(id);
@Override
protected Object doRemove(Object id) {
Assert.notNull(id, "'id' must not be null");
return this.messageStoreRegion.remove(id);
}
@ManagedAttribute
public long getMessageCount() {
return this.region.size();
@Override
protected Collection<?> doListKeys(String keyPattern) {
Assert.hasText(keyPattern, "'keyPattern' must not be empty");
Collection<Object> keys = this.messageStoreRegion.keySet();
List<Object> keyList = new ArrayList<Object>();
for (Object key : keys) {
String keyValue = key.toString();
if (PatternMatchUtils.simpleMatch(keyPattern, keyValue)){
keyList.add(keyValue);
}
}
return keyList;
}
}

View File

@@ -1,340 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.gemfire.store;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
/**
* A {@link org.springframework.integration.store.MessageGroup} that manipulates keys and values to provide persistence.
* Responsible for managing one group's messages as a {@link org.springframework.integration.store.MessageGroup}.
*
* @author Josh Long
* @since 2.1
*/
@SuppressWarnings("serial")
public class KeyValueMessageGroup implements MessageGroup, Serializable {
/**
* this should not be persisted. it's passed in through {@link KeyValueMessageGroupStore}, which has the reference to the {@link java.util.concurrent.ConcurrentMap} instance that should be set here
*/
private transient Map<String, Message<?>> marked;
/**
* this should not be persisted. it's passed in through {@link KeyValueMessageGroupStore}, which has the reference to the {@link java.util.concurrent.ConcurrentMap} instance that should be set here
*/
private transient Map<String, Message<?>> unmarked;
/**
* the #groupId is the unique ID to associate this aggregation of {@link org.springframework.integration.Message}s
*/
private Object groupId;
/**
* passed in through the {@link org.springframework.integration.store.MessageGroupStore}
*/
private long timestamp;
/**
* default javabean ctor (so that this object plays well as a {@link java.io.Serializable} object)
*/
public KeyValueMessageGroup() {
}
public KeyValueMessageGroup(Object groupId) {
this(groupId, System.currentTimeMillis(), null, null);
}
public KeyValueMessageGroup(Object groupId, long timestamp,
ConcurrentMap<String, Message<?>> marked,
ConcurrentMap<String, Message<?>> unmarked) {
this.groupId = groupId;
this.timestamp = timestamp;
this.marked = marked;
this.unmarked = unmarked;
}
public KeyValueMessageGroup(Object groupId,
ConcurrentMap<String, Message<?>> marked,
ConcurrentMap<String, Message<?>> unmarked) {
this(groupId, System.currentTimeMillis(), marked, unmarked);
}
@Override
public int hashCode() {
return groupId.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof KeyValueMessageGroup) {
Object otherGroupId = ((KeyValueMessageGroup) obj).getGroupId();
return getGroupId().equals(otherGroupId);
}
return false;
}
public void setUnmarked(Map<String, Message<?>> unmarked) {
this.unmarked = unmarked;
}
public void setMarked( Map<String, Message<?>> marked) {
this.marked = marked;
}
/**
* @return the timestamp (milliseconds since epoch) associated with the creation of this group
*/
public long getTimestamp() {
return timestamp;
}
/**
* Query if the message can be added.
*/
public boolean canAdd(Message<?> message) {
return !isMember(message);
}
/**
* Add this {@link org.springframework.integration.Message} to the
* {@link org.springframework.integration.store.MessageGroup}, delegating in this case to the {@link #unmarked} field
*
* @param message the {@link org.springframework.integration.Message} you are adding to the {@link java.util.Map}
*/
public void add(Message<?> message) {
if (isMember(message)) {
return;
}
String unmarkedKey = this.unmarkedKey(message);
this.unmarked.put(unmarkedKey, (Message<?>) message);
}
/**
* the only reason we differentiate the keys is so that conceptually you could use the <em>same</em> {@link java.util.Map} instance for both <em>marked</em> and <em>unmarked</em> messages.
*
* This method simply differentiates the key, building on {@link #baseKey(org.springframework.integration.Message)}'s return value
*
* @param msg the {@link org.springframework.integration.Message} from which the key should be generated.
* @return a String to be used as a key
*/
protected String markedKey(Message<?> msg) {
return baseKey(msg) + "-m";
}
/**
* the only reason we differentiate the keys is so that conceptually you could use the <em>same</em> {@link java.util.Map} instance for both <em>marked</em> and <em>unmarked</em> messages.
*
* This method simply differentiates the key, building on {@link #baseKey(org.springframework.integration.Message)}'s return value
*
* @param msg the {@link org.springframework.integration.Message} from which the key should be generated.
* @return a String to be used as a key
*/
protected String unmarkedKey(Message<?> msg) {
return baseKey(msg) + "-u";
}
/**
* Removes this {@link org.springframework.integration.Message} from this {@link org.springframework.integration.store.MessageGroup}'s memory
*
* @param message the message to remove
*/
public void remove(Message<?> message) {
if (unmarked.containsValue(message)) {
unmarked.remove(unmarkedKey(message));
}
if (marked.containsValue(message)) {
marked.remove(markedKey(message));
}
}
/**
* the groupKey is based on the groupID and it sits at the beginning of all the keys for this {@link org.springframework.integration.store.MessageGroup}s keys
*
* @return a string based on {@link #getGroupId()}
*/
protected String groupKey() {
return (getGroupId()).toString();
}
protected String baseKey(Message<?> msg) {
String groupKey = groupKey();
UUID id = msg.getHeaders().getId();
Integer sn = msg.getHeaders().getSequenceNumber();
Integer ss = msg.getHeaders().getSequenceSize();
return String.format("%s-%s-%s-%s", groupKey, id.toString(),
sn.toString(), ss.toString());
}
public Collection<Message<?>> getUnmarked() {
return getMessagesForMessageGroup(this.unmarked);
}
/**
* this method will be used to discover all the messages for a given group in a {@link com.gemstone.gemfire.cache.Region}
*
* @param region the region from which we're hoping to discover these {@link org.springframework.integration.Message}s
* @return a collection of messages
*/
protected Collection<Message<?>> getMessagesForMessageGroup(
Map<String, Message<?>> region) {
try {
String groupMsgKey = groupKey();
Collection<Message<?>> msgs = new ArrayList<Message<?>>();
for (String k : region.keySet()) {
if (k.startsWith(groupMsgKey)) {
msgs.add(region.get(k));
}
}
return msgs;
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
public Collection<Message<?>> getMarked() {
return getMessagesForMessageGroup(this.marked);
}
/**
* @return the key that links these messages together
*/
public Object getGroupId() {
return groupId;
}
/**
* @return true if the group is complete (i.e. no more messages are expected to be added)
*/
public boolean isComplete() {
if (size() == 0) {
return true;
}
int sequenceSize = getSequenceSize();
return (sequenceSize > 0) && (sequenceSize == size());
}
public int getSequenceSize() {
if (size() == 0) {
return 0;
}
return getOne().getHeaders().getSequenceSize();
}
/**
* Mark the given message in this group. If the message is not part of this group then this call has no effect.
*
* @param messageToMark the message that should be marked
*/
public void mark(Message<?> messageToMark) {
if (this.unmarked.containsValue(messageToMark)) {
this.unmarked.remove(baseKey(messageToMark));
}
this.marked.put(baseKey(messageToMark), messageToMark);
}
public void markAll() {
for (Message<?> msg : getUnmarked())
mark(msg);
}
/**
* @return the total number of messages (marked and unmarked) in this group
*/
public int size() {
return getMarked().size() + getUnmarked().size();
}
/**
* @return a single message from the group
*/
public Message<?> getOne() {
if (!this.unmarked.isEmpty()) {
String aKey = this.unmarked.keySet().iterator().next();
return this.unmarked.get(aKey);
}
return null;
}
/**
* This method determines whether messages have been added to this group that supersede the given message based on
* its sequence id. This can be helpful to avoid ending up with sequences larger than their required sequence size
* or sequences that are missing certain sequence numbers.
*
* @param message the message to test for candidacy
*
* @return whether or not the message is a member of the group
*
*/
protected boolean isMember(Message<?> message) {
if (size() == 0) {
return false;
}
Integer messageSequenceNumber = message.getHeaders().getSequenceNumber();
if ((messageSequenceNumber != null) && (messageSequenceNumber > 0)) {
Integer messageSequenceSize = message.getHeaders().getSequenceSize();
if (!messageSequenceSize.equals(getSequenceSize())) {
return true;
} else {
if (containsSequenceNumber(getUnmarked(), messageSequenceNumber) ||
containsSequenceNumber(getUnmarked(),
messageSequenceNumber)) {
return true;
}
}
}
return false;
}
protected boolean containsSequenceNumber(Collection<Message<?>> messages,
Integer messageSequenceNumber) {
for (Message<?> member : messages) {
Integer memberSequenceNumber = member.getHeaders()
.getSequenceNumber();
if (messageSequenceNumber.equals(memberSequenceNumber)) {
return true;
}
}
return false;
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.gemfire.store;
import org.springframework.integration.Message;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* Provides an implementation of {@link org.springframework.integration.store.MessageGroupStore} that delegates to a backend Gemfire instance.
* Gemfire holds keys and values. This class provides a strategy to hold objects.
*
* @author Josh Long
* @since 2.1
*/
public class KeyValueMessageGroupStore extends AbstractMessageGroupStore {
/**
* Required {@link com.gemstone.gemfire.cache.Region} to managed the association of groups => {@link KeyValueMessageGroup}
*/
protected Map<Object, KeyValueMessageGroup> groupIdToMessageGroup;
/**
* Required {@link com.gemstone.gemfire.cache.Region} to manage the #unmarked data
*/
protected Map<String, Message<?>> unmarked;
/**
* Required {@link com.gemstone.gemfire.cache.Region} to manage the #marked data
*/
protected Map<String, Message<?>> marked;
/**
* Create a KeyValueMessageGroupStore with two backing regions to handle the state management.
*
* @param groupIdToMessageGroup the region to associate
* @param marked the collection that will hold which messages are marked (delivered)
* @param unmarked the collection that holds which messages are unmarked (not yet delivered)
*/
public KeyValueMessageGroupStore(Map<Object, KeyValueMessageGroup> groupIdToMessageGroup, Map<String, Message<?>> marked, Map<String, Message<?>> unmarked) {
this.marked = marked;
this.unmarked = unmarked;
this.groupIdToMessageGroup = groupIdToMessageGroup;
}
public MessageGroup getMessageGroup(Object groupId) {
Assert.notNull(groupId, "'groupId' must not be null");
return this.getMessageGroupInternal(groupId);
}
public MessageGroup addMessageToGroup(Object groupId, Message<?> message) {
KeyValueMessageGroup group = getMessageGroupInternal(groupId);
group.add(message);
return group;
}
public MessageGroup markMessageGroup(MessageGroup group) {
Object groupId = group.getGroupId();
KeyValueMessageGroup internal = getMessageGroupInternal(groupId);
internal.markAll();
return internal;
}
public void removeMessageGroup(Object groupId) {
groupIdToMessageGroup.remove(groupId);
}
public MessageGroup removeMessageFromGroup(Object key, Message<?> messageToRemove) {
KeyValueMessageGroup group = getMessageGroupInternal(key);
group.remove(messageToRemove);
return group;
}
public MessageGroup markMessageFromGroup(Object key, Message<?> messageToMark) {
KeyValueMessageGroup group = getMessageGroupInternal(key);
group.mark(messageToMark);
return group;
}
@Override
public Iterator<MessageGroup> iterator() {
return new HashSet<MessageGroup>(groupIdToMessageGroup.values()).iterator();
}
protected KeyValueMessageGroup ensureMessageGroupHasReferencesToRegions(KeyValueMessageGroup keyValueMessageGroup) {
if (keyValueMessageGroup == null) {
return null;
}
keyValueMessageGroup.setMarked(this.marked);
keyValueMessageGroup.setUnmarked(this.unmarked);
return keyValueMessageGroup;
}
protected KeyValueMessageGroup getMessageGroupInternal(Object groupId) {
if (!groupIdToMessageGroup.containsKey(groupId)) {
groupIdToMessageGroup.put(groupId, new KeyValueMessageGroup(groupId));
}
return ensureMessageGroupHasReferencesToRegions(groupIdToMessageGroup.get( groupId));
}
}

View File

@@ -0,0 +1,342 @@
/*
* 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);
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);
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);
messageGroup = store.getMessageGroup(1);
assertEquals(1, messageGroup.size());
}
@Test
public void testRemoveMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
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);
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);
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);
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
public void testMarkAllMessagesInMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
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);
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);
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.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@Test
public void testMarkMessageInMessageGroup() throws Exception{
GemfireMessageStore store = new GemfireMessageStore(this.cache);
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);
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);
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);
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);
GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
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);
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);
GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
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);
final GemfireMessageStore store2 = new GemfireMessageStore(this.cache);
final Message<?> message = new GenericMessage<String>("1");
ExecutorService executor = null;
final List<Object> failures = new ArrayList<Object>();
for (int i = 0; i < 100; i++) {
executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
public void run() {
MessageGroup group = store1.addMessageToGroup(1, message);
if (group.getUnmarked().size() != 1){
failures.add("ADD");
throw new AssertionFailedError("Failed on ADD");
}
}
});
executor.execute(new Runnable() {
public void run() {
MessageGroup group = store2.removeMessageFromGroup(1, message);
if (group.getUnmarked().size() != 0){
failures.add("REMOVE");
throw new AssertionFailedError("Failed on Remove");
}
}
});
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
store2.removeMessageFromGroup(1, message); // ensures that if ADD thread executed after REMOVE, the store is empty for the next cycle
}
assertTrue(failures.size() == 0);
}
@Test
public void testWithAggregatorWithShutdown(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("gemfire-aggregator-config.xml", this.getClass());
MessageChannel input = context.getBean("inputChannel", MessageChannel.class);
QueueChannel output = context.getBean("outputChannel", QueueChannel.class);
Message<?> m1 = MessageBuilder.withPayload("1").setSequenceNumber(1).setSequenceSize(3).setCorrelationId(1).build();
Message<?> m2 = MessageBuilder.withPayload("2").setSequenceNumber(2).setSequenceSize(3).setCorrelationId(1).build();
input.send(m1);
assertNull(output.receive(1000));
input.send(m2);
assertNull(output.receive(1000));
context = new ClassPathXmlApplicationContext("gemfire-aggregator-config-a.xml", this.getClass());
MessageChannel inputA = context.getBean("inputChannel", MessageChannel.class);
QueueChannel outputA = context.getBean("outputChannel", QueueChannel.class);
Message<?> m3 = MessageBuilder.withPayload("3").setSequenceNumber(3).setSequenceSize(3).setCorrelationId(1).build();
inputA.send(m3);
assertNotNull(outputA.receive(1000));
}
@Before
public void init() throws Exception{
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.afterPropertiesSet();
this.cache = (Cache)cacheFactoryBean.getObject();
}
@After
public void cleanup(){
this.cache.close();
}
}

View File

@@ -1,213 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.gemfire.store;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy;
import org.springframework.integration.aggregator.ReleaseStrategy;
import org.springframework.integration.aggregator.SequenceSizeReleaseStrategy;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.gemfire.store.KeyValueMessageGroup;
import org.springframework.integration.gemfire.store.KeyValueMessageGroupStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.Region;
/**
* Our aggregator needs a
* {@link org.springframework.integration.gemfire.store.KeyValueMessageGroupStore}
* . This handles configuration of the ancillary objects.
*
* @author Josh Long
* @since 2.1
*/
@Configuration
public class GemfireMessageGroupStoreTestConfiguration {
public static List<String> LIST_OF_STRINGS = Arrays.asList("1,2,3,4,5".split(","));
static private Log log = LogFactory.getLog(GemfireMessageGroupStoreTestConfiguration.class);
@Value("${correlation-header}")
private String correlationHeader;
@Bean
public Cache cache() throws Throwable {
CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();
cacheFactoryBean.afterPropertiesSet();
return (Cache)cacheFactoryBean.getObject();
}
@Bean
public Region<Object, KeyValueMessageGroup> messageGroupRegion() throws Throwable {
RegionFactoryBean<Object, KeyValueMessageGroup> regionFactoryBean = new RegionFactoryBean<Object, KeyValueMessageGroup>();
regionFactoryBean.setName("messageGroupRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean
public Region<String, Message<?>> unmarkedRegion() throws Throwable {
RegionFactoryBean<String, Message<?>> regionFactoryBean = new RegionFactoryBean<String, Message<?>>();
regionFactoryBean.setName("unmarkedRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean
public Region<String, Message<?>> markedRegion() throws Throwable {
RegionFactoryBean<String, Message<?>> regionFactoryBean = new RegionFactoryBean<String, Message<?>>();
regionFactoryBean.setName("markedRegion");
regionFactoryBean.setCache(cache());
regionFactoryBean.afterPropertiesSet();
return regionFactoryBean.getObject();
}
@Bean(name = "messageGroupStoreActivator")
public FakeMessageConsumer serviceActivator() {
return new FakeMessageConsumer();
}
@Bean
public ReleaseStrategy releaseStrategy() {
return new SequenceSizeReleaseStrategy(false);
}
@Bean
public CorrelationStrategy correlationStrategy() {
return new HeaderAttributeCorrelationStrategy(this.correlationHeader);
}
@Bean
public KeyValueMessageGroupStore gemfireMessageGroupStore() throws Throwable {
return new KeyValueMessageGroupStore(messageGroupRegion(), markedRegion(), unmarkedRegion());
}
@Bean
public FakeMessageProducer producer() {
return new FakeMessageProducer();
}
static public class FakeMessageConsumer {
private List<Collection<Object>> batches = new ArrayList<Collection<Object>>();
public List<Collection<Object>> getBatches() {
return this.batches;
}
@ServiceActivator
public void activateAsMessagesArriveInBatches(Message<Collection<Object>> msg) throws Throwable {
Collection<Object> payloads = msg.getPayload();
batches.add(payloads);
if (log.isDebugEnabled()) {
log.debug(payloads);
}
}
}
static public class FakeMessageProducer implements InitializingBean, SmartLifecycle {
public boolean isAutoStartup() {
return false;
}
public void stop(Runnable callback) {
stop();
callback.run();
}
public int getPhase() {
return 0;
}
@Autowired
@Qualifier("i")
private MessageChannel messageChannel;
private MessagingTemplate messagingTemplate = new MessagingTemplate();
private volatile boolean running = false;
@Value("${correlation-header}")
private String correlationHeader;
public void sendManyMessages(int correlationValue, Collection<String> lines) throws Throwable {
Assert.notNull(lines, "the collection must be non-null");
Assert.notEmpty(lines, "the collection must not be empty");
int ctr = 0;
int size = lines.size();
for (String l : lines) {
Message<?> msg = MessageBuilder.withPayload(l).setCorrelationId(this.correlationHeader)
.setHeader(this.correlationHeader, correlationValue).setSequenceNumber(++ctr)
.setSequenceSize(size).build();
this.messagingTemplate.send(msg);
}
}
public void afterPropertiesSet() throws Exception {
this.messagingTemplate.setDefaultChannel(this.messageChannel);
}
public void start() {
running = true;
for (int i = 0; i < 10; i++) {
try {
sendManyMessages(i, LIST_OF_STRINGS);
}
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
}
public void stop() {
running = false;
}
public boolean isRunning() {
return running;
}
}
}

View File

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

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.gemfire.store;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests the Gemfire
* {@link org.springframework.integration.store.MessageGroupStore}
* implementation,
* {@link org.springframework.integration.gemfire.store.GemfireMessageGroupStore}
* .
* <p/>
* It tests the {@link org.springframework.integration.store.MessageGroupStore}
* by sending 10 batches of letters (all of the same width), and then counting
* on the other end that indeed all 10 batches arrived and that all letters
* expected are there. *
*
* @author Josh Long
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class GemfireMessageGroupStoreTests {
@Autowired
private GemfireMessageGroupStoreTestConfiguration.FakeMessageConsumer consumer;
@Autowired
private GemfireMessageGroupStoreTestConfiguration.FakeMessageProducer producer;
private List<String> letters = GemfireMessageGroupStoreTestConfiguration.LIST_OF_STRINGS;
private int maxSize = 10;
@Test
public void testGemfireMessageGroupStore() throws Exception {
producer.afterPropertiesSet();
producer.start();
List<Collection<Object>> batches = consumer.getBatches();
assertEquals(maxSize, batches.size());
for (Collection<Object> collection : batches) {
Assert.assertTrue(letters.size() == collection.size());
for (String c : this.letters) {
Assert.assertTrue(collection.contains(c));
}
for (Object o : collection) {
Assert.assertTrue(o instanceof String);
}
}
producer.stop();
}
}

View File

@@ -16,20 +16,15 @@
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 +37,8 @@ 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);
MessageStore store = new GemfireMessageStore(cache);
Message<?> message = MessageBuilder.withPayload("test").build();
store.addMessage(message);
Message<?> retrieved = store.getMessage(message.getHeaders().getId());

View File

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

View File

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