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

@@ -3,21 +3,26 @@
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/file
https://www.springframework.org/schema/integration/file/spring-integration-file.xsd">
https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<context:property-placeholder/>
<util:constant id="temporaryFolder"
static-field="org.springframework.integration.file.config.FileOutboundGatewayParserTests.tempFolder"/>
<int-file:outbound-gateway id="ordered"
request-channel="someChannel" reply-timeout="777" directory="${java.io.tmpdir}"
request-channel="someChannel" reply-timeout="777" directory="#{temporaryFolder.root}"
auto-startup="false" order="777" filename-generator-expression="'foo.txt'"/>
<int-file:outbound-gateway id="gatewayWithDirectoryExpression"
request-channel="someChannel" directory-expression="'build/foo'"
request-channel="someChannel" directory-expression="temporaryFolder.root"
auto-startup="false" order="777" filename-generator-expression="'foo.txt'"
requires-reply="false">
<int-file:request-handler-advice-chain>
@@ -28,32 +33,32 @@
<int-file:outbound-gateway id="gatewayWithReplaceMode"
request-channel="gatewayWithReplaceModeChannel"
filename-generator-expression="'fileToAppend.txt'" mode="REPLACE"
directory="test" requires-reply="false"/>
directory="#{temporaryFolder.root}" requires-reply="false"/>
<int-file:outbound-gateway id="gatewayWithAppendMode"
request-channel="gatewayWithAppendModeChannel"
filename-generator-expression="'fileToAppend.txt'" mode="APPEND"
directory="test"/>
directory="#{temporaryFolder.root}"/>
<int-file:outbound-gateway id="gatewayWithFailMode"
request-channel="gatewayWithFailModeChannel"
filename-generator-expression="'fileToAppend.txt'" mode="FAIL"
directory="test"/>
directory="#{temporaryFolder.root}"/>
<int-file:outbound-gateway id="gatewayWithIgnoreMode"
request-channel="gatewayWithIgnoreModeChannel"
filename-generator-expression="'fileToAppend.txt'" mode="IGNORE"
directory="test"/>
directory="#{temporaryFolder.root}"/>
<int-file:outbound-gateway id="gatewayWithFailModeLowercase"
request-channel="gatewayWithFailModeLowercaseChannel"
filename-generator-expression="'fileToAppend.txt'" mode="fail"
directory="test"/>
directory="#{temporaryFolder.root}"/>
<int-file:outbound-gateway id="gatewayWithAppendNewLine"
request-channel="gatewayWithAppendNewLineChannel"
filename-generator-expression="'fileToAppend.txt'"
append-new-line="true"
directory="test"/>
directory="#{temporaryFolder.root}"/>
</beans:beans>

View File

@@ -17,11 +17,14 @@
package org.springframework.integration.file.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.io.File;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
@@ -39,8 +42,8 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.FileCopyUtils;
/**
@@ -49,10 +52,13 @@ import org.springframework.util.FileCopyUtils;
* @author Artem Bilan
* @author Tony Falabella
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class FileOutboundGatewayParserTests {
@ClassRule
public static final TemporaryFolder tempFolder = new TemporaryFolder();
@Autowired
private EventDrivenConsumer ordered;
@@ -83,6 +89,11 @@ public class FileOutboundGatewayParserTests {
private static volatile int adviceCalled;
@Before
public void setup() {
tempFolder.delete();
}
@Test
public void checkOrderedGateway() {
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(ordered);
@@ -109,7 +120,7 @@ public class FileOutboundGatewayParserTests {
FileWritingMessageHandler handler =
TestUtils.getPropertyValue(gatewayWithDirectoryExpression, "handler", FileWritingMessageHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "destinationDirectoryExpression", Expression.class)
.getExpressionString()).isEqualTo("'build/foo'");
.getExpressionString()).isEqualTo("temporaryFolder.root");
handler.handleMessage(new GenericMessage<>("foo"));
assertThat(adviceCalled).isEqualTo(1);
}
@@ -129,11 +140,7 @@ public class FileOutboundGatewayParserTests {
messagingTemplate.setDefaultDestination(this.gatewayWithIgnoreModeChannel);
final String expectedFileContent = "Initial File Content:";
final File testFile = new File("test/fileToAppend.txt");
if (testFile.exists()) {
testFile.delete();
}
final File testFile = new File(tempFolder.getRoot(), "fileToAppend.txt");
messagingTemplate.sendAndReceive(new GenericMessage<>("Initial File Content:"));
@@ -165,29 +172,16 @@ public class FileOutboundGatewayParserTests {
String expectedFileContent = "Initial File Content:";
File testFile = new File("test/fileToAppend.txt");
if (testFile.exists()) {
testFile.delete();
}
File testFile = new File(tempFolder.getRoot(), "fileToAppend.txt");
messagingTemplate.sendAndReceive(new GenericMessage<>("Initial File Content:"));
final String actualFileContent = new String(FileCopyUtils.copyToByteArray(testFile));
assertThat(actualFileContent).isEqualTo(expectedFileContent);
try {
messagingTemplate.sendAndReceive(new GenericMessage<>("String content:"));
}
catch (MessageHandlingException e) {
assertThat(e.getMessage()).startsWith("The destination file already exists at '");
return;
}
fail("Was expecting a MessageHandlingException to be thrown.");
assertThatExceptionOfType(MessageHandlingException.class)
.isThrownBy(() -> messagingTemplate.sendAndReceive(new GenericMessage<>("String content:")))
.withMessageStartingWith("The destination file already exists at '");
}
/**
@@ -200,35 +194,21 @@ public class FileOutboundGatewayParserTests {
*/
@Test
public void gatewayWithFailModeLowercase() throws Exception {
final MessagingTemplate messagingTemplate = new MessagingTemplate();
messagingTemplate.setDefaultDestination(this.gatewayWithFailModeLowercaseChannel);
String expectedFileContent = "Initial File Content:";
File testFile = new File("test/fileToAppend.txt");
if (testFile.exists()) {
testFile.delete();
}
File testFile = new File(tempFolder.getRoot(), "fileToAppend.txt");
messagingTemplate.sendAndReceive(new GenericMessage<>("Initial File Content:"));
final String actualFileContent = new String(FileCopyUtils.copyToByteArray(testFile));
assertThat(actualFileContent).isEqualTo(expectedFileContent);
try {
messagingTemplate.sendAndReceive(new GenericMessage<>("String content:"));
}
catch (MessageHandlingException e) {
assertThat(e.getMessage()).startsWith("The destination file already exists at '");
return;
}
fail("Was expecting a MessageHandlingException to be thrown.");
assertThatExceptionOfType(MessageHandlingException.class)
.isThrownBy(() -> messagingTemplate.sendAndReceive(new GenericMessage<>("String content:")))
.withMessageStartingWith("The destination file already exists at '");
}
/**
@@ -243,17 +223,12 @@ public class FileOutboundGatewayParserTests {
*/
@Test
public void gatewayWithAppendMode() throws Exception {
final MessagingTemplate messagingTemplate = new MessagingTemplate();
messagingTemplate.setDefaultDestination(this.gatewayWithAppendModeChannel);
String expectedFileContent = "Initial File Content:String content:";
File testFile = new File("test/fileToAppend.txt");
if (testFile.exists()) {
testFile.delete();
}
File testFile = new File(tempFolder.getRoot(), "fileToAppend.txt");
messagingTemplate.sendAndReceive(new GenericMessage<>("Initial File Content:"));
Message<?> m = messagingTemplate.sendAndReceive(new GenericMessage<>("String content:"));
@@ -280,7 +255,6 @@ public class FileOutboundGatewayParserTests {
*/
@Test
public void gatewayWithReplaceMode() throws Exception {
assertThat(TestUtils.getPropertyValue(this.gatewayWithReplaceModeHandler, "requiresReply", Boolean.class))
.isFalse();
@@ -289,11 +263,7 @@ public class FileOutboundGatewayParserTests {
String expectedFileContent = "String content:";
File testFile = new File("test/fileToAppend.txt");
if (testFile.exists()) {
testFile.delete();
}
File testFile = new File(tempFolder.getRoot(), "fileToAppend.txt");
messagingTemplate.sendAndReceive(new GenericMessage<>("Initial File Content:"));
Message<?> m = messagingTemplate.sendAndReceive(new GenericMessage<>("String content:"));