From 417c848c0bd5897452a693c8adfc820b3e29246a Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 18 Sep 2013 18:07:01 +0300 Subject: [PATCH] INT-3129: FTP local-filename-generator-expression * Add `local-filename-generator-expression` attribute to FTP and SFTP Outbound Gateway * Refactoring for `(S)FtpOutboundGatewayParser` * Add `local-filename-generator-expression` tests * Add 'What's New' and attribute description JIRA: https://jira.springsource.org/browse/INT-3129 Polishing Doc Polishing Assert attribute is only set for get/mget Move tests to the get gateways --- ...stractRemoteFileOutboundGatewayParser.java | 20 +++++++--- .../AbstractRemoteFileOutboundGateway.java | 40 +++++++++++++++---- .../ftp/config/FtpOutboundGatewayParser.java | 18 +++++---- .../ftp/config/spring-integration-ftp-3.0.xsd | 15 +++++++ .../FtpOutboundGatewayParserTests-context.xml | 5 +++ .../config/FtpOutboundGatewayParserTests.java | 24 ++++++++++- .../config/SftpOutboundGatewayParser.java | 18 +++++---- .../config/spring-integration-sftp-3.0.xsd | 15 +++++++ ...SftpOutboundGatewayParserTests-context.xml | 5 +++ .../SftpOutboundGatewayParserTests.java | 23 ++++++++++- src/reference/docbook/ftp.xml | 27 +++++++++---- src/reference/docbook/sftp.xml | 27 +++++++++---- src/reference/docbook/whats-new.xml | 10 +++++ 13 files changed, 198 insertions(+), 49 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java index ca68e7f0b4..fbd4442b3b 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java @@ -19,6 +19,7 @@ import org.w3c.dom.Element; 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.AbstractConsumerEndpointParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.util.StringUtils; @@ -55,7 +56,16 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-local-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "rename-expression"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); return builder; + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "rename-expression"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); + String localFileGeneratorExpression = element.getAttribute("local-filename-generator-expression"); + if (StringUtils.hasText(localFileGeneratorExpression)) { + BeanDefinitionBuilder localFileGeneratorExpressionBuilder = + BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class); + localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression); + builder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition()); + } + return builder; } protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { @@ -77,21 +87,21 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo } else if (hasFileNamePattern) { BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition( - this.getSimplePatternFileListFilterClassname()); + this.getSimplePatternFileListFilterClassName()); filterBuilder.addConstructorArgValue(fileNamePattern); builder.addPropertyValue("filter", filterBuilder.getBeanDefinition()); } else if (hasFileNameRegex) { BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition( - this.getRegexPatternFileListFilterClassname()); + this.getRegexPatternFileListFilterClassName()); filterBuilder.addConstructorArgValue(fileNameRegex); builder.addPropertyValue("filter", filterBuilder.getBeanDefinition()); } } - protected abstract String getRegexPatternFileListFilterClassname(); + protected abstract String getRegexPatternFileListFilterClassName(); - protected abstract String getSimplePatternFileListFilterClassname(); + protected abstract String getSimplePatternFileListFilterClassName(); protected abstract String getGatewayClassName(); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index b9a5fa8c51..194e18b104 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -29,9 +29,12 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.integration.MessagingException; +import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.filters.FileListFilter; import org.springframework.integration.file.remote.AbstractFileInfo; @@ -49,6 +52,7 @@ import org.springframework.util.StringUtils; * Base class for Outbound Gateways that perform remote file operations. * * @author Gary Russell + * @author Artem Bilan * @since 2.1 */ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReplyProducingMessageHandler { @@ -178,6 +182,8 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply private volatile FileListFilter filter; + private volatile Expression localFilenameGeneratorExpression; + public AbstractRemoteFileOutboundGateway(SessionFactory sessionFactory, String command, String expression) { this.sessionFactory = sessionFactory; @@ -249,6 +255,12 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply new SpelExpressionParser().parseExpression(expression)); } + public void setLocalFilenameGeneratorExpression(Expression localFilenameGeneratorExpression) { + Assert.notNull(localFilenameGeneratorExpression, "'localFilenameGeneratorExpression' must not be null"); + this.localFilenameGeneratorExpression = localFilenameGeneratorExpression; + } + + @Override protected void onInit() { super.onInit(); @@ -334,7 +346,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply if (remoteDir.length() == 0) { remoteDir = this.remoteFileSeparator; } - File payload = get(session, remoteFilePath, remoteFilename, true); + File payload = get(requestMessage, session, remoteFilePath, remoteFilename, true); return MessageBuilder.withPayload(payload) .setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir) .setHeader(FileHeaders.REMOTE_FILE, remoteFilename) @@ -348,7 +360,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply if (remoteDir.length() == 0) { remoteDir = this.remoteFileSeparator; } - List payload = mGet(session, remoteDir, remoteFilename); + List payload = mGet(requestMessage, session, remoteDir, remoteFilename); return MessageBuilder.withPayload(payload) .setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir) .setHeader(FileHeaders.REMOTE_FILE, remoteFilename) @@ -454,11 +466,13 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply /** * Copy a remote file to the configured local directory. + * + * @param message * @param session * @param remoteFilePath * @throws IOException */ - protected File get(Session session, String remoteFilePath, String remoteFilename, boolean lsFirst) + protected File get(Message message, Session session, String remoteFilePath, String remoteFilename, boolean lsFirst) throws IOException { F[] files = null; if (lsFirst) { @@ -467,7 +481,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply throw new MessagingException(remoteFilePath + " is not a file"); } } - File localFile = new File(this.localDirectory, remoteFilename); + File localFile = new File(this.localDirectory, this.generateLocalFileName(message, remoteFilename)); if (!localFile.exists()) { String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix; File tempFile = new File(tempFileName); @@ -488,6 +502,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply fileOutputStream.close(); } catch (Exception ignored2) { + //Ignore it } } if (!tempFile.renameTo(localFile)) { @@ -503,8 +518,8 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply } } - protected List mGet(Session session, String remoteDirectory, - String remoteFilename) throws IOException { + protected List mGet(Message message, Session session, String remoteDirectory, + String remoteFilename) throws IOException { String path = generateFullPath(remoteDirectory, remoteFilename); String[] fileNames = session.listNames(path); if (fileNames == null) { @@ -519,11 +534,11 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply File file; if (fileName.contains(this.remoteFileSeparator) && fileName.startsWith(remoteDirectory)) { // the server returned the full path - file = this.get(session, fileName, + file = this.get(message, session, fileName, fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false); } else { - file = this.get(session, generateFullPath(remoteDirectory, fileName), fileName, false); + file = this.get(message, session, generateFullPath(remoteDirectory, fileName), fileName, false); } files.add(file); } @@ -573,6 +588,15 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply session.rename(remoteFilePath, remoteFileNewPath); } + private String generateLocalFileName(Message message, String remoteFileName){ + if (this.localFilenameGeneratorExpression != null){ + EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); + evaluationContext.setVariable("remoteFileName", remoteFileName); + return this.localFilenameGeneratorExpression.getValue(evaluationContext, message, String.class); + } + return remoteFileName; + } + abstract protected boolean isDirectory(F file); abstract protected boolean isLink(F file); diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java index 78c3a98dbe..67121f5337 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 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. @@ -16,28 +16,30 @@ package org.springframework.integration.ftp.config; import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser; +import org.springframework.integration.ftp.filters.FtpRegexPatternFileListFilter; +import org.springframework.integration.ftp.filters.FtpSimplePatternFileListFilter; +import org.springframework.integration.ftp.gateway.FtpOutboundGateway; /** * @author Gary Russell + * @author Artem Bilan * @since 2.1 * */ public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser { - private static final String BASE_PACKAGE = "org.springframework.integration.ftp"; - public String getGatewayClassName() { - return BASE_PACKAGE + ".gateway.FtpOutboundGateway"; + return FtpOutboundGateway.class.getName(); } @Override - protected String getSimplePatternFileListFilterClassname() { - return BASE_PACKAGE + ".filters.FtpSimplePatternFileListFilter"; + protected String getSimplePatternFileListFilterClassName() { + return FtpSimplePatternFileListFilter.class.getName(); } @Override - protected String getRegexPatternFileListFilterClassname() { - return BASE_PACKAGE + ".filters.FtpRegexPatternFileListFilter"; + protected String getRegexPatternFileListFilterClassName() { + return FtpRegexPatternFileListFilter.class.getName(); } } 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 e8d715a51b..d27674c478 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 @@ -394,6 +394,21 @@ + + + + Allows you to provide a SpEL expression to + generate the file name of + the local (transferred) file. The root + object of the SpEL + evaluation is the request Message, but the name of the original + remote file is also provided as the 'remoteFileName' variable. + For example, a valid expression would be: + "#remoteFileName.toUpperCase() + headers.foo". + Only used with 'get' and 'mget' commands. + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml index 152d56cc9a..a2ec9117ee 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml @@ -31,6 +31,10 @@ order="1" /> + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java index 43d8229ed9..73c8f1c18e 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java @@ -21,10 +21,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; +import java.lang.reflect.Method; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.Message; import org.springframework.integration.endpoint.AbstractEndpoint; @@ -39,6 +42,7 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ReflectionUtils; /** * @author Gary Russell @@ -77,8 +81,9 @@ public class FtpOutboundGatewayParserTests { assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory")); assertNotNull(TestUtils.getPropertyValue(gateway, "filter")); assertEquals(Command.LS, TestUtils.getPropertyValue(gateway, "command")); + @SuppressWarnings("unchecked") - Set options = TestUtils.getPropertyValue(gateway, "options", Set.class); + Set + + + + Allows you to provide a SpEL expression to + generate the file name of + the local (transferred) file. The root + object of the SpEL + evaluation is the request Message, but the name of the original + remote file is also provided as the 'remoteFileName' variable. + For example, a valid expression would be: + "#remoteFileName.toUpperCase() + headers.foo". + Only used with 'get' and 'mget' commands. + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml index 03434774fe..55b07d8b8f 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml @@ -31,6 +31,10 @@ order="1" /> + + + + options = TestUtils.getPropertyValue(gateway, "options", Set.class); + Set