INT-2480: Add aggregate headers strategy

JIRA: https://jira.spring.io/browse/INT-2480

* Introduce `headers-function` option into the `aggregator` for merging
and computing headers for the output message based on the completed
group
* Implement a `DefaultAggregateHeadersFunction` and use it in the
`AbstractAggregatingMessageGroupProcessor` for default behavior with
possible injection for any other implementation
* Add `DelegatingMessageGroupProcessor` to wrap any other
`MessageGroupProcessor` implementations with possible usage of the
`headersFunction` if result is not a `Message` or `MessageBuilder`
* Make `AbstractCorrelatingMessageHandler.getOutputProcessor()` as
`public` rto give access to this option from the `AggregatorSpec` to
be able to inject a `headersFunction` in Java DSL configuration
* Add `AbstractIntegrationMessageBuilder.getHeader()` to get access to
some underlying header avoiding extra `Map` in case of `getHeaders()`
* Change a logic in the `AbstractMessageProducingHandler.produceOutput()`
to consult a `reply` for the `replyChannel` as well `routingSlip` header
if the `reply` is a `Message` or `MessageBuilder`
* Introduce a `AbstractMessageProducingHandler.messageBuilderForReply()`
and use it in `AbstractMessageSplitter` to avoid duplication
* Validate a new functionality in tests
* Fix `FileOutboundGatewayParserTests` to rely on the `TemporaryFolder`
to clean up test files after using

* JavaDocs for `DefaultAggregateHeadersFunction`
* Some `router.adoc` polishing

* Fix link to Reactor in the `router.adoc`

* Add docs for new `Function<MessageGroup, Map<String, Object>>` strategy

* Doc polishing.
This commit is contained in:
Artem Bilan
2019-07-02 17:11:53 -04:00
committed by Gary Russell
parent 09c4f03d7c
commit 5a1846cfe5
25 changed files with 536 additions and 219 deletions

View File

@@ -11,7 +11,12 @@
<queue capacity="5" />
</channel>
<aggregator ref="summer" method="sum" input-channel="input" output-channel="output" expression="">
<beans:bean id="headersFunction"
class="org.springframework.integration.aggregator.integration.AggregatorIntegrationTests"
factory-method="firstMessageHeaders"/>
<aggregator ref="summer" method="sum" input-channel="input" output-channel="output" expression=""
headers-function="headersFunction">
<poller task-executor="executor" max-messages-per-poll="5" fixed-delay="20" />
</aggregator>

View File

@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.function.Function;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +33,7 @@ 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.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
@@ -92,6 +94,7 @@ public class AggregatorIntegrationTests {
Message<?> receive = output.receive(10000);
assertThat(receive).isNotNull();
assertThat(receive.getPayload()).isEqualTo(1 + 2 + 3 + 4);
assertThat(receive.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER)).isEqualTo(0);
}
@Test
@@ -245,6 +248,18 @@ public class AggregatorIntegrationTests {
}
// configured in context associated with this test
private Map<String, Object> stubHeaders(int sequenceNumber, int sequenceSize, int correlationId) {
Map<String, Object> headers = new HashMap<>();
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, sequenceNumber);
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, sequenceSize);
headers.put(IntegrationMessageHeaderAccessor.CORRELATION_ID, correlationId);
return headers;
}
public static Function<MessageGroup, Map<String, Object>> firstMessageHeaders() {
return (messageGroup) -> messageGroup.getOne().getHeaders();
}
public static class SummingAggregator {
public Integer sum(List<Integer> numbers) {
int result = 0;
@@ -255,12 +270,6 @@ public class AggregatorIntegrationTests {
}
}
private Map<String, Object> stubHeaders(int sequenceNumber, int sequenceSize, int correlationId) {
Map<String, Object> headers = new HashMap<>();
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, sequenceNumber);
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, sequenceSize);
headers.put(IntegrationMessageHeaderAccessor.CORRELATION_ID, correlationId);
return headers;
}
}

View File

@@ -83,7 +83,7 @@
<channel id="fooChannel"/>
<header-enricher input-channel="routingSlipInput">
<routing-slip value="fooChannel; barExpression; bazRoutingSlip"/>
<routing-slip value="request.headers.replyChannel; fooChannel; barExpression; bazRoutingSlip"/>
</header-enricher>
<header-enricher input-channel="payloadExpressionInput">

View File

@@ -270,9 +270,10 @@ public class HeaderEnricherTests {
@SuppressWarnings("unchecked")
List<Object> routingSlipPath = (List<Object>) ((Map<?, ?>) routingSlip).keySet().iterator().next();
assertThat(routingSlipPath.get(0)).isEqualTo("fooChannel");
assertThat(routingSlipPath.get(1)).isInstanceOf(ExpressionEvaluatingRoutingSlipRouteStrategy.class);
assertThat(routingSlipPath.get(2)).isEqualTo("bazRoutingSlip");
assertThat(routingSlipPath.get(0)).isInstanceOf(ExpressionEvaluatingRoutingSlipRouteStrategy.class);
assertThat(routingSlipPath.get(1)).isEqualTo("fooChannel");
assertThat(routingSlipPath.get(2)).isInstanceOf(ExpressionEvaluatingRoutingSlipRouteStrategy.class);
assertThat(routingSlipPath.get(3)).isEqualTo("bazRoutingSlip");
}

View File

@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -141,9 +142,10 @@ public class CorrelationHandlerTests {
public void testSubscriberAggregateFlow() {
this.subscriberAggregateFlowInput.send(new GenericMessage<>("test"));
Message<?> receive1 = this.subscriberAggregateResult.receive(10000);
assertThat(receive1).isNotNull();
assertThat(receive1.getPayload()).isEqualTo("Hello World!");
Message<?> receive = this.subscriberAggregateResult.receive(10000);
assertThat(receive).isNotNull();
assertThat(receive.getPayload()).isEqualTo("Hello World!");
assertThat(receive.getHeaders().get("foo")).isEqualTo("bar");
}
@@ -274,10 +276,13 @@ public class CorrelationHandlerTests {
@Bean
public IntegrationFlow publishSubscribeAggregateFlow() {
return flow -> flow
.aggregate(a -> a.outputProcessor(g -> g.getMessages()
.stream()
.map(m -> (String) m.getPayload())
.collect(Collectors.joining(" "))))
.aggregate(a -> a
.outputProcessor((group) -> group
.getMessages()
.stream()
.map(m -> (String) m.getPayload())
.collect(Collectors.joining(" ")))
.headersFunction((group) -> Collections.singletonMap("foo", "bar")))
.channel(MessageChannels.queue("subscriberAggregateResult"));
}