/* * Copyright 2007-2017 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.redis.store; import static org.hamcrest.Matchers.instanceOf; 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.assertThat; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; 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.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.message.AdviceMessage; import org.springframework.integration.redis.rules.RedisAvailable; import org.springframework.integration.redis.rules.RedisAvailableTests; import org.springframework.integration.store.MessageGroup; import org.springframework.integration.store.SimpleMessageGroup; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.MutableMessage; import org.springframework.integration.support.json.JacksonJsonUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; import com.fasterxml.jackson.databind.ObjectMapper; import junit.framework.AssertionFailedError; /** * @author Oleg Zhurakousky * @author Artem Bilan * @author Gary Russell */ public class RedisMessageGroupStoreTests extends RedisAvailableTests { @Before @After public void setUpTearDown() { StringRedisTemplate template = createStringRedisTemplate(getConnectionFactoryForTest()); template.delete(template.keys("MESSAGE_GROUP_*")); } @Test @RedisAvailable public void testNonExistingEmptyMessageGroup() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); MessageGroup messageGroup = store.getMessageGroup(1); assertNotNull(messageGroup); assertTrue(messageGroup instanceof SimpleMessageGroup); assertEquals(0, messageGroup.size()); } @Test @RedisAvailable public void testMessageGroupUpdatedDateChangesWithEachAddedMessage() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); Message<?> message = new GenericMessage<String>("Hello"); MessageGroup messageGroup = store.addMessageToGroup(1, message); assertEquals(1, messageGroup.size()); long createdTimestamp = messageGroup.getTimestamp(); long updatedTimestamp = messageGroup.getLastModified(); assertEquals(createdTimestamp, updatedTimestamp); Thread.sleep(10); message = new GenericMessage<String>("Hello"); messageGroup = store.addMessageToGroup(1, message); createdTimestamp = messageGroup.getTimestamp(); updatedTimestamp = messageGroup.getLastModified(); assertTrue(updatedTimestamp > createdTimestamp); // make sure the store is properly rebuild from Redis store = new RedisMessageStore(jcf); messageGroup = store.getMessageGroup(1); assertEquals(2, messageGroup.size()); } @Test @RedisAvailable public void testMessageGroupWithAddedMessage() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); Message<?> message = new GenericMessage<String>("Hello"); MessageGroup messageGroup = store.addMessageToGroup(1, message); assertEquals(1, messageGroup.size()); // make sure the store is properly rebuild from Redis store = new RedisMessageStore(jcf); messageGroup = store.getMessageGroup(1); assertEquals(1, messageGroup.size()); } @Test @RedisAvailable public void testRemoveMessageGroup() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); 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.getMessages().size()); assertEquals(0, messageGroupA.size()); // make sure the store is properly rebuild from Redis store = new RedisMessageStore(jcf); messageGroup = store.getMessageGroup(1); assertEquals(0, messageGroup.getMessages().size()); assertEquals(0, messageGroup.size()); } @Test @RedisAvailable public void testCompleteMessageGroup() throws Exception { RedisConnectionFactory jcf = 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 { RedisConnectionFactory jcf = 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 { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); MessageGroup messageGroup = store.getMessageGroup(1); Message<?> message = new GenericMessage<String>("2"); store.addMessagesToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1"), message); messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<String>("3")); assertEquals(3, messageGroup.size()); store.removeMessagesFromGroup(1, message); messageGroup = store.getMessageGroup(1); assertEquals(2, messageGroup.size()); // make sure the store is properly rebuild from Redis store = new RedisMessageStore(jcf); messageGroup = store.getMessageGroup(1); assertEquals(2, messageGroup.size()); } @Test @RedisAvailable public void testWithMessageHistory() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); 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); message = store.getMessageGroup(1).getMessages().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")); } @Test @RedisAvailable public void testRemoveNonExistingMessageFromTheGroup() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); MessageGroup messageGroup = store.getMessageGroup(1); store.addMessagesToGroup(messageGroup.getGroupId(), new GenericMessage<String>("1")); store.removeMessagesFromGroup(1, new GenericMessage<String>("2")); } @Test @RedisAvailable public void testRemoveNonExistingMessageFromNonExistingTheGroup() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); store.removeMessagesFromGroup(1, new GenericMessage<String>("2")); } @Test @RedisAvailable public void testMultipleInstancesOfGroupStore() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store1 = new RedisMessageStore(jcf); RedisMessageStore store2 = new RedisMessageStore(jcf); Message<?> message = new GenericMessage<String>("1"); store1.addMessagesToGroup(1, message); MessageGroup messageGroup = store2.addMessageToGroup(1, new GenericMessage<String>("2")); assertEquals(2, messageGroup.getMessages().size()); RedisMessageStore store3 = new RedisMessageStore(jcf); store3.removeMessagesFromGroup(1, message); messageGroup = store3.getMessageGroup(1); assertEquals(1, messageGroup.getMessages().size()); } @Test @RedisAvailable public void testIteratorOfMessageGroups() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store1 = new RedisMessageStore(jcf); RedisMessageStore store2 = new RedisMessageStore(jcf); store1.addMessagesToGroup(1, new GenericMessage<String>("1")); store2.addMessagesToGroup(2, new GenericMessage<String>("2")); store1.addMessagesToGroup(3, new GenericMessage<String>("3"), new GenericMessage<String>("3A")); Iterator<MessageGroup> messageGroups = store1.iterator(); int counter = 0; while (messageGroups.hasNext()) { MessageGroup group = messageGroups.next(); String groupId = (String) group.getGroupId(); if (groupId.equals("1")) { assertEquals(1, group.getMessages().size()); } else if (groupId.equals("2")) { assertEquals(1, group.getMessages().size()); } else if (groupId.equals("3")) { assertEquals(2, group.getMessages().size()); } counter++; } assertEquals(3, counter); store2.removeMessageGroup(3); messageGroups = store1.iterator(); counter = 0; while (messageGroups.hasNext()) { messageGroups.next(); counter++; } assertEquals(2, counter); } @Test @RedisAvailable @Ignore public void testConcurrentModifications() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); final RedisMessageStore store1 = new RedisMessageStore(jcf); final RedisMessageStore store2 = new RedisMessageStore(jcf); 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(() -> { MessageGroup group = store1.addMessageToGroup(1, message); if (group.getMessages().size() != 1) { failures.add("ADD"); throw new AssertionFailedError("Failed on ADD"); } }); executor.execute(() -> { store2.removeMessagesFromGroup(1, message); MessageGroup group = store2.getMessageGroup(1); if (group.getMessages().size() != 0) { failures.add("REMOVE"); throw new AssertionFailedError("Failed on Remove"); } }); executor.shutdown(); executor.awaitTermination(10, TimeUnit.SECONDS); store2.removeMessagesFromGroup(1, message); // ensures that if ADD thread executed after REMOVE, the store is empty for the next cycle } assertTrue(failures.size() == 0); } @Test @RedisAvailable public void testWithAggregatorWithShutdown() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("redis-aggregator-config.xml", 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.close(); context = new ClassPathXmlApplicationContext("redis-aggregator-config.xml", getClass()); input = context.getBean("inputChannel", MessageChannel.class); output = context.getBean("outputChannel", QueueChannel.class); Message<?> m3 = MessageBuilder.withPayload("3") .setSequenceNumber(3) .setSequenceSize(3) .setCorrelationId(1) .build(); input.send(m3); assertNotNull(output.receive(1000)); context.close(); } @Test @RedisAvailable public void testAddAndRemoveMessagesFromMessageGroup() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore messageStore = new RedisMessageStore(jcf); String groupId = "X"; messageStore.removeMessageGroup("X"); 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()); messageStore.removeMessageGroup("X"); } @Test @RedisAvailable public void testJsonSerialization() throws Exception { RedisConnectionFactory jcf = getConnectionFactoryForTest(); RedisMessageStore store = new RedisMessageStore(jcf); ObjectMapper mapper = JacksonJsonUtils.messagingAwareMapper(); GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(mapper); store.setValueSerializer(serializer); Message<?> genericMessage = new GenericMessage<>(new Date()); Message<?> mutableMessage = new MutableMessage<>(UUID.randomUUID()); Message<?> adviceMessage = new AdviceMessage<>("foo", genericMessage); ErrorMessage errorMessage = new ErrorMessage(new RuntimeException("test exception"), mutableMessage); store.addMessagesToGroup(1, genericMessage, mutableMessage, adviceMessage, errorMessage); MessageGroup messageGroup = store.getMessageGroup(1); assertEquals(4, messageGroup.size()); List<Message<?>> messages = new ArrayList<>(messageGroup.getMessages()); assertEquals(genericMessage, messages.get(0)); assertEquals(mutableMessage, messages.get(1)); assertEquals(adviceMessage, messages.get(2)); Message<?> errorMessageResult = messages.get(3); assertEquals(errorMessage.getHeaders(), errorMessageResult.getHeaders()); assertThat(errorMessageResult, instanceOf(ErrorMessage.class)); assertEquals(errorMessage.getOriginalMessage(), ((ErrorMessage) errorMessageResult).getOriginalMessage()); assertEquals(errorMessage.getPayload().getMessage(), ((ErrorMessage) errorMessageResult).getPayload().getMessage()); } }