INT-4231: Add FileExistsMode.REPLACE_IF_MODIFIED
JIRA: https://jira.spring.io/browse/INT-4231 For `FileWritingMessageHandler` and (S)FTP outbound gateways, support `FileExistsMode.REPLACE_IF_MODIFIED` to allow overwriting an existing file if the source file modified time is different to the existing file. Polishing
This commit is contained in:
committed by
Artem Bilan
parent
b30f404173
commit
2d4a7cf92f
@@ -213,8 +213,18 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
* <p>
|
||||
* Otherwise the LockRegistry is set to {@link PassThruLockRegistry} which
|
||||
* has no effect.
|
||||
* <p>
|
||||
* With {@link FileExistsMode#REPLACE_IF_MODIFIED}, if the file exists,
|
||||
* it is only replaced if its last modified timestamp is different to the
|
||||
* source; otherwise, the write is ignored. For {@link File} payloads,
|
||||
* the actual timestamp of the {@link File} is compared; for other payloads,
|
||||
* the {@link FileHeaders#SET_MODIFIED} is compared to the existing file.
|
||||
* If the header is missing, or its value is not a {@link Number}, the file
|
||||
* is always replaced. This mode will typically only make sense if
|
||||
* {@link #setPreserveTimestamp(boolean) preserveTimestamp} is true.
|
||||
*
|
||||
* @param fileExistsMode Must not be null
|
||||
* @see #setPreserveTimestamp(boolean)
|
||||
*/
|
||||
public void setFileExistsMode(FileExistsMode fileExistsMode) {
|
||||
|
||||
@@ -426,26 +436,30 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
File tempFile = new File(destinationDirectoryToUse, generatedFileName + this.temporaryFileSuffix);
|
||||
File resultFile = new File(destinationDirectoryToUse, generatedFileName);
|
||||
|
||||
if (FileExistsMode.FAIL.equals(this.fileExistsMode) && resultFile.exists()) {
|
||||
boolean exists = resultFile.exists();
|
||||
if (exists && FileExistsMode.FAIL.equals(this.fileExistsMode)) {
|
||||
throw new MessageHandlingException(requestMessage,
|
||||
"The destination file already exists at '" + resultFile.getAbsolutePath() + "'.");
|
||||
}
|
||||
|
||||
final boolean ignore = FileExistsMode.IGNORE.equals(this.fileExistsMode) &&
|
||||
(resultFile.exists() ||
|
||||
(StringUtils.hasText(this.temporaryFileSuffix) && tempFile.exists()));
|
||||
|
||||
Object timestamp = requestMessage.getHeaders().get(FileHeaders.SET_MODIFIED);
|
||||
if (payload instanceof File) {
|
||||
timestamp = ((File) payload).lastModified();
|
||||
}
|
||||
boolean ignore = (FileExistsMode.IGNORE.equals(this.fileExistsMode)
|
||||
&& (exists || (StringUtils.hasText(this.temporaryFileSuffix) && tempFile.exists())))
|
||||
|| ((exists && FileExistsMode.REPLACE_IF_MODIFIED.equals(this.fileExistsMode))
|
||||
&& (timestamp instanceof Number
|
||||
&& ((Number) timestamp).longValue() == resultFile.lastModified()));
|
||||
if (!ignore) {
|
||||
try {
|
||||
Object timestamp = requestMessage.getHeaders().get(FileHeaders.SET_MODIFIED);
|
||||
if (!resultFile.exists() &&
|
||||
if (!exists &&
|
||||
generatedFileName.replaceAll("/", Matcher.quoteReplacement(File.separator))
|
||||
.contains(File.separator)) {
|
||||
resultFile.getParentFile().mkdirs(); //NOSONAR - will fail on the writing below
|
||||
}
|
||||
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,
|
||||
@@ -711,6 +725,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
case FAIL:
|
||||
case IGNORE:
|
||||
case REPLACE:
|
||||
case REPLACE_IF_MODIFIED:
|
||||
fileToWriteTo = tempFile;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -282,6 +282,8 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
|
||||
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
|
||||
Assert.isTrue(!FileExistsMode.APPEND.equals(mode) || !this.useTemporaryFileName,
|
||||
"Cannot append when using a temporary file name");
|
||||
Assert.isTrue(!FileExistsMode.REPLACE_IF_MODIFIED.equals(mode),
|
||||
"FilExistsMode.REPLACE_IF_MODIFIED can only be used for local files");
|
||||
final StreamHolder inputStreamHolder = this.payloadToInputStream(message);
|
||||
if (inputStreamHolder != null) {
|
||||
try {
|
||||
|
||||
@@ -587,7 +587,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
else {
|
||||
payload = this.remoteFileTemplate.execute(session1 ->
|
||||
get(requestMessage, session1, remoteDir, remoteFilePath, remoteFilename, true));
|
||||
get(requestMessage, session1, remoteDir, remoteFilePath, remoteFilename, null));
|
||||
}
|
||||
return getMessageBuilderFactory().withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
@@ -831,33 +831,38 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
* Copy a remote file to the configured local directory.
|
||||
*
|
||||
*
|
||||
* @param message The message.
|
||||
* @param session The session.
|
||||
* @param remoteDir The remote directory.
|
||||
* @param remoteFilePath The remote file path.
|
||||
* @param remoteFilename The remote file name.
|
||||
* @param lsFirst true to execute an 'ls' command first.
|
||||
* @param message the message.
|
||||
* @param session the session.
|
||||
* @param remoteDir the remote directory.
|
||||
* @param remoteFilePath the remote file path.
|
||||
* @param remoteFilename the remote file name.
|
||||
* @param fileInfoParam the remote file info; if null we will execute an 'ls' command
|
||||
* first.
|
||||
* @return The file.
|
||||
* @throws IOException Any IOException.
|
||||
*/
|
||||
protected File get(Message<?> message, Session<F> session, String remoteDir, String remoteFilePath,
|
||||
String remoteFilename, boolean lsFirst) throws IOException {
|
||||
F[] files = null;
|
||||
if (lsFirst) {
|
||||
files = session.list(remoteFilePath);
|
||||
String remoteFilename, F fileInfoParam) throws IOException {
|
||||
F fileInfo = fileInfoParam;
|
||||
if (fileInfo == null) {
|
||||
F[] files = session.list(remoteFilePath);
|
||||
if (files == null) {
|
||||
throw new MessagingException("Session returned null when listing " + remoteFilePath);
|
||||
}
|
||||
if (files.length != 1 || isDirectory(files[0]) || isLink(files[0])) {
|
||||
if (files.length != 1 || files[0] == null || isDirectory(files[0]) || isLink(files[0])) {
|
||||
throw new MessagingException(remoteFilePath + " is not a file");
|
||||
}
|
||||
fileInfo = files[0];
|
||||
}
|
||||
File localFile =
|
||||
new File(generateLocalDirectory(message, remoteDir), generateLocalFileName(message, remoteFilename));
|
||||
FileExistsMode fileExistsMode = this.fileExistsMode;
|
||||
boolean appending = FileExistsMode.APPEND.equals(fileExistsMode);
|
||||
boolean replacing = FileExistsMode.REPLACE.equals(fileExistsMode);
|
||||
if (!localFile.exists() || appending || replacing) {
|
||||
boolean exists = localFile.exists();
|
||||
boolean replacing = FileExistsMode.REPLACE.equals(fileExistsMode)
|
||||
|| (exists && FileExistsMode.REPLACE_IF_MODIFIED.equals(fileExistsMode)
|
||||
&& localFile.lastModified() != getModified(fileInfo));
|
||||
if (!exists || appending || replacing) {
|
||||
OutputStream outputStream;
|
||||
String tempFileName = localFile.getAbsolutePath() + this.remoteFileTemplate.getTemporaryFileSuffix();
|
||||
File tempFile = new File(tempFileName);
|
||||
@@ -898,11 +903,14 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
if (!appending && !tempFile.renameTo(localFile)) {
|
||||
throw new MessagingException("Failed to rename local file");
|
||||
}
|
||||
if (lsFirst && this.options.contains(Option.PRESERVE_TIMESTAMP)) {
|
||||
localFile.setLastModified(getModified(files[0]));
|
||||
if (this.options.contains(Option.PRESERVE_TIMESTAMP)) {
|
||||
localFile.setLastModified(getModified(fileInfo));
|
||||
}
|
||||
}
|
||||
else if (FileExistsMode.IGNORE != fileExistsMode) {
|
||||
else if (FileExistsMode.REPLACE_IF_MODIFIED.equals(fileExistsMode)) {
|
||||
logger.debug("Local file '" + localFile + "' has the same modified timestamp, ignored");
|
||||
}
|
||||
else if (!FileExistsMode.IGNORE.equals(fileExistsMode)) {
|
||||
throw new MessageHandlingException(message, "Local file " + localFile + " already exists");
|
||||
}
|
||||
else {
|
||||
@@ -955,10 +963,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
String fileName = this.getRemoteFilename(fullFileName);
|
||||
String actualRemoteDirectory = this.getRemoteDirectory(fullFileName, fileName);
|
||||
File file = get(message, session, actualRemoteDirectory,
|
||||
fullFileName, fileName, false);
|
||||
if (this.options.contains(Option.PRESERVE_TIMESTAMP)) {
|
||||
file.setLastModified(getModified(lsEntry.getFileInfo()));
|
||||
}
|
||||
fullFileName, fileName, lsEntry.getFileInfo());
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
@@ -1001,10 +1006,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
String fileName = this.getRemoteFilename(fullFileName);
|
||||
String actualRemoteDirectory = this.getRemoteDirectory(fullFileName, fileName);
|
||||
File file = get(message, session, actualRemoteDirectory,
|
||||
fullFileName, fileName, false);
|
||||
if (this.options.contains(Option.PRESERVE_TIMESTAMP)) {
|
||||
file.setLastModified(getModified(lsEntry.getFileInfo()));
|
||||
}
|
||||
fullFileName, fileName, lsEntry.getFileInfo());
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -55,7 +55,14 @@ public enum FileExistsMode {
|
||||
/**
|
||||
* If the file already exists, replace it.
|
||||
*/
|
||||
REPLACE;
|
||||
REPLACE,
|
||||
|
||||
/**
|
||||
* If the file already exists, replace it only if the last modified time
|
||||
* is different. Only applies to local files.
|
||||
* @since 5.0
|
||||
*/
|
||||
REPLACE_IF_MODIFIED;
|
||||
|
||||
/**
|
||||
* For a given non-null and not-empty input string, this method returns the
|
||||
|
||||
@@ -828,6 +828,15 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="REPLACE_IF_MODIFIED">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
If the local file already exists, it will be overwritten only
|
||||
if the last modified timestamp does not match the source
|
||||
timestamp. Only applies to local files.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="APPEND">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
@@ -1004,6 +1013,12 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
|
||||
This is the default behavior when writing files. If the
|
||||
target file already exists, it will be overwritten.
|
||||
|
||||
REPLACE_IF_MODIFIED:
|
||||
|
||||
If the local file already exists, it will be overwritten only
|
||||
if the last modified timestamp does not match the source
|
||||
timestamp. Only applies to local files.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
|
||||
@@ -518,6 +518,61 @@ public class FileWritingMessageHandlerTests {
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceIfDifferent() throws IOException {
|
||||
QueueChannel output = new QueueChannel();
|
||||
this.handler.setOutputChannel(output);
|
||||
this.handler.setPreserveTimestamp(true);
|
||||
this.handler.setFileExistsMode(FileExistsMode.REPLACE_IF_MODIFIED);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload("foo")
|
||||
.setHeader(FileHeaders.FILENAME, "replaceIfDifferent.txt")
|
||||
.setHeader(FileHeaders.SET_MODIFIED, 42_000_000)
|
||||
.build());
|
||||
Message<?> result = output.receive(0);
|
||||
assertFileContentIs(result, "foo");
|
||||
assertLastModifiedIs(result, 42_000_000);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload("bar")
|
||||
.setHeader(FileHeaders.FILENAME, "replaceIfDifferent.txt")
|
||||
.setHeader(FileHeaders.SET_MODIFIED, 42_000_000)
|
||||
.build());
|
||||
result = output.receive(0);
|
||||
assertFileContentIs(result, "foo"); // no overwrite - timestamp same
|
||||
assertLastModifiedIs(result, 42_000_000);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload("bar")
|
||||
.setHeader(FileHeaders.FILENAME, "replaceIfDifferent.txt")
|
||||
.setHeader(FileHeaders.SET_MODIFIED, 43_000_000)
|
||||
.build());
|
||||
result = output.receive(0);
|
||||
assertFileContentIs(result, "bar");
|
||||
assertLastModifiedIs(result, 43_000_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceIfDifferentFile() throws IOException {
|
||||
File file = new File(this.temp.newFolder(), "foo.txt");
|
||||
FileCopyUtils.copy("foo".getBytes(), new FileOutputStream(file));
|
||||
file.setLastModified(42_000_000);
|
||||
QueueChannel output = new QueueChannel();
|
||||
this.handler.setOutputChannel(output);
|
||||
this.handler.setPreserveTimestamp(true);
|
||||
this.handler.setFileExistsMode(FileExistsMode.REPLACE_IF_MODIFIED);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload(file).build());
|
||||
Message<?> result = output.receive(0);
|
||||
assertFileContentIs(result, "foo");
|
||||
assertLastModifiedIs(result, 42_000_000);
|
||||
FileCopyUtils.copy("bar".getBytes(), new FileOutputStream(file));
|
||||
file.setLastModified(42_000_000);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload(file).build());
|
||||
result = output.receive(0);
|
||||
assertFileContentIs(result, "foo"); // no overwrite - timestamp same
|
||||
assertLastModifiedIs(result, 42_000_000);
|
||||
file.setLastModified(43_000_000);
|
||||
this.handler.handleMessage(MessageBuilder.withPayload(file).build());
|
||||
result = output.receive(0);
|
||||
assertFileContentIs(result, "bar");
|
||||
assertLastModifiedIs(result, 43_000_000);
|
||||
}
|
||||
|
||||
void assertFileContentIsMatching(Message<?> result) throws IOException {
|
||||
assertFileContentIs(result, SAMPLE_CONTENT);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
command="mget"
|
||||
expression="payload"
|
||||
command-options="-R -P"
|
||||
mode="REPLACE_IF_MODIFIED"
|
||||
filter="starDotTxtFilter"
|
||||
local-directory-expression="@extraConfig.targetLocalDirectoryName + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
|
||||
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -48,6 +49,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.hamcrest.Matchers;
|
||||
@@ -247,9 +249,11 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testInt3172LocalDirectoryExpressionMGETRecursive() {
|
||||
public void testInt3172LocalDirectoryExpressionMGETRecursive() throws IOException {
|
||||
String dir = "ftpSource/";
|
||||
long modified = setModifiedOnSource1();
|
||||
File secondRemote = new File(getSourceRemoteDirectory(), "ftpSource2.txt");
|
||||
secondRemote.setLastModified(System.currentTimeMillis() - 1_000_000);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
|
||||
Message<?> result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
@@ -268,6 +272,30 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
assertThat(localFiles.get(2).getPath().replaceAll(quoteReplacement(File.separator), "/"),
|
||||
containsString(dir + "subFtpSource"));
|
||||
|
||||
File secondTarget = new File(getTargetLocalDirectory() + File.separator + "ftpSource", "localTarget2.txt");
|
||||
ByteArrayOutputStream remoteContents = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondRemote, remoteContents);
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
String localAsString = new String(localContents.toByteArray());
|
||||
assertEquals(new String(remoteContents.toByteArray()), localAsString);
|
||||
long oldLastModified = secondRemote.lastModified();
|
||||
FileUtils.copyInputStreamToFile(new ByteArrayInputStream("junk".getBytes()), secondRemote);
|
||||
long newLastModified = secondRemote.lastModified();
|
||||
secondRemote.setLastModified(oldLastModified);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
|
||||
this.output.receive(0);
|
||||
localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
assertEquals(localAsString, new String(localContents.toByteArray()));
|
||||
secondRemote.setLastModified(newLastModified);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
|
||||
this.output.receive(0);
|
||||
localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
assertEquals("junk", new String(localContents.toByteArray()));
|
||||
// restore the remote file contents
|
||||
FileUtils.copyInputStreamToFile(new ByteArrayInputStream(localAsString.getBytes()), secondRemote);
|
||||
}
|
||||
|
||||
private long setModifiedOnSource1() {
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
||||
</log4j:configuration>
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
||||
</log4j:configuration>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
command="mget"
|
||||
expression="payload"
|
||||
command-options="-R -P"
|
||||
mode="REPLACE_IF_MODIFIED"
|
||||
filter="dotStarDotTxtFilter"
|
||||
local-directory-expression="@extraConfig.targetLocalDirectoryName + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('sftpSource', 'localTarget')"
|
||||
|
||||
@@ -30,6 +30,7 @@ import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -40,6 +41,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -208,9 +210,11 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testInt3172LocalDirectoryExpressionMGETRecursive() {
|
||||
public void testInt3172LocalDirectoryExpressionMGETRecursive() throws IOException {
|
||||
String dir = "sftpSource/";
|
||||
long modified = setModifiedOnSource1();
|
||||
File secondRemote = new File(getSourceRemoteDirectory(), "sftpSource2.txt");
|
||||
secondRemote.setLastModified(System.currentTimeMillis() - 1_000_000);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
Message<?> result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
@@ -229,6 +233,30 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(localFiles.get(2).getPath().replaceAll(quoteReplacement(File.separator), "/"),
|
||||
containsString(dir + "subSftpSource"));
|
||||
|
||||
File secondTarget = new File(getTargetLocalDirectory() + File.separator + "sftpSource", "localTarget2.txt");
|
||||
ByteArrayOutputStream remoteContents = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondRemote, remoteContents);
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
String localAsString = new String(localContents.toByteArray());
|
||||
assertEquals(new String(remoteContents.toByteArray()), localAsString);
|
||||
long oldLastModified = secondRemote.lastModified();
|
||||
FileUtils.copyInputStreamToFile(new ByteArrayInputStream("junk".getBytes()), secondRemote);
|
||||
long newLastModified = secondRemote.lastModified();
|
||||
secondRemote.setLastModified(oldLastModified);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
this.output.receive(0);
|
||||
localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
assertEquals(localAsString, new String(localContents.toByteArray()));
|
||||
secondRemote.setLastModified(newLastModified);
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
this.output.receive(0);
|
||||
localContents = new ByteArrayOutputStream();
|
||||
FileUtils.copyFile(secondTarget, localContents);
|
||||
assertEquals("junk", new String(localContents.toByteArray()));
|
||||
// restore the remote file contents
|
||||
FileUtils.copyInputStreamToFile(new ByteArrayInputStream(localAsString.getBytes()), secondRemote);
|
||||
}
|
||||
|
||||
private long setModifiedOnSource1() {
|
||||
|
||||
@@ -617,6 +617,7 @@ This behavior, though, can be changed by setting the _mode_ attribute on the res
|
||||
The following options exist:
|
||||
|
||||
* REPLACE (Default)
|
||||
* REPLACE_IF_MODIFIED
|
||||
* APPEND
|
||||
* APPEND_NO_FLUSH
|
||||
* FAIL
|
||||
@@ -631,6 +632,13 @@ _REPLACE_
|
||||
If the target file already exists, it will be overwritten.
|
||||
If the _mode_ attribute is not specified, then this is the default behavior when writing files.
|
||||
|
||||
_REPLACE_IF_MODIFIED_
|
||||
|
||||
If the target file already exists, it will be overwritten only if the last modified timestamp is different to the source file.
|
||||
For `File` payloads, the payload `lastModified` time is compared to the existing file.
|
||||
For other payloads, the `FileHeaders.SET_MODIFIED` (`file_setModified`) header is compared to the existing file.
|
||||
If the header is missing, or has a value that is not a `Number`, the file is always replaced.
|
||||
|
||||
_APPEND_
|
||||
|
||||
This mode allows you to append Message content to the existing file instead of creating a new file each time.
|
||||
|
||||
@@ -848,12 +848,15 @@ _mget_ retrieves multiple remote files based on a pattern and supports the follo
|
||||
|
||||
* -P - preserve the timestamps of the remote files
|
||||
|
||||
* -R - retrieve the entire directory tree recursively
|
||||
|
||||
* -x - Throw an exception if no files match the pattern (otherwise an empty list is returned)
|
||||
|
||||
The message payload resulting from an _mget_ operation is a `List<File>` object - a List of File objects, each representing a retrieved file.
|
||||
|
||||
The remote directory is provided in the `file_remoteDirectory` header, and the pattern for the file names is provided in the `file_remoteFile` header.
|
||||
The expression used to determine the remote path should produce a result that ends with `*` - e.g. `foo/*` will fetch the complete tree under `foo`.
|
||||
|
||||
Starting with _version 5.0_, a recursive `MGET`, combined with the new `FileExistsMode.REPLACE_IF_MODIFIED` mode, can be used to periodically synchronize an entire remote directory tree locally.
|
||||
|
||||
[NOTE]
|
||||
.Notes for when using recursion (`-R`)
|
||||
|
||||
@@ -410,4 +410,4 @@ private MongoDbOutboundGatewaySpec collectionCallbackOutboundGateway() {
|
||||
.collectionCallback(MongoCollection::count)
|
||||
.collectionName("foo");
|
||||
}
|
||||
----
|
||||
----
|
||||
|
||||
@@ -183,4 +183,4 @@ public interface SecuredGateway {
|
||||
Future<String> send(String payload);
|
||||
|
||||
}
|
||||
----
|
||||
----
|
||||
|
||||
@@ -866,12 +866,15 @@ _mget_ retrieves multiple remote files based on a pattern and supports the follo
|
||||
|
||||
* -P - preserve the timestamps of the remote files
|
||||
|
||||
* -R - retrieve the entire directory tree recursively
|
||||
|
||||
* -x - Throw an exception if no files match the pattern (otherwise an empty list is returned)
|
||||
|
||||
The message payload resulting from an _mget_ operation is a `List<File>` object - a List of File objects, each representing a retrieved file.
|
||||
|
||||
The remote directory is provided in the `file_remoteDirectory` header, and the pattern for the filenames is provided in the `file_remoteFile` header.
|
||||
The expression used to determine the remote path should produce a result that ends with `*` - e.g. `foo/*` will fetch the complete tree under `foo`.
|
||||
|
||||
Starting with _version 5.0_, a recursive `MGET`, combined with the new `FileExistsMode.REPLACE_IF_MODIFIED` mode, can be used to periodically synchronize an entire remote directory tree locally.
|
||||
|
||||
[NOTE]
|
||||
.Notes for when using recursion (`-R`)
|
||||
|
||||
@@ -69,6 +69,8 @@ See <<file-tailing>> for more information.
|
||||
The flush predicates for the `FileWritingMessageHandler` now have an additional parameter.
|
||||
See <<file-flushing>> for more information.
|
||||
|
||||
The file outbound channel adapter (`FileWritingMessageHandler`) now supports the `REPLACE_IF_MODIFIED` `FileExistsMode`.
|
||||
|
||||
==== (S)FTP Changes
|
||||
|
||||
The inbound channel adapters now have a property `max-fetch-size` which is used to limit the number of files fetched during a poll when there are no files currently in the local directory.
|
||||
@@ -77,6 +79,9 @@ The regex and pattern filters can now be configured to always pass directories.
|
||||
This can be useful when using recursion in the outbound gateways.
|
||||
See <<ftp-outbound-gateway>> and <<sftp-outbound-gateway>> for more information.
|
||||
|
||||
The FTP and SFTP outbound gateways now support the `REPLACE_IF_MODIFIED` `FileExistsMode` when fetching remote files.
|
||||
See <<ftp-outbound-gateway>> and <<sftp-outbound-gateway>> for more information.
|
||||
|
||||
==== Integration Properties
|
||||
|
||||
Since _version 4.3.2_ a new `spring.integration.readOnly.headers` global property has been added to customize the list of headers which should not be copied to a newly created `Message` by the `MessageBuilder`.
|
||||
|
||||
Reference in New Issue
Block a user