/* * 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.channel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.jdbc.store.JdbcChannelMessageStore; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.util.UUIDConverter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; /** * @author Gunnar Hillert * @author Artem Bilan */ @RunWith(SpringJUnit4ClassRunner.class) @DirtiesContext // close at the end after class public abstract class AbstractTxTimeoutMessageStoreTests { private static final Log log = LogFactory.getLog(AbstractTxTimeoutMessageStoreTests.class); @Autowired protected DataSource dataSource; @Autowired protected MessageChannel inputChannel; @Autowired protected PlatformTransactionManager transactionManager; @Autowired protected TestService testService; @Autowired @Qualifier("store") protected JdbcChannelMessageStore jdbcChannelMessageStore; @Autowired private MessageChannel first; @Autowired private CountDownLatch successfulLatch; @Autowired private AtomicInteger errorAtomicInteger; @Autowired protected PollableChannel priorityChannel; @Autowired protected PollableChannel afterTxChannel; @Test public void test() throws InterruptedException { int maxMessages = 10; int maxWaitTime = 30000; final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.setIsolationLevel(Isolation.READ_COMMITTED.value()); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); for (int i = 1; i <= maxMessages; ++i) { final String message = "TEST MESSAGE " + i; log.info("Sending message: " + message); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { inputChannel.send(MessageBuilder.withPayload(message).build()); } }); log.info(String.format("Done sending message %s of %s: %s", i, maxMessages, message)); } log.info("Done sending " + maxMessages + " messages."); Assert.assertTrue(String.format("Countdown latch did not count down from " + "%s to 0 in %sms.", maxMessages, maxWaitTime), testService.await(maxWaitTime)); for (int i = 0; i < maxMessages; i++) { Message<?> afterTxMessage = this.afterTxChannel.receive(10000); assertNotNull(afterTxMessage); } Assert.assertEquals(Integer.valueOf(0), Integer.valueOf(jdbcChannelMessageStore.getSizeOfIdCache())); Assert.assertEquals(Integer.valueOf(maxMessages), Integer.valueOf(testService.getSeenMessages().size())); Assert.assertEquals(Integer.valueOf(0), Integer.valueOf(testService.getDuplicateMessagesCount())); } @Test public void testInt2993IdCacheConcurrency() throws InterruptedException, ExecutionException { final String groupId = "testInt2993Group"; for (int i = 0; i < 100; i++) { this.jdbcChannelMessageStore.addMessageToGroup(groupId, new GenericMessage<String>("testInt2993Message")); } ExecutorService executorService = Executors.newCachedThreadPool(); CompletionService<Boolean> completionService = new ExecutorCompletionService<Boolean>(executorService); final int concurrency = 5; final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); for (int i = 0; i < concurrency; i++) { completionService.submit(() -> { for (int i1 = 0; i1 < 100; i1++) { boolean result = transactionTemplate.execute(status -> { Message<?> message = null; try { message = jdbcChannelMessageStore.pollMessageFromGroup(groupId); } catch (Exception e1) { log.error("IdCache race condition.", e1); return false; } try { Thread.sleep(10); } catch (InterruptedException e2) { log.error(e2); } if (message != null) { jdbcChannelMessageStore.removeFromIdCache(message.getHeaders().getId().toString()); } return true; }); if (!result) { return false; } } return true; }); } for (int j = 0; j < concurrency; j++) { assertTrue(completionService.take().get()); } executorService.shutdown(); assertTrue(executorService.awaitTermination(10, TimeUnit.SECONDS)); } @Test public void testInt3181ConcurrentPolling() throws InterruptedException { for (int i = 0; i < 10; i++) { this.first.send(new GenericMessage<Object>("test")); } assertTrue(this.successfulLatch.await(20, TimeUnit.SECONDS)); assertEquals(0, errorAtomicInteger.get()); } @Test public void testMessageSequenceColumn() throws InterruptedException { JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource); String messageGroup = "TEST_MESSAGE_GROUP"; this.jdbcChannelMessageStore.addMessageToGroup(messageGroup, new GenericMessage<Object>("foo")); // The simple sleep to to be sure that messages are stored with different 'CREATED_DATE' Thread.sleep(10); this.jdbcChannelMessageStore.addMessageToGroup(messageGroup, new GenericMessage<Object>("bar")); List<Map<String, Object>> result = jdbcTemplate.queryForList("SELECT MESSAGE_SEQUENCE FROM INT_CHANNEL_MESSAGE " + "WHERE GROUP_KEY = ? ORDER BY CREATED_DATE", UUIDConverter.getUUID(messageGroup).toString()); assertEquals(2, result.size()); Object messageSequence1 = result.get(0).get("MESSAGE_SEQUENCE"); Object messageSequence2 = result.get(1).get("MESSAGE_SEQUENCE"); assertNotNull(messageSequence1); assertThat(messageSequence1, Matchers.instanceOf(Number.class)); assertNotNull(messageSequence2); assertThat(messageSequence2, Matchers.instanceOf(Number.class)); assertThat(((Number) messageSequence1).longValue(), Matchers.lessThan(((Number) messageSequence2).longValue())); this.jdbcChannelMessageStore.removeMessageGroup(messageGroup); } @Test public void testPriorityChannel() throws Exception { Message<String> message = MessageBuilder.withPayload("1").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 1).build(); priorityChannel.send(message); message = MessageBuilder.withPayload("-1").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, -1).build(); priorityChannel.send(message); message = MessageBuilder.withPayload("3").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 3).build(); priorityChannel.send(message); message = MessageBuilder.withPayload("0").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 0).build(); priorityChannel.send(message); message = MessageBuilder.withPayload("2").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 2).build(); priorityChannel.send(message); message = MessageBuilder.withPayload("none").build(); priorityChannel.send(message); message = MessageBuilder.withPayload("31").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 3).build(); priorityChannel.send(message); Message<?> receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("3", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("31", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("2", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("1", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("0", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("-1", receive.getPayload()); receive = priorityChannel.receive(10000); assertNotNull(receive); assertEquals("none", receive.getPayload()); } }