diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java
index c88f2a84f5..b9e942a742 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -39,4 +39,6 @@ public abstract class FileHeaders {
public static final String RENAME_TO = PREFIX + "renameTo";
+ public static final String SET_MODIFIED = PREFIX + "setModified";
+
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java
index 5a914d8f6a..06657e0779 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java
@@ -152,6 +152,8 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
private volatile MessageFlushPredicate flushPredicate = new DefaultFlushPredicate();
+ private volatile boolean preserveTimestamp;
+
/**
* Constructor which sets the {@link #destinationDirectoryExpression} using
* a {@link LiteralExpression}.
@@ -327,6 +329,19 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
this.flushPredicate = flushPredicate;
}
+ /**
+ * Set to true to preserve the destination file timestamp. If true and
+ * the payload is a {@link File}, the payload's {@code lastModified} time will be
+ * transferred to the destination file. For other payloads, the
+ * {@link FileHeaders#SET_MODIFIED} header {@value FileHeaders#SET_MODIFIED}
+ * will be used if present and it's a {@link Number}.
+ * @param preserveTimestamp the preserveTimestamp to set.
+ * @since 4.3
+ */
+ public void setPreserveTimestamp(boolean preserveTimestamp) {
+ this.preserveTimestamp = preserveTimestamp;
+ }
+
@Override
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
@@ -408,6 +423,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
if (!ignore) {
try {
+ Object timestamp = requestMessage.getHeaders().get(FileHeaders.SET_MODIFIED);
if (!resultFile.exists() &&
generatedFileName.replaceAll("/", Matcher.quoteReplacement(File.separator))
.contains(File.separator)) {
@@ -415,6 +431,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
}
if (payload instanceof File) {
resultFile = handleFileMessage((File) payload, tempFile, resultFile);
+ timestamp = ((File) payload).lastModified();
}
else if (payload instanceof InputStream) {
resultFile = handleInputStreamMessage((InputStream) payload, originalFileFromHeader, tempFile,
@@ -432,6 +449,17 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
throw new IllegalArgumentException(
"unsupported Message payload type [" + payload.getClass().getName() + "]");
}
+ if (this.preserveTimestamp) {
+ if (timestamp instanceof Number) {
+ resultFile.setLastModified(((Number) timestamp).longValue());
+ }
+ else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not set lastModified, header " + FileHeaders.SET_MODIFIED
+ + " must be a Number, not " + timestamp.getClass());
+ }
+ }
+ }
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "failed to write Message payload to file", e);
diff --git a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.3.xsd b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.3.xsd
index 836b693756..3254cd0583 100644
--- a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.3.xsd
+++ b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.3.xsd
@@ -509,6 +509,17 @@ Only files matching this regular expression will be picked up by this adapter.
+
+
+
+ Specify whether to preserve the modified timestamp from the source
+ file on the destination file after copying. Applies to 'java.io.File' payloads.
+ For other payload types, the optional 'file_setModified' header will be used,
+ if present (and a 'Number'), to set the lastModified time.
+ By default, the timestamp will NOT be preserved.
+
+
+
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
index 67f77d613b..04d87a3c01 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
@@ -120,13 +120,18 @@ public class FileWritingMessageHandlerTests {
@Test
public void stringPayloadCopiedToNewFile() throws Exception {
- Message> message = MessageBuilder.withPayload(SAMPLE_CONTENT).build();
+ long lastModified = 1234000L;
+ Message> message = MessageBuilder.withPayload(SAMPLE_CONTENT)
+ .setHeader(FileHeaders.SET_MODIFIED, lastModified)
+ .build();
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setOutputChannel(output);
+ handler.setPreserveTimestamp(true);
handler.handleMessage(message);
Message> result = output.receive(0);
assertFileContentIsMatching(result);
+ assertLastModifiedIs(result, lastModified);
}
@Test
@@ -184,11 +189,15 @@ public class FileWritingMessageHandlerTests {
@Test
public void filePayloadCopiedToNewFile() throws Exception {
Message> message = MessageBuilder.withPayload(sourceFile).build();
+ long lastModified = 12345000L;
+ sourceFile.setLastModified(lastModified);
QueueChannel output = new QueueChannel();
handler.setOutputChannel(output);
+ handler.setPreserveTimestamp(true);
handler.handleMessage(message);
Message> result = output.receive(0);
assertFileContentIsMatching(result);
+ assertLastModifiedIs(result, lastModified);
}
@Test
@@ -512,10 +521,11 @@ public class FileWritingMessageHandlerTests {
}
void assertFileContentIs(Message> result, String expected) throws IOException {
- assertThat(result, is(notNullValue()));
- assertThat(result.getPayload(), is(instanceOf(File.class)));
- File destFile = (File) result.getPayload();
- assertFileContentIs(destFile, expected);
+ assertFileContentIs(messageToFile(result), expected);
+ }
+
+ void assertLastModifiedIs(Message> result, long expected) {
+ assertThat(messageToFile(result).lastModified(), is(expected));
}
void assertFileContentIs(File destFile, String expected) throws IOException {
@@ -525,4 +535,11 @@ public class FileWritingMessageHandlerTests {
assertThat(new String(destFileContent, DEFAULT_ENCODING), is(expected));
}
+ protected File messageToFile(Message> result) {
+ assertThat(result, is(notNullValue()));
+ assertThat(result.getPayload(), is(instanceOf(File.class)));
+ File destFile = (File) result.getPayload();
+ return destFile;
+ }
+
}
diff --git a/src/reference/asciidoc/file.adoc b/src/reference/asciidoc/file.adoc
index a6fa2bfb2f..14e2360a9b 100644
--- a/src/reference/asciidoc/file.adoc
+++ b/src/reference/asciidoc/file.adoc
@@ -105,7 +105,7 @@ to, say, network glitches.
----
-*The directory scanning and polling*
+*Directory scanning and polling*
The `FileReadingMessageSource` doesn't produce messages for files from the directory immediately.
It uses an internal queue for 'eligible files' returned by the `scanner`.
@@ -584,6 +584,18 @@ or `FileWritingMessageHandler.MessageFlushPredicate` implementation.
The predicates are called for each open file.
See the java docs for these interfaces for more information.
+[[file-timestamps]]
+==== File Timestamps
+
+By default, the destination file `lastModified` timestamp will be the time the file was created (except a rename
+in-place will retain the current timestamp).
+Starting with _version 4.3_, you can now configure `preserve-timestamp` (or `setPreserveTimestamp(true)` when using
+Java configuration).
+For `File` payloads, this will transfer the timestamp from the inbound file to the outbound (regardless of whether a
+copy was required).
+For other payloads, if the `FileHeaders.SET_MODIFIED` header (`file_setModified`) is present, it will be used to set
+the destination file's `lastModified` timestamp, as long as the header is a `Number`.
+
[[file-outbound-channel-adapter]]
==== File Outbound Channel Adapter
diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc
index 783274410d..88f4bca943 100644
--- a/src/reference/asciidoc/whats-new.adoc
+++ b/src/reference/asciidoc/whats-new.adoc
@@ -76,6 +76,11 @@ When writing files, you can now specify the buffer size to use.
You can now avoid flushing files when appending and use a number of strategies to flush the data during idle periods.
See <> for more information.
+===== Preserving Timestamps
+
+The outbound channel adapter can now be configured to set the destination file's lastmodified timestamp.
+See <> for more information.
+
==== AMQP Changes
===== Content Type Message Converter