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:
committed by
Artem Bilan
parent
ad0839da8b
commit
516846b750
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user