INT-3960: File Outbound - Preserve Timestamp

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

Polishing - PR Comments

Fix errors according Checkstyle report.
Wrap `warn()` to `isWarnEnable()`.
Polishing for `setPreserveTimestamp()` JavaDocs.
This commit is contained in:
Gary Russell
2016-03-23 12:48:45 -04:00
committed by Artem Bilan
parent ad0839da8b
commit 516846b750
6 changed files with 82 additions and 7 deletions

View File

@@ -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";
}

View File

@@ -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);

View File

@@ -509,6 +509,17 @@ Only files matching this regular expression will be picked up by this adapter.
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="preserve-timestamp" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
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.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
</xsd:complexType>

View File

@@ -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;
}
}

View File

@@ -105,7 +105,7 @@ to, say, network glitches.
</bean>
----
*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

View File

@@ -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 <<file-flushing>> for more information.
===== Preserving Timestamps
The outbound channel adapter can now be configured to set the destination file's lastmodified timestamp.
See <<file-timestamps>> for more information.
==== AMQP Changes
===== Content Type Message Converter