From 5059ecc31835e03393227b4478bb249758751673 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 18 Oct 2011 17:33:20 -0400 Subject: [PATCH] INT-1844 added support for file name generation Added support for generating local file name for FTP and SFTP inbound adapters --- docs/src/reference/docbook/ftp.xml | 10 +++++ docs/src/reference/docbook/sftp.xml | 9 +++++ ...RemoteFileInboundChannelAdapterParser.java | 9 +++++ .../AbstractInboundFileSynchronizer.java | 24 ++++++++++- .../ftp/config/spring-integration-ftp-2.1.xsd | 9 +++++ ...boundChannelAdapterParserTests-context.xml | 1 + .../FtpInboundChannelAdapterParserTests.java | 1 + ...boundRemoteFileSystemSynchronizerTest.java | 40 +++++++++++-------- .../config/spring-integration-sftp-2.1.xsd | 9 +++++ ...boundChannelAdapterParserTests-context.xml | 1 + .../InboundChannelAdapterParserTests.java | 1 + 11 files changed, 96 insertions(+), 18 deletions(-) diff --git a/docs/src/reference/docbook/ftp.xml b/docs/src/reference/docbook/ftp.xml index c3dcc764af..ea8918dbd8 100644 --- a/docs/src/reference/docbook/ftp.xml +++ b/docs/src/reference/docbook/ftp.xml @@ -116,6 +116,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp filename-pattern="*.txt" remote-directory="some/remote/path" remote-file-separator="/" + local-filename-generator-expression="#this.toUpperCase() + '.a'" local-directory="."> ]]> @@ -124,6 +125,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp element while also providing values for various attributes such as local-directory, filename-pattern (which is based on simple pattern matching, not regular expressions), and of course the reference to a session-factory. + + By default the transferred file will carry the same name as the original file. If you want to override this behavior you + can set local-filename-generator-expression attribute which allows you to provide SpEL Expression to generate + the name of the local file. Unlike outbound gateways and adapters where the root object of the SpEL Evaluation Context + is Message, Inbound adapter generates Message based on the transferred file + which means that Message does not exist yet at the time of Expression evaluation. So the root object + of the SpEL Evaluation Context is the original name of the remote file (String) + + Some times file filtering based on the simple pattern specified via filename-pattern attribute might not be sufficient. If this is the case, you can use the filename-regex attribute to specify a Regular Expression diff --git a/docs/src/reference/docbook/sftp.xml b/docs/src/reference/docbook/sftp.xml index 5b73aaa1e9..4408d08c5d 100644 --- a/docs/src/reference/docbook/sftp.xml +++ b/docs/src/reference/docbook/sftp.xml @@ -80,6 +80,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp remote-directory="/foo/bar" local-directory="file:target/foo" auto-create-local-directory="true" + local-filename-generator-expression="#this.toUpperCase() + '.a'" delete-remote-files="false"> ]]> @@ -90,6 +91,14 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp going to be transferred FROM - as well as other attributes including a session-factory reference to the bean we configured earlier. + + By default the transferred file will carry the same name as the original file. If you want to override this behavior you + can set local-filename-generator-expression attribute which allows you to provide SpEL Expression to generate + the name of the local file. Unlike outbound gateways and adapters where the root object of the SpEL Evaluation Context + is Message, Inbound adapter generates Message based on the transferred file + which means that Message does not exist yet at the time of Expression evaluation. So the root object + of the SpEL Evaluation Context is the original name of the remote file (String) + Some times file filtering based on the simple pattern specified via filename-pattern attribute might not be sufficient. If this is the case, you can use the filename-regex attribute to specify a Regular Expression 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 d5c344ef3e..5a7f25108e 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 @@ -21,6 +21,7 @@ import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.util.StringUtils; @@ -54,6 +55,14 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst // configure the InboundFileSynchronizer properties IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files"); + String localFileGeneratorExpression = element.getAttribute("local-filename-generator-expression"); + + if (StringUtils.hasText(localFileGeneratorExpression)){ + BeanDefinitionBuilder localFileGeneratorExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class); + localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression); + synchronizerBuilder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition()); + } + String remoteFileSeparator = element.getAttribute("remote-file-separator"); synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator); IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "temporary-file-suffix"); 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 6b17239b5b..3cb1392186 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 @@ -26,8 +26,9 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.InitializingBean; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.MessagingException; import org.springframework.integration.file.filters.FileListFilter; import org.springframework.integration.file.remote.session.Session; @@ -49,6 +50,8 @@ import org.springframework.util.ObjectUtils; * @since 2.0 */ public abstract class AbstractInboundFileSynchronizer implements InboundFileSynchronizer, InitializingBean { + + private static final StandardEvaluationContext context = new StandardEvaluationContext(); private String remoteFileSeparator = "/"; /** @@ -57,6 +60,8 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS private volatile String temporaryFileSuffix =".writing"; protected final Log logger = LogFactory.getLog(this.getClass()); + + private volatile Expression localFilenameGeneratorExpression; /** * the path on the remote mount as a String. @@ -80,6 +85,11 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS private boolean deleteRemoteFiles; + public void setLocalFilenameGeneratorExpression(Expression localFilenameGeneratorExpression) { + Assert.notNull(localFilenameGeneratorExpression, "'localFilenameGeneratorExpression' must not be null"); + this.localFilenameGeneratorExpression = localFilenameGeneratorExpression; + } + /** * Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances. */ @@ -159,6 +169,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS private void copyFileToLocalDirectory(String remoteDirectoryPath, F remoteFile, File localDirectory, Session session) throws IOException { String remoteFileName = this.getFilename(remoteFile); + String localFileName = this.generateLocalFileName(remoteFileName); String remoteFilePath = remoteDirectoryPath + remoteFileSeparator + remoteFileName; if (!this.isFile(remoteFile)) { if (logger.isDebugEnabled()) { @@ -166,7 +177,8 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } return; } - File localFile = new File(localDirectory, remoteFileName); + + File localFile = new File(localDirectory, localFileName); if (!localFile.exists()) { String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix; File tempFile = new File(tempFileName); @@ -197,6 +209,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS catch (Exception ignored2) { } } + if (tempFile.renameTo(localFile)) { if (this.deleteRemoteFiles) { session.remove(remoteFilePath); @@ -207,6 +220,13 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } } + + private String generateLocalFileName(String remoteFileName){ + if (this.localFilenameGeneratorExpression != null){ + return this.localFilenameGeneratorExpression.getValue(context, remoteFileName, String.class); + } + return remoteFileName; + } protected abstract boolean isFile(F file); diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd index 3ce1800dfe..47314f5442 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd @@ -115,6 +115,15 @@ endpoint itself is a Polling Consumer for a channel with a queue. + + + + Allows you to provide SpEL expression which will compute the file name of + the local file (transfered file). Root object of SpEL is the name of the original + file (e.g., "#this.toUpperCase() + '.a'" where #this represents the original name of the remote file) + + + 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 c80cf5807d..a559a0c6bc 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 @@ -21,6 +21,7 @@ filename-pattern="*.txt" local-directory="." remote-file-separator="" + local-filename-generator-expression="#this.toUpperCase() + '.a'" comparator="comparator" temporary-file-suffix=".foo" remote-directory="foo/bar"> 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 ec4a2def89..4d747a6a14 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 @@ -66,6 +66,7 @@ public class FtpInboundChannelAdapterParserTests { FtpInboundFileSynchronizer fisync = (FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer"); + assertNotNull(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression")); 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/FtpInboundRemoteFileSystemSynchronizerTest.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTest.java index b08e5bfcd0..aa2485a699 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTest.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTest.java @@ -16,17 +16,6 @@ package org.springframework.integration.ftp.inbound; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import java.io.File; import java.io.OutputStream; import java.util.ArrayList; @@ -37,11 +26,25 @@ import org.apache.commons.net.ftp.FTPFile; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; - +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.integration.ftp.filters.FtpRegexPatternFileListFilter; import org.springframework.integration.ftp.session.AbstractFtpSessionFactory; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + /** * @author Oleg Zhurakousky * @since 2.0 @@ -76,27 +79,32 @@ public class FtpInboundRemoteFileSystemSynchronizerTest { synchronizer.setDeleteRemoteFiles(true); synchronizer.setRemoteDirectory("remote-test-dir"); synchronizer.setFilter(new FtpRegexPatternFileListFilter(".*\\.test$")); + + ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression expression = expressionParser.parseExpression("#this.toUpperCase() + '.a'"); + synchronizer.setLocalFilenameGeneratorExpression(expression); FtpInboundFileSynchronizingMessageSource ms = new FtpInboundFileSynchronizingMessageSource(synchronizer); + ms.setAutoCreateLocalDirectory(true); ms.setLocalDirectory(localDirectoy); ms.afterPropertiesSet(); Message atestFile = ms.receive(); assertNotNull(atestFile); - assertEquals("a.test", atestFile.getPayload().getName()); + assertEquals("A.TEST.a", atestFile.getPayload().getName()); Message btestFile = ms.receive(); assertNotNull(btestFile); - assertEquals("b.test", btestFile.getPayload().getName()); + assertEquals("B.TEST.a", btestFile.getPayload().getName()); Message nothing = ms.receive(); assertNull(nothing); // two times because on the third receive (above) the internal queue will be empty, so it will attempt verify(synchronizer, times(2)).synchronizeToLocalDirectory(localDirectoy); - assertTrue(new File("test/a.test").exists()); - assertTrue(new File("test/b.test").exists()); + assertTrue(new File("test/A.TEST.a").exists()); + assertTrue(new File("test/B.TEST.a").exists()); } diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd index b6be8c76d2..4ce5d2ee63 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd @@ -222,6 +222,15 @@ endpoint itself is a Polling Consumer for a channel with a queue. + + + + Allows you to provide SpEL expression which will compute the file name of + the local file (transfered file). Root object of SpEL is the name of the original + file (e.g., "#this.toUpperCase() + '.a'" where #this represents the original name of the remote file) + + + 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 38ae641385..3da9bb86c6 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 @@ -45,6 +45,7 @@ local-directory="file:local-test-dir" auto-create-local-directory="false" remote-file-separator="." + local-filename-generator-expression="#this.toUpperCase() + '.a'" temporary-file-suffix=".bar" comparator="comparator" delete-remote-files="${delete.remote.files}"> 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 3744f9f97a..0dbe86111e 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 @@ -73,6 +73,7 @@ public class InboundChannelAdapterParserTests { Comparator comparator = TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived.q.comparator", Comparator.class); assertNotNull(comparator); SftpInboundFileSynchronizer synchronizer = (SftpInboundFileSynchronizer) TestUtils.getPropertyValue(source, "synchronizer"); + assertNotNull(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression")); String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator"); assertEquals(".bar", TestUtils.getPropertyValue(synchronizer, "temporaryFileSuffix", String.class)); assertNotNull(remoteFileSeparator);