/*
* Copyright 2002-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.jdbc.store;
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 static org.springframework.integration.test.matcher.PayloadAndHeaderMatcher.sameExceptIgnorableHeaders;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Dave Syer
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Artem Bilan
* @author Gary Russell
* @author Will Schipp
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext // close at the end after class
@Transactional
public class JdbcMessageStoreTests {
@Autowired
private DataSource dataSource;
private JdbcMessageStore messageStore;
@Before
public void init() {
messageStore = new JdbcMessageStore(dataSource);
}
@Test
public void testGetNonExistent() throws Exception {
Message<?> result = messageStore.getMessage(UUID.randomUUID());
assertNull(result);
}
@Test
public void testAddAndGet() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
Message<String> saved = messageStore.addMessage(message);
Message<?> result = messageStore.getMessage(saved.getHeaders().getId());
assertNotNull(result);
assertThat(saved, sameExceptIgnorableHeaders(result));
}
@Test
public void testWithMessageHistory() throws Exception {
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);
messageStore.addMessage(message);
message = messageStore.getMessage(message.getHeaders().getId());
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
public void testSize() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
messageStore.addMessage(message);
assertEquals(1, messageStore.getMessageCount());
}
@Test
public void testSerializer() throws Exception {
// N.B. these serializers are not realistic (just for test purposes)
messageStore.setSerializer((object, outputStream) -> {
outputStream.write(((Message<?>) object).getPayload().toString().getBytes());
outputStream.flush();
});
messageStore.setDeserializer(inputStream -> {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
return new GenericMessage<String>(reader.readLine());
});
Message<String> message = MessageBuilder.withPayload("foo").build();
Message<String> saved = messageStore.addMessage(message);
assertNotNull(messageStore.getMessage(message.getHeaders().getId()));
Message<?> result = messageStore.getMessage(saved.getHeaders().getId());
assertNotNull(result);
assertEquals("foo", result.getPayload());
}
@Test
public void testAddAndGetWithDifferentRegion() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
Message<String> saved = messageStore.addMessage(message);
messageStore.setRegion("FOO");
Message<?> result = messageStore.getMessage(saved.getHeaders().getId());
assertNull(result);
}
@Test
public void testAddAndUpdate() throws Exception {
Message<?> message = MessageBuilder.withPayload("foo").setCorrelationId("X").build();
message = messageStore.addMessage(message);
message = MessageBuilder.fromMessage(message).setCorrelationId("Y").build();
message = messageStore.addMessage(message);
message = messageStore.getMessage(message.getHeaders().getId());
assertEquals("Y", new IntegrationMessageHeaderAccessor(message).getCorrelationId());
}
@Test
public void testAddAndUpdateAlreadySaved() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
message = messageStore.addMessage(message);
Message<String> result = messageStore.addMessage(message);
assertEquals(message, result);
}
@Test
public void testAddAndUpdateAlreadySavedAndCopied() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
Message<String> saved = messageStore.addMessage(message);
Message<String> copy = MessageBuilder.fromMessage(saved).build();
Message<String> result = messageStore.addMessage(copy);
assertEquals(copy, result);
assertEquals(saved, result);
assertNotNull(messageStore.getMessage(saved.getHeaders().getId()));
}
@Test
public void testAddAndUpdateWithChange() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
Message<String> saved = messageStore.addMessage(message);
Message<String> copy = MessageBuilder.fromMessage(saved).setHeader("newHeader", 1).build();
Message<String> result = messageStore.addMessage(copy);
assertNotSame(saved, result);
assertThat(saved, sameExceptIgnorableHeaders(result, "newHeader"));
assertNotNull(messageStore.getMessage(saved.getHeaders().getId()));
}
@Test
public void testAddAndRemoveMessageGroup() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").build();
message = messageStore.addMessage(message);
assertNotNull(messageStore.removeMessage(message.getHeaders().getId()));
}
@Test
public void testAddAndGetMessageGroup() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
long now = System.currentTimeMillis();
messageStore.addMessagesToGroup(groupId, message);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(1, group.size());
assertTrue("Timestamp too early: " + group.getTimestamp() + "<" + now, group.getTimestamp() >= now);
}
@Test
public void testAddAndRemoveMessageFromMessageGroup() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
messageStore.removeMessagesFromGroup(groupId, message);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
}
@Test
public void testAddAndRemoveMessagesFromMessageGroup() throws Exception {
String groupId = "X";
this.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();
messages.add(message);
}
this.messageStore.addMessagesToGroup(groupId, messages.toArray(new Message<?>[messages.size()]));
MessageGroup group = this.messageStore.getMessageGroup(groupId);
assertEquals(25, group.size());
this.messageStore.removeMessagesFromGroup(groupId, messages);
group = this.messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
}
@Test
public void testRemoveMessageGroup() throws Exception {
JdbcTemplate template = new JdbcTemplate(this.dataSource);
template.afterPropertiesSet();
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
messageStore.removeMessageGroup(groupId);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
String uuidGroupId = UUIDConverter.getUUID(groupId).toString();
assertTrue(template.queryForList(
"SELECT * from INT_GROUP_TO_MESSAGE where GROUP_KEY = ?", uuidGroupId).size() == 0);
}
@Test
public void testCompleteMessageGroup() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
messageStore.completeGroup(groupId);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertTrue(group.isComplete());
assertEquals(1, group.size());
}
@Test
public void testUpdateLastReleasedSequence() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
messageStore.setLastReleasedSequenceNumberForGroup(groupId, 5);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(5, group.getLastReleasedMessageSequenceNumber());
}
@Test
public void testMessageGroupCount() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").build();
messageStore.addMessagesToGroup(groupId, message);
assertEquals(1, messageStore.getMessageGroupCount());
}
@Test
public void testMessageGroupSizes() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").build();
messageStore.addMessagesToGroup(groupId, message);
assertEquals(1, messageStore.getMessageCountForAllMessageGroups());
}
@Test
public void testOrderInMessageGroup() throws Exception {
String groupId = "X";
this.messageStore.addMessagesToGroup(groupId,
MessageBuilder.withPayload("foo").setCorrelationId(groupId).build());
Thread.sleep(1);
this.messageStore.addMessagesToGroup(groupId,
MessageBuilder.withPayload("bar").setCorrelationId(groupId).build());
MessageGroup group = this.messageStore.getMessageGroup(groupId);
assertEquals(2, group.size());
assertEquals("foo", this.messageStore.pollMessageFromGroup(groupId).getPayload());
assertEquals("bar", this.messageStore.pollMessageFromGroup(groupId).getPayload());
}
@Test
public void testExpireMessageGroupOnCreateOnly() throws Exception {
final String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.addMessagesToGroup(groupId, message);
final CountDownLatch groupRemovalLatch = new CountDownLatch(1);
messageStore.registerMessageGroupExpiryCallback((messageGroupStore, group) -> {
messageGroupStore.removeMessageGroup(group.getGroupId());
groupRemovalLatch.countDown();
});
messageStore.expireMessageGroups(2000);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(1, group.size());
messageStore.addMessagesToGroup(groupId, MessageBuilder.withPayload("bar").setCorrelationId(groupId).build());
JdbcTemplate template = new JdbcTemplate(this.dataSource);
template.afterPropertiesSet();
template.update("UPDATE INT_MESSAGE_GROUP set CREATED_DATE=? where GROUP_KEY=? and REGION=?",
(PreparedStatementSetter) ps -> {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis() - 10000));
ps.setString(2, UUIDConverter.getUUID(groupId).toString());
ps.setString(3, "DEFAULT");
});
messageStore.expireMessageGroups(2000);
group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
assertTrue(groupRemovalLatch.await(10, TimeUnit.SECONDS));
}
@Test
public void testExpireMessageGroupOnIdleOnly() throws Exception {
String groupId = "X";
Message<String> message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build();
messageStore.setTimeoutOnIdle(true);
messageStore.addMessagesToGroup(groupId, message);
messageStore.registerMessageGroupExpiryCallback((messageGroupStore, group) -> messageGroupStore.removeMessageGroup(group.getGroupId()));
JdbcTemplate template = new JdbcTemplate(this.dataSource);
template.afterPropertiesSet();
updateMessageGroup(template, groupId, 1000);
messageStore.expireMessageGroups(2000);
MessageGroup group = messageStore.getMessageGroup(groupId);
assertEquals(1, group.size());
updateMessageGroup(template, groupId, 2000);
messageStore.addMessagesToGroup(groupId, MessageBuilder.withPayload("bar").setCorrelationId(groupId).build());
group = messageStore.getMessageGroup(groupId);
assertEquals(2, group.size());
updateMessageGroup(template, groupId, 2000);
messageStore.expireMessageGroups(2000);
group = messageStore.getMessageGroup(groupId);
assertEquals(0, group.size());
}
private void updateMessageGroup(JdbcTemplate template, final String groupId, final long timeout) {
template.update("UPDATE INT_MESSAGE_GROUP set UPDATED_DATE=? where GROUP_KEY=? and REGION=?",
(PreparedStatementSetter) ps -> {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis() - timeout));
ps.setString(2, UUIDConverter.getUUID(groupId).toString());
ps.setString(3, "DEFAULT");
});
}
@Test
public void testMessagePollingFromTheGroup() throws Exception {
String groupId = "X";
messageStore.addMessagesToGroup(groupId, MessageBuilder.withPayload("foo").setCorrelationId(groupId).build());
messageStore.addMessagesToGroup(groupId, MessageBuilder.withPayload("bar").setCorrelationId(groupId).build());
messageStore.addMessagesToGroup(groupId, MessageBuilder.withPayload("baz").setCorrelationId(groupId).build());
messageStore.addMessagesToGroup("Y", MessageBuilder.withPayload("barA").setCorrelationId(groupId).build(),
MessageBuilder.withPayload("bazA").setCorrelationId(groupId).build());
MessageGroup group = messageStore.getMessageGroup("X");
assertEquals(3, group.size());
Message<?> message1 = messageStore.pollMessageFromGroup("X");
assertNotNull(message1);
assertEquals("foo", message1.getPayload());
group = messageStore.getMessageGroup("X");
assertEquals(2, group.size());
Message<?> message2 = messageStore.pollMessageFromGroup("X");
assertNotNull(message2);
assertEquals("bar", message2.getPayload());
group = messageStore.getMessageGroup("X");
assertEquals(1, group.size());
}
@Test
public void testSameMessageToMultipleGroups() throws Exception {
final String group1Id = "group1";
final String group2Id = "group2";
final Message<String> message = MessageBuilder.withPayload("foo").build();
final MessageBuilder<String> builder1 = MessageBuilder.fromMessage(message);
final MessageBuilder<String> builder2 = MessageBuilder.fromMessage(message);
builder1.setSequenceNumber(1);
builder2.setSequenceNumber(2);
final Message<?> message1 = builder1.build();
final Message<?> message2 = builder2.build();
messageStore.addMessageToGroup(group1Id, message1);
messageStore.addMessageToGroup(group2Id, message2);
final Message<?> messageFromGroup1 = messageStore.pollMessageFromGroup(group1Id);
final Message<?> messageFromGroup2 = messageStore.pollMessageFromGroup(group2Id);
assertNotNull(messageFromGroup1);
assertNotNull(messageFromGroup2);
assertEquals(1, messageFromGroup1.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER));
assertEquals(2, messageFromGroup2.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER));
}
@Test
public void testSameMessageAndGroupToMultipleRegions() throws Exception {
final String groupId = "myGroup";
final String region1 = "region1";
final String region2 = "region2";
final JdbcMessageStore messageStore1 = new JdbcMessageStore(dataSource);
messageStore1.setRegion(region1);
final JdbcMessageStore messageStore2 = new JdbcMessageStore(dataSource);
messageStore1.setRegion(region2);
final Message<String> message = MessageBuilder.withPayload("foo").build();
final MessageBuilder<String> builder1 = MessageBuilder.fromMessage(message);
final MessageBuilder<String> builder2 = MessageBuilder.fromMessage(message);
builder1.setSequenceNumber(1);
builder2.setSequenceNumber(2);
final Message<?> message1 = builder1.build();
final Message<?> message2 = builder2.build();
messageStore1.addMessageToGroup(groupId, message1);
messageStore2.addMessageToGroup(groupId, message2);
final Message<?> messageFromRegion1 = messageStore1.pollMessageFromGroup(groupId);
final Message<?> messageFromRegion2 = messageStore2.pollMessageFromGroup(groupId);
assertNotNull(messageFromRegion1);
assertNotNull(messageFromRegion2);
assertEquals(1, messageFromRegion1.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER));
assertEquals(2, messageFromRegion2.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER));
}
@Test
public void testCompletedNotExpiredGroupINT3037() throws Exception {
/*
* based on the aggregator scenario as follows;
*
* send three messages in
* 1 of 2
* 2 of 2
* 2 of 2 (last again)
*
* expected behavior is that the LAST message (2 of 2 repeat) should be on the discard channel
* (discard behavior performed by the AbstractCorrelatingMessageHandler.handleMessageInternal)
*/
final JdbcMessageStore messageStore = new JdbcMessageStore(dataSource);
//init
String groupId = "group";
//build the messages
Message<?> oneOfTwo = MessageBuilder.withPayload("hello")
.setSequenceNumber(1)
.setSequenceSize(2)
.setCorrelationId(groupId)
.build();
Message<?> twoOfTwo = MessageBuilder.withPayload("world")
.setSequenceNumber(2)
.setSequenceSize(2)
.setCorrelationId(groupId)
.build();
//add to the messageStore
messageStore.addMessagesToGroup(groupId, oneOfTwo, twoOfTwo);
//check that 2 messages are there
assertTrue(messageStore.getMessageGroupCount() == 1);
assertTrue(messageStore.getMessageCount() == 2);
//retrieve the group (like in the aggregator)
MessageGroup messageGroup = messageStore.getMessageGroup(groupId);
//'complete' the group
messageStore.completeGroup(messageGroup.getGroupId());
//now clear the messages
for (Message<?> message : messageGroup.getMessages()) {
messageStore.removeMessagesFromGroup(groupId, message);
} //end for
//'add' the other message --> emulated by getting the messageGroup
messageGroup = messageStore.getMessageGroup(groupId);
//should be marked 'complete' --> old behavior it would not
assertTrue(messageGroup.isComplete());
}
}