/*
* Copyright 2002-2016 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.mongodb.store;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.mongodb.rules.MongoDbAvailable;
import org.springframework.integration.mongodb.rules.MongoDbAvailableTests;
import org.springframework.integration.store.AbstractBatchingMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import com.mongodb.MongoClient;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Amol Nayak
* @author Artem Bilan
*/
public abstract class AbstractMongoDbMessageGroupStoreTests extends MongoDbAvailableTests {
@Test
@MongoDbAvailable
public void testNonExistingEmptyMessageGroup() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = getMessageGroupStore();
store.addMessagesToGroup(1, new GenericMessage<Object>("foo"));
MessageGroup messageGroup = store.getMessageGroup(1);
assertNotNull(messageGroup);
assertThat(messageGroup.getClass().getName(), containsString("PersistentMessageGroup"));
assertEquals(1, messageGroup.size());
}
@Test
@MongoDbAvailable
public void testMessageGroupWithAddedMessagePrimitiveGroupId() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageStore messageStore = this.getMessageStore();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> messageA = new GenericMessage<String>("A");
Message<?> messageB = new GenericMessage<String>("B");
store.addMessagesToGroup(1, messageA);
messageGroup = store.addMessageToGroup(1, messageB);
assertNotNull(messageGroup);
assertEquals(2, messageGroup.size());
Message<?> retrievedMessage = messageStore.getMessage(messageA.getHeaders().getId());
assertNotNull(retrievedMessage);
assertEquals(retrievedMessage.getHeaders().getId(), messageA.getHeaders().getId());
// ensure that 'message_group' header that is only used internally is not propagated
assertNull(retrievedMessage.getHeaders().get("message_group"));
}
@Test
@MongoDbAvailable
public void testMessageGroupWithAddedMessageUUIDGroupIdAndUUIDHeader() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageStore messageStore = this.getMessageStore();
Object id = UUID.randomUUID();
MessageGroup messageGroup = store.getMessageGroup(id);
UUID uuidA = UUID.randomUUID();
Message<?> messageA = MessageBuilder.withPayload("A").setHeader("foo", uuidA).build();
UUID uuidB = UUID.randomUUID();
Message<?> messageB = MessageBuilder.withPayload("B").setHeader("foo", uuidB).build();
store.addMessagesToGroup(id, messageA);
messageGroup = store.addMessageToGroup(id, messageB);
assertNotNull(messageGroup);
assertEquals(2, messageGroup.size());
Message<?> retrievedMessage = messageStore.getMessage(messageA.getHeaders().getId());
assertNotNull(retrievedMessage);
assertEquals(retrievedMessage.getHeaders().getId(), messageA.getHeaders().getId());
// ensure that 'message_group' header that is only used internally is not propagated
assertNull(retrievedMessage.getHeaders().get("message_group"));
Object fooHeader = retrievedMessage.getHeaders().get("foo");
assertTrue(fooHeader instanceof UUID);
assertEquals(uuidA, fooHeader);
}
@Test
@MongoDbAvailable
public void testCountMessagesInGroup() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
Message<?> messageA = new GenericMessage<String>("A");
Message<?> messageB = new GenericMessage<String>("B");
store.addMessagesToGroup(1, messageA, messageB);
assertEquals(2, store.messageGroupSize(1));
}
@Test
@MongoDbAvailable
public void testPollMessages() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
Message<?> messageA = new GenericMessage<String>("A");
Message<?> messageB = new GenericMessage<String>("B");
store.addMessagesToGroup(1, messageA);
Thread.sleep(10);
store.addMessagesToGroup(1, messageB);
assertEquals(2, store.messageGroupSize(1));
Message<?> out = store.pollMessageFromGroup(1);
assertNotNull(out);
assertEquals("A", out.getPayload());
assertEquals(1, store.messageGroupSize(1));
out = store.pollMessageFromGroup(1);
assertEquals("B", out.getPayload());
assertEquals(0, store.messageGroupSize(1));
}
@Test
@MongoDbAvailable
public void testSameMessageMultipleGroupsPoll() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
Message<?> messageA = new GenericMessage<String>("A");
store.addMessagesToGroup(1, messageA);
store.addMessagesToGroup(2, messageA);
store.addMessagesToGroup(3, messageA);
store.addMessagesToGroup(4, messageA);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(1, store.messageGroupSize(3));
assertEquals(1, store.messageGroupSize(4));
store.pollMessageFromGroup(3);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(1, store.messageGroupSize(4));
store.pollMessageFromGroup(4);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
store.pollMessageFromGroup(2);
assertEquals(1, store.messageGroupSize(1));
assertEquals(0, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
store.pollMessageFromGroup(1);
assertEquals(0, store.messageGroupSize(1));
assertEquals(0, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
}
@Test
@MongoDbAvailable
public void testSameMessageMultipleGroupsRemove() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
Message<?> messageA = new GenericMessage<String>("A");
store.addMessagesToGroup(1, messageA);
store.addMessagesToGroup(2, messageA);
store.addMessagesToGroup(3, messageA);
store.addMessagesToGroup(4, messageA);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(1, store.messageGroupSize(3));
assertEquals(1, store.messageGroupSize(4));
store.removeMessagesFromGroup(3, messageA);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(1, store.messageGroupSize(4));
store.removeMessagesFromGroup(4, messageA);
assertEquals(1, store.messageGroupSize(1));
assertEquals(1, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
store.removeMessagesFromGroup(2, messageA);
assertEquals(1, store.messageGroupSize(1));
assertEquals(0, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
store.removeMessagesFromGroup(1, messageA);
assertEquals(0, store.messageGroupSize(1));
assertEquals(0, store.messageGroupSize(2));
assertEquals(0, store.messageGroupSize(3));
assertEquals(0, store.messageGroupSize(4));
}
@Test
@MongoDbAvailable
public void testMessageGroupUpdatedDateChangesWithEachAddedMessage() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
messageGroup = store.addMessageToGroup(1, message);
assertNotNull(messageGroup);
assertEquals(1, messageGroup.size());
long createdTimestamp = messageGroup.getTimestamp();
long updatedTimestamp = messageGroup.getLastModified();
assertEquals(createdTimestamp, updatedTimestamp);
Thread.sleep(10);
message = new GenericMessage<String>("Hello again");
messageGroup = store.addMessageToGroup(1, message);
createdTimestamp = messageGroup.getTimestamp();
updatedTimestamp = messageGroup.getLastModified();
assertTrue(updatedTimestamp > createdTimestamp);
assertEquals(2, messageGroup.size());
// make sure the store is properly rebuild from MongoDB
store = this.getMessageGroupStore();
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
@MongoDbAvailable
public void testMessageGroupMarkingMessage() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> messageA = new GenericMessage<String>("A");
Message<?> messageB = new GenericMessage<String>("B");
store.addMessagesToGroup(1, messageA);
messageGroup = store.addMessageToGroup(1, messageB);
assertNotNull(messageGroup);
assertEquals(2, messageGroup.size());
store.removeMessagesFromGroup(1, messageA);
messageGroup = store.getMessageGroup(1);
assertEquals(1, messageGroup.size());
// validate that the updates were propagated to Mongo as well
store = this.getMessageGroupStore();
messageGroup = store.getMessageGroup(1);
assertEquals(1, messageGroup.size());
}
@Test
@MongoDbAvailable
public void testRemoveMessageGroup() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageStore messageStore = this.getMessageStore();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
UUID id = message.getHeaders().getId();
messageGroup = store.addMessageToGroup(1, message);
assertNotNull(messageGroup);
assertEquals(1, messageGroup.size());
message = messageStore.getMessage(id);
assertNotNull(message);
store.removeMessageGroup(1);
MessageGroup messageGroupA = store.getMessageGroup(1);
assertEquals(0, messageGroupA.size());
assertFalse(messageGroupA.equals(messageGroup));
}
@Test
@MongoDbAvailable
public void testCompleteMessageGroup() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageGroup messageGroup = store.getMessageGroup(1);
assertNotNull(messageGroup);
Message<?> message = new GenericMessage<String>("Hello");
store.addMessagesToGroup(messageGroup.getGroupId(), message);
store.completeGroup(messageGroup.getGroupId());
messageGroup = store.getMessageGroup(1);
assertTrue(messageGroup.isComplete());
}
@Test
@MongoDbAvailable
public void testLastReleasedSequenceNumber() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageGroup messageGroup = store.getMessageGroup(1);
assertNotNull(messageGroup);
Message<?> message = new GenericMessage<String>("Hello");
store.addMessagesToGroup(messageGroup.getGroupId(), message);
store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5);
messageGroup = store.getMessageGroup(1);
assertEquals(5, messageGroup.getLastReleasedMessageSequenceNumber());
}
@Test
@MongoDbAvailable
public void testRemoveMessageFromTheGroup() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
MessageGroup messageGroup = store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("2");
store.addMessagesToGroup(1, new GenericMessage<String>("1"), message);
messageGroup = store.addMessageToGroup(1, new GenericMessage<String>("3"));
assertNotNull(messageGroup);
assertEquals(3, messageGroup.size());
store.removeMessagesFromGroup(1, message);
messageGroup = store.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
@MongoDbAvailable
public void testMultipleMessageStores() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store1 = this.getMessageGroupStore();
MessageGroupStore store2 = this.getMessageGroupStore();
Message<?> message = new GenericMessage<String>("1");
store1.addMessagesToGroup(1, message, new GenericMessage<String>("2"), new GenericMessage<String>("3"));
MessageGroupStore store3 = this.getMessageGroupStore();
MessageGroup messageGroup = store3.getMessageGroup(1);
assertNotNull(messageGroup);
assertEquals(3, messageGroup.size());
store3.removeMessagesFromGroup(1, message);
messageGroup = store2.getMessageGroup(1);
assertEquals(2, messageGroup.size());
}
@Test
@MongoDbAvailable
public void testMessageGroupIterator() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store1 = this.getMessageGroupStore();
MessageGroupStore store2 = this.getMessageGroupStore();
Message<?> message = new GenericMessage<String>("1");
store2.addMessagesToGroup("1", message);
store1.addMessagesToGroup("2", new GenericMessage<String>("2"));
store2.addMessagesToGroup("3", new GenericMessage<String>("3"));
MessageGroupStore store3 = this.getMessageGroupStore();
Iterator<MessageGroup> iterator = store3.iterator();
assertNotNull(iterator);
int counter = 0;
while (iterator.hasNext()) {
iterator.next();
counter++;
}
assertEquals(3, counter);
store2.removeMessagesFromGroup("1", message);
iterator = store3.iterator();
counter = 0;
while (iterator.hasNext()) {
iterator.next();
counter++;
}
assertEquals(2, counter);
}
@Test
@MongoDbAvailable
public void testAddAndRemoveMessagesFromMessageGroup() throws Exception {
MessageGroupStore messageStore = (MessageGroupStore) this.getMessageStore();
String groupId = "X";
messageStore.removeMessageGroup("X");
((AbstractBatchingMessageGroupStore) messageStore).setRemoveBatchSize(10);
List<Message<?>> messages = new ArrayList<Message<?>>();
for (int i = 0; i < 25; i++) {
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
messages.add(message);
}
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(25, group.size());
messageStore.removeMessagesFromGroup(groupId, messages);
group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
}
// @Test
// @MongoDbAvailable
// public void testConcurrentModifications() throws Exception{
// MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
// final MongoDbMessageStore store1 = new MongoDbMessageStore(mongoDbFactory);
// final MongoDbMessageStore store2 = new MongoDbMessageStore(mongoDbFactory);
//
// 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);
// }
protected void testWithAggregatorWithShutdown(String config) throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config, this.getClass());
context.refresh();
MessageChannel input = context.getBean("inputChannel", MessageChannel.class);
QueueChannel output = context.getBean("outputChannel", QueueChannel.class);
Message<?> m1 = MessageBuilder.withPayload("1")
.setSequenceNumber(1)
.setSequenceSize(10)
.setCorrelationId(1)
.build();
Message<?> m2 = MessageBuilder.withPayload("2")
.setSequenceNumber(2)
.setSequenceSize(10)
.setCorrelationId(1)
.build();
input.send(m1);
assertNull(output.receive(1000));
input.send(m2);
assertNull(output.receive(1000));
for (int i = 3; i < 10; i++) {
input.send(MessageBuilder.withPayload("" + i)
.setSequenceNumber(i)
.setSequenceSize(10)
.setCorrelationId(1)
.build());
}
context.close();
context = new ClassPathXmlApplicationContext(config, this.getClass());
input = context.getBean("inputChannel", MessageChannel.class);
output = context.getBean("outputChannel", QueueChannel.class);
Message<?> m10 = MessageBuilder.withPayload("10")
.setSequenceNumber(10)
.setSequenceSize(10)
.setCorrelationId(1)
.build();
input.send(m10);
assertNotNull(output.receive(2000));
context.close();
}
@Test
@MongoDbAvailable
public void testWithMessageHistory() throws Exception {
this.cleanupCollections(new SimpleMongoDbFactory(new MongoClient(), "test"));
MessageGroupStore store = this.getMessageGroupStore();
store.getMessageGroup(1);
Message<?> message = new GenericMessage<String>("Hello");
DirectChannel fooChannel = new DirectChannel();
fooChannel.setBeanName("fooChannel");
DirectChannel barChannel = new DirectChannel();
barChannel.setBeanName("barChannel");
message = MessageHistory.write(message, fooChannel);
message = MessageHistory.write(message, barChannel);
store.addMessagesToGroup(1, message);
MessageGroup group = store.getMessageGroup(1);
assertNotNull(group);
Collection<Message<?>> messages = group.getMessages();
assertTrue(!messages.isEmpty());
message = messages.iterator().next();
MessageHistory messageHistory = MessageHistory.read(message);
assertNotNull(messageHistory);
assertEquals(2, messageHistory.size());
Properties fooChannelHistory = messageHistory.get(0);
assertEquals("fooChannel", fooChannelHistory.get("name"));
assertEquals("channel", fooChannelHistory.get("type"));
}
protected abstract MessageGroupStore getMessageGroupStore() throws Exception;
protected abstract MessageStore getMessageStore() throws Exception;
}