/*
* Copyright 2002-2015 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.aggregator.integration;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ErrorMessage;
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;
/**
* @author Iwein Fuld
* @author Alex Peters
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
public class AggregatorIntegrationTests {
@Autowired
private ApplicationContext context;
@Autowired
private MessageChannel input;
@Autowired
private MessageChannel expiringAggregatorInput;
@Autowired
private MessageChannel nonExpiringAggregatorInput;
@Autowired
private MessageChannel groupTimeoutAggregatorInput;
@Autowired
private MessageChannel groupTimeoutExpressionAggregatorInput;
@Autowired
private MessageChannel zeroGroupTimeoutExpressionAggregatorInput;
@Autowired
private QueueChannel output;
@Autowired
private PollableChannel discard;
@Autowired
private QueueChannel errors;
@Test
public void testVanillaAggregation() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
input.send(new GenericMessage<Integer>(i, headers));
}
Message<?> receive = output.receive(10000);
assertNotNull(receive);
assertEquals(1 + 2 + 3 + 4, receive.getPayload());
}
@Test
public void testNonExpiringAggregator() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNull(output.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
assertNotNull(discard.receive(0));
}
@Test
public void testExpiringAggregator() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
}
assertNotNull(output.receive(0));
assertNull(discard.receive(0));
}
@Test
public void testGroupTimeoutScheduling() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
this.groupTimeoutAggregatorInput.send(new GenericMessage<Integer>(i, headers));
//Wait until 'group-timeout' does its stuff.
MessageGroupStore mgs = TestUtils.getPropertyValue(this.context.getBean("gta.handler"), "messageStore",
MessageGroupStore.class);
int n = 0;
while (n++ < 100 && mgs.getMessageGroupCount() > 0) {
Thread.sleep(100);
}
assertTrue("Group did not complete", n < 100);
assertNotNull(this.output.receive(10000));
assertNull(this.discard.receive(0));
}
}
@Test
public void testGroupTimeoutReschedulingOnMessageDeliveryException() throws Exception {
for (int i = 0; i < 5; i++) {
this.output.send(new GenericMessage<String>("fake message"));
}
Map<String, Object> headers = stubHeaders(1, 2, 1);
this.groupTimeoutAggregatorInput.send(new GenericMessage<Integer>(1, headers));
//Wait until 'group-timeout' does its stuff.
MessageGroupStore mgs = TestUtils.getPropertyValue(this.context.getBean("gta.handler"), "messageStore",
MessageGroupStore.class);
int n = 0;
while (n++ < 100 && mgs.getMessageGroupCount() > 0) {
Thread.sleep(100);
if (n == 10) {
TestUtils.getPropertyValue(this.output, "queue", Queue.class).clear();
}
}
assertTrue("Group did not complete", n < 100);
Message<?> receive = this.output.receive(10000);
assertNotNull(receive);
assertEquals(Collections.singletonList(1), receive.getPayload());
assertNull(this.discard.receive(0));
}
@Test
public void testGroupTimeoutExpressionScheduling() throws Exception {
// Since group-timeout-expression="size() >= 2 ? 100 : -1". The first message won't be scheduled to 'forceComplete'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(1, stubHeaders(1, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
// As far as 'group.size() >= 2' it will be scheduled to 'forceComplete'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(2, stubHeaders(2, 6, 1)));
assertNull(this.output.receive(0));
Message<?> receive = this.output.receive(10000);
assertNotNull(receive);
assertEquals(2, ((Collection<?>) receive.getPayload()).size());
assertNull(this.discard.receive(0));
// The same with these three messages
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(3, stubHeaders(3, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(4, stubHeaders(4, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(5, stubHeaders(5, 6, 1)));
assertNull(this.output.receive(0));
receive = this.output.receive(10000);
assertNotNull(receive);
assertEquals(3, ((Collection<?>) receive.getPayload()).size());
assertNull(this.discard.receive(0));
// The last message in the sequence - normal release by provided 'ReleaseStrategy'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(6, stubHeaders(6, 6, 1)));
receive = this.output.receive(10000);
assertNotNull(receive);
assertEquals(1, ((Collection<?>) receive.getPayload()).size());
assertNull(this.discard.receive(0));
}
@Test
public void testZeroGroupTimeoutExpressionScheduling() throws Exception {
try {
this.output.purge(null);
this.errors.purge(null);
GenericMessage<String> message = new GenericMessage<String>("foo");
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.zeroGroupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(1, stubHeaders(1, 2, 1)));
ErrorMessage em = (ErrorMessage) this.errors.receive(10000);
assertNotNull(em);
assertThat(em.getPayload().getMessage().toLowerCase(),
containsString("failed to send message to channel 'output' within timeout: 10"));
}
finally {
this.output.purge(null);
this.errors.purge(null);
}
}
// configured in context associated with this test
public static class SummingAggregator {
public Integer sum(List<Integer> numbers) {
int result = 0;
for (Integer number : numbers) {
result += number;
}
return result;
}
}
private Map<String, Object> stubHeaders(int sequenceNumber, int sequenceSize, int correlationId) {
Map<String, Object> headers = new HashMap<String, Object>();
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, sequenceNumber);
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, sequenceSize);
headers.put(IntegrationMessageHeaderAccessor.CORRELATION_ID, correlationId);
return headers;
}
}