diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java index 811fa53248..38e14b2141 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java @@ -46,6 +46,7 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst // configure the InboundFileSynchronizer properties IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "preserve-timestamp"); String remoteFileSeparator = element.getAttribute("remote-file-separator"); synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java index cb42377025..590f3092bc 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java @@ -19,7 +19,6 @@ package org.springframework.integration.file.remote.synchronizer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -50,6 +49,7 @@ import org.springframework.util.ObjectUtils; * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public abstract class AbstractInboundFileSynchronizer implements InboundFileSynchronizer, @@ -89,6 +89,12 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS */ private volatile boolean deleteRemoteFiles; + /** + * Should we transfer the remote file timestamp + * to the local file? By default this is false. + */ + private volatile boolean preserveTimestamp; + /** * Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances. */ @@ -127,6 +133,10 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS this.deleteRemoteFiles = deleteRemoteFiles; } + public void setPreserveTimestamp(boolean preserveTimestamp) { + this.preserveTimestamp = preserveTimestamp; + } + @Override public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; @@ -149,7 +159,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS Session session = null; try { session = this.sessionFactory.getSession(); - Assert.state(session != null, "failed to acquire a Session"); + Assert.notNull(session, "failed to acquire a Session"); F[] files = session.list(this.remoteDirectory); if (!ObjectUtils.isEmpty(files)) { Collection filteredFiles = this.filterFiles(files); @@ -192,7 +202,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS if (!localFile.exists()) { String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix; File tempFile = new File(tempFileName); - InputStream inputStream = null; FileOutputStream fileOutputStream = new FileOutputStream(tempFile); try { session.read(remoteFilePath, fileOutputStream); @@ -206,13 +215,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } - catch (Exception ignored1) { - } try { fileOutputStream.close(); } @@ -228,6 +230,9 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } } + if (this.preserveTimestamp) { + localFile.setLastModified(getModified(remoteFile)); + } } } @@ -242,4 +247,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS protected abstract String getFilename(F file); + protected abstract long getModified(F file); + } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java index a5b942cae6..1bcca21604 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -28,6 +28,7 @@ import org.springframework.integration.file.remote.synchronizer.AbstractInboundF * @author Iwein Fuld * @author Josh Long * @author Mark Fisher + * @author Artem Bilan * @since 2.0 */ public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer { @@ -50,4 +51,9 @@ public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer< return (file != null ? file.getName() : null); } + @Override + protected long getModified(FTPFile file) { + return file.getTimestamp().getTimeInMillis(); + } + } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd index bfd362abb3..afaaa5e246 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd @@ -235,7 +235,7 @@ - + Specify whether to delete the remote source @@ -245,6 +245,16 @@ + + + + Specify whether to preserve the modified timestamp from the remote source + file on the local file after copying. + By default, the remote timestamp will NOT be + preserved. + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml index 6d2732ec76..4b6f73f98f 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml @@ -7,20 +7,21 @@ http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd"> - - + - + @@ -48,13 +49,13 @@ - + @@ -76,7 +77,7 @@ - + @@ -89,7 +90,7 @@ - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java index 1d1a62082a..4d40e37900 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java @@ -77,6 +77,7 @@ public class FtpInboundChannelAdapterParserTests { FtpInboundFileSynchronizer fisync = (FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer"); assertNotNull(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression")); + assertTrue(TestUtils.getPropertyValue(fisync, "preserveTimestamp", Boolean.class)); assertEquals(".foo", TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class)); String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); assertNotNull(remoteFileSeparator); diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java index 96bc62aef5..7106219858 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -30,10 +31,12 @@ import static org.mockito.Mockito.when; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; @@ -51,6 +54,7 @@ import org.springframework.integration.ftp.session.AbstractFtpSessionFactory; * @author Oleg Zhurakousky * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public class FtpInboundRemoteFileSystemSynchronizerTests { @@ -80,6 +84,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { ftpSessionFactory.setHost("foo.com"); FtpInboundFileSynchronizer synchronizer = spy(new FtpInboundFileSynchronizer(ftpSessionFactory)); synchronizer.setDeleteRemoteFiles(true); + synchronizer.setPreserveTimestamp(true); synchronizer.setRemoteDirectory("remote-test-dir"); synchronizer.setFilter(new FtpRegexPatternFileListFilter(".*\\.test$")); synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext()); @@ -98,9 +103,15 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { Message atestFile = ms.receive(); assertNotNull(atestFile); assertEquals("A.TEST.a", atestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message btestFile = ms.receive(); assertNotNull(btestFile); assertEquals("B.TEST.a", btestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message nothing = ms.receive(); assertNull(nothing); @@ -127,6 +138,9 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { FTPFile file = new FTPFile(); file.setName(fileName); file.setType(FTPFile.FILE_TYPE); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, 1); + file.setTimestamp(calendar); ftpFiles.add(file); when(ftpClient.retrieveFile(Mockito.eq("remote-test-dir/" + fileName) , Mockito.any(OutputStream.class))).thenReturn(true); } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java index 1ee8246995..a6f177aa75 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java @@ -46,4 +46,9 @@ public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer return (file != null ? file.getFilename() : null); } + @Override + protected long getModified(LsEntry file) { + return (long) file.getAttrs().getMTime() * 1000; + } + } diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd index 15d09c624f..912fcc0683 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd @@ -236,7 +236,7 @@ - + Specify whether to delete the remote source @@ -246,6 +246,16 @@ + + + + Specify whether to preserve the modified timestamp from the remote source + file on the local file after copying. + By default, the remote timestamp will NOT be + preserved. + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml index 3e461e1db5..e0ffc0b0dc 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml @@ -22,11 +22,11 @@ - + - + @@ -49,14 +49,15 @@ temporary-file-suffix=".bar" comparator="comparator" local-filter="acceptAllFilter" - delete-remote-files="${delete.remote.files}"> + delete-remote-files="${delete.remote.files}" + preserve-timestamp="true"> - + @@ -118,7 +119,7 @@ - + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java index fb60fafd47..69284fd1e5 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java @@ -82,6 +82,7 @@ public class InboundChannelAdapterParserTests { assertNotNull(comparator); SftpInboundFileSynchronizer synchronizer = (SftpInboundFileSynchronizer) TestUtils.getPropertyValue(source, "synchronizer"); assertNotNull(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression")); + assertTrue(TestUtils.getPropertyValue(synchronizer, "preserveTimestamp", Boolean.class)); String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator"); assertEquals(".bar", TestUtils.getPropertyValue(synchronizer, "temporaryFileSuffix", String.class)); assertNotNull(remoteFileSeparator); diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java index dd8842efb7..c8fa674f48 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -29,8 +30,10 @@ import static org.mockito.Mockito.when; import java.io.File; import java.io.FileInputStream; +import java.util.Calendar; import java.util.Vector; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; @@ -49,6 +52,7 @@ import com.jcraft.jsch.SftpATTRS; * @author Oleg Zhurakousky * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public class SftpInboundRemoteFileSystemSynchronizerTests { @@ -81,6 +85,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { SftpInboundFileSynchronizer synchronizer = spy(new SftpInboundFileSynchronizer(ftpSessionFactory)); synchronizer.setDeleteRemoteFiles(true); + synchronizer.setPreserveTimestamp(true); synchronizer.setRemoteDirectory("remote-test-dir"); synchronizer.setFilter(new SftpRegexPatternFileListFilter(".*\\.test$")); synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext()); @@ -93,9 +98,15 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { Message atestFile = ms.receive(); assertNotNull(atestFile); assertEquals("a.test", atestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message btestFile = ms.receive(); assertNotNull(btestFile); assertEquals("b.test", btestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message nothing = ms.receive(); assertNull(nothing); @@ -120,6 +131,10 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { LsEntry lsEntry = mock(LsEntry.class); SftpATTRS attributes = mock(SftpATTRS.class); when(lsEntry.getAttrs()).thenReturn(attributes); + + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, 1); + when(lsEntry.getAttrs().getMTime()).thenReturn(new Long(calendar.getTimeInMillis() / 1000).intValue()); when(lsEntry.getFilename()).thenReturn(fileName); sftpEntries.add(lsEntry); when(channel.get("remote-test-dir/"+fileName)).thenReturn(new FileInputStream("remote-test-dir/" + fileName)); diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 289b0d090a..5cb22379b3 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -248,6 +248,10 @@ to be maintained across JVM executions, a custom filter that retains state, perhaps on the file system, can now be configured. + + Inbound Channel Adapters now support the preserve-timestamp attribute, which + sets the local file modified timestamp to the timestamp from the server (default false). + For more information, see and .