From ad0839da8b44721e67e84bc4b188dd48973b48ed Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 21 Mar 2016 19:19:27 -0400 Subject: [PATCH] INT-3973: SFTP - Support chmod JIRA: https://jira.spring.io/browse/INT-3973 Add `chmod` to outbound adapter and gateway (put methods). Polishing - PR Comments Fix Checkstyle vulnerabilities --- ...stractRemoteFileOutboundGatewayParser.java | 5 + ...emoteFileOutboundChannelAdapterParser.java | 11 ++- .../AbstractRemoteFileOutboundGateway.java | 43 +++++++++ .../FileTransferringMessageHandler.java | 47 +++++++++- .../SftpOutboundChannelAdapterParser.java | 16 ++++ .../config/SftpOutboundGatewayParser.java | 9 ++ .../sftp/gateway/SftpOutboundGateway.java | 26 ++++++ .../sftp/outbound/SftpMessageHandler.java | 91 +++++++++++++++++++ .../sftp/outbound/package-info.java | 4 + .../sftp/support/GeneralSftpException.java | 35 +++++++ .../sftp/support/package-info.java | 4 + .../config/spring-integration-sftp-4.3.xsd | 13 +++ .../integration/sftp/TestSftpServer.java | 7 +- .../sftp/TestSftpServerConfig.java | 6 +- ...erParserTests-context-fail-fileFileGen.xml | 8 +- ...boundChannelAdapterParserTests-context.xml | 1 + .../OutboundChannelAdapterParserTests.java | 53 +++++++---- .../SftpServerOutboundTests-context.xml | 1 + .../outbound/SftpServerOutboundTests.java | 28 ++++-- .../session/SftpRemoteFileTemplateTests.java | 3 +- src/reference/asciidoc/sftp.adoc | 13 +++ src/reference/asciidoc/whats-new.adoc | 7 ++ 22 files changed, 392 insertions(+), 39 deletions(-) create mode 100644 spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java create mode 100644 spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java create mode 100644 spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/GeneralSftpException.java create mode 100644 spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/package-info.java 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 83bda34083..3fc713a41f 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 @@ -81,9 +81,14 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-filename-generator-expression", "localFilenameGeneratorExpressionString"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "mode", "fileExistsMode"); + postProcessBuilder(builder, element); return builder; } + protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { + // no-op + } + protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext, String filterAttribute, String patternPrefix, String propertyName) { String filter = element.getAttribute(filterAttribute); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java index efbefe9cac..57f0aaef2c 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java @@ -38,7 +38,7 @@ public abstract class RemoteFileOutboundChannelAdapterParser extends AbstractOut @Override protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class); + BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(handlerClass()); BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true, getTemplateClass()); @@ -48,9 +48,18 @@ public abstract class RemoteFileOutboundChannelAdapterParser extends AbstractOut if (StringUtils.hasText(mode)) { handlerBuilder.addConstructorArgValue(mode); } + postProcessBuilder(handlerBuilder, element); return handlerBuilder.getBeanDefinition(); } + protected Class handlerClass() { + return FileTransferringMessageHandler.class; + } + + protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { + // no-op + } + protected abstract Class> getTemplateClass(); } 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 2bd9070a97..04dafab549 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 @@ -230,6 +230,8 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply private volatile FileExistsMode fileExistsMode; + private volatile Integer chmod; + /** * Construct an instance using the provided session factory and callback for * performing operations on the session. @@ -430,6 +432,32 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply } } + /** + * String setter for Spring XML convenience. + * @param chmod permissions as an octal string e.g "600"; + * @see #setChmod(int) + * @since 4.3 + */ + public void setChmodOctal(String chmod) { + Assert.notNull(chmod, "'chmod' cannot be null"); + setChmod(Integer.parseInt(chmod, 8)); + } + + /** + * Set the file permissions after uploading, e.g. 0600 for + * owner read/write. + * @param chmod the permissions. + * @since 4.3 + */ + public void setChmod(int chmod) { + Assert.isTrue(isChmodCapable(), "chmod operations not supported"); + this.chmod = chmod; + } + + public boolean isChmodCapable() { + return false; + } + @Override protected void doInit() { Assert.state(this.command != null || this.messageSessionCallback != null, @@ -616,9 +644,24 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply if (path == null) { throw new MessagingException(requestMessage, "No local file found for " + requestMessage); } + if (this.chmod != null && isChmodCapable()) { + doChmod(this.remoteFileTemplate, path, this.chmod); + } return path; } + /** + * Set the mode on the remote file after transfer; the default implementation does + * nothing. + * @param remoteFileTemplate the remote file template. + * @param path the path. + * @param chmod the chmod to set. + * @since 4.3 + */ + protected void doChmod(RemoteFileTemplate remoteFileTemplate, String path, int chmod) { + // no-op + } + private Object doMput(Message requestMessage) { File file = null; if (requestMessage.getPayload() instanceof File) { diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java index 1c3c3650de..c2961d28e8 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java @@ -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. @@ -42,6 +42,8 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { private final FileExistsMode mode; + private Integer chmod; + public FileTransferringMessageHandler(SessionFactory sessionFactory) { Assert.notNull(sessionFactory, "sessionFactory must not be null"); this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory); @@ -131,6 +133,32 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix); } + /** + * String setter for Spring XML convenience. + * @param chmod permissions as an octal string e.g "600"; + * @see #setChmod(int) + * @since 4.3 + */ + public void setChmodOctal(String chmod) { + Assert.notNull(chmod, "'chmod' cannot be null"); + setChmod(Integer.parseInt(chmod, 8)); + } + + /** + * Set the file permissions after uploading, e.g. 0600 for + * owner read/write. + * @param chmod the permissions. + * @since 4.3 + */ + public void setChmod(int chmod) { + Assert.isTrue(isChmodCapable(), "chmod operations not supported"); + this.chmod = chmod; + } + + public boolean isChmodCapable() { + return false; + } + @Override protected void onInit() throws Exception { this.remoteFileTemplate.setBeanFactory(this.getBeanFactory()); @@ -139,7 +167,22 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { @Override protected void handleMessageInternal(Message message) throws Exception { - this.remoteFileTemplate.send(message, this.mode); + String path = this.remoteFileTemplate.send(message, this.mode); + if (this.chmod != null && isChmodCapable()) { + doChmod(this.remoteFileTemplate, path, this.chmod); + } + } + + /** + * Set the mode on the remote file after transfer; the default implementation does + * nothing. + * @param remoteFileTemplate the remote file template. + * @param path the path. + * @param chmod the chmod to set. + * @since 4.3 + */ + protected void doChmod(RemoteFileTemplate remoteFileTemplate, String path, int chmod) { + // no-op } } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java index 5070cfbb6c..4a7557c2d3 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java @@ -16,8 +16,13 @@ package org.springframework.integration.sftp.config; +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; import org.springframework.integration.file.remote.RemoteFileOperations; +import org.springframework.integration.sftp.outbound.SftpMessageHandler; import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; /** @@ -29,9 +34,20 @@ import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; */ public class SftpOutboundChannelAdapterParser extends RemoteFileOutboundChannelAdapterParser { + + @Override + protected Class handlerClass() { + return SftpMessageHandler.class; + } + @Override protected Class> getTemplateClass() { return SftpRemoteFileTemplate.class; } + @Override + protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal"); + } + } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java index cf3764b851..9fecd76d00 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java @@ -16,6 +16,10 @@ package org.springframework.integration.sftp.config; +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser; import org.springframework.integration.file.remote.RemoteFileOperations; import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; @@ -51,4 +55,9 @@ public class SftpOutboundGatewayParser extends AbstractRemoteFileOutboundGateway return SftpRemoteFileTemplate.class; } + @Override + protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal"); + } + } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java index 7692626ec0..e27d15d8aa 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java @@ -22,13 +22,17 @@ import java.util.List; import org.springframework.beans.DirectFieldAccessor; import org.springframework.integration.file.remote.AbstractFileInfo; +import org.springframework.integration.file.remote.ClientCallbackWithoutResult; import org.springframework.integration.file.remote.MessageSessionCallback; import org.springframework.integration.file.remote.RemoteFileTemplate; import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.sftp.session.SftpFileInfo; +import org.springframework.integration.sftp.support.GeneralSftpException; +import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; +import com.jcraft.jsch.SftpException; /** * Outbound Gateway for performing remote file operations via SFTP. @@ -129,4 +133,26 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway remoteFileTemplate, final String path, final int chmod) { + remoteFileTemplate.executeWithClient(new ClientCallbackWithoutResult() { + + @Override + protected void doWithClientWithoutResult(ChannelSftp client) { + try { + client.chmod(chmod, path); + } + catch (SftpException e) { + throw new GeneralSftpException("Failed to execute chmod", e); + } + } + + }); + } + } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java new file mode 100644 index 0000000000..0741469a20 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.sftp.outbound; + +import org.springframework.integration.file.remote.ClientCallbackWithoutResult; +import org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; +import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; +import org.springframework.integration.sftp.support.GeneralSftpException; + +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.ChannelSftp.LsEntry; +import com.jcraft.jsch.SftpException; + +/** + * Subclass of {@link FileTransferringMessageHandler} for SFTP. + * + * @author Gary Russell + * @since 4.3 + * + */ +public class SftpMessageHandler extends FileTransferringMessageHandler { + + /** + * @param remoteFileTemplate the template. + * @see FileTransferringMessageHandler#FileTransferringMessageHandler + * (org.springframework.integration.file.remote.RemoteFileTemplate) + */ + public SftpMessageHandler(SftpRemoteFileTemplate remoteFileTemplate) { + super(remoteFileTemplate); + } + + /** + * + * @param remoteFileTemplate the template. + * @param mode the file exists mode. + * @see FileTransferringMessageHandler#FileTransferringMessageHandler + * (org.springframework.integration.file.remote.RemoteFileTemplate, FileExistsMode) + */ + public SftpMessageHandler(SftpRemoteFileTemplate remoteFileTemplate, FileExistsMode mode) { + super(remoteFileTemplate, mode); + } + + /** + * @param sessionFactory the session factory. + * @see FileTransferringMessageHandler#FileTransferringMessageHandler + * (SessionFactory) + */ + public SftpMessageHandler(SessionFactory sessionFactory) { + super(sessionFactory); + } + + @Override + public boolean isChmodCapable() { + return true; + } + + @Override + protected void doChmod(RemoteFileTemplate remoteFileTemplate, final String path, final int chmod) { + remoteFileTemplate.executeWithClient(new ClientCallbackWithoutResult() { + + @Override + protected void doWithClientWithoutResult(ChannelSftp client) { + try { + client.chmod(chmod, path); + } + catch (SftpException e) { + throw new GeneralSftpException("Failed to execute chmod", e); + } + } + + }); + } + +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java new file mode 100644 index 0000000000..e15f8a7aaf --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes for the outbound channel adapter. + */ +package org.springframework.integration.sftp.outbound; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/GeneralSftpException.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/GeneralSftpException.java new file mode 100644 index 0000000000..2c056932d5 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/GeneralSftpException.java @@ -0,0 +1,35 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.sftp.support; + +import org.springframework.core.NestedRuntimeException; + +/** + * Simple runtime exception to wrap an SftpException. + * + * @author Gary Russell + * @since 4.3 + * + */ +@SuppressWarnings("serial") +public class GeneralSftpException extends NestedRuntimeException { + + public GeneralSftpException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/package-info.java new file mode 100644 index 0000000000..b78b05597d --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides general support classes for sftp. + */ +package org.springframework.integration.sftp.support; diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-4.3.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-4.3.xsd index 8b24efd1f1..0a46f0fdbb 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-4.3.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-4.3.xsd @@ -66,6 +66,7 @@ + @@ -523,6 +524,7 @@ + @@ -602,4 +604,15 @@ + + + + + Change the mode of the remote file after transferring. Integer value + expressed in Octal, e.g. '644'. + + + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java index 8befdcdd8b..018ccfee30 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java @@ -33,8 +33,11 @@ import org.junit.rules.TemporaryFolder; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import com.jcraft.jsch.ChannelSftp.LsEntry; + /** * @author Gary Russell * @author Artem Bilan @@ -170,14 +173,14 @@ public class TestSftpServer implements InitializingBean, DisposableBean { } } - public DefaultSftpSessionFactory getSessionFactory() { + public CachingSessionFactory getSessionFactory() { DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true); factory.setHost("localhost"); factory.setPort(this.server.getPort()); factory.setUser("foo"); factory.setPassword("foo"); factory.setAllowUnknownKeys(true); - return factory; + return new CachingSessionFactory(factory); } } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java index fc8008608c..8b1db23f49 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java @@ -18,7 +18,9 @@ package org.springframework.integration.sftp; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.integration.file.remote.session.CachingSessionFactory; + +import com.jcraft.jsch.ChannelSftp.LsEntry; /** * @author Gary Russell @@ -34,7 +36,7 @@ public class TestSftpServerConfig { } @Bean - public DefaultSftpSessionFactory sftpSessionFactory(TestSftpServer server) { + public CachingSessionFactory sftpSessionFactory(TestSftpServer server) { return sftpServer().getSessionFactory(); } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml index 165301cd37..4837c76436 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml @@ -16,9 +16,9 @@ - + - + - - + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml index 72ea5c4c0e..f923a00213 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml @@ -32,6 +32,7 @@ temporary-file-suffix=".bar" remote-directory="foo/bar" temporary-remote-directory="foo/baz" + chmod="600" order="23"/> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class); - String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileTemplate.remoteFileSeparator"); + FileTransferringMessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", + FileTransferringMessageHandler.class); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, + "remoteFileTemplate.remoteFileSeparator"); assertNotNull(remoteFileSeparator); assertEquals(".", remoteFileSeparator); - assertEquals(".bar", TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class)); - Expression remoteDirectoryExpression = (Expression) TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor.expression"); + assertEquals(".bar", + TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class)); + Expression remoteDirectoryExpression = (Expression) TestUtils.getPropertyValue(handler, + "remoteFileTemplate.directoryExpressionProcessor.expression"); assertNotNull(remoteDirectoryExpression); assertTrue(remoteDirectoryExpression instanceof LiteralExpression); assertNotNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor")); - assertEquals(context.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator")); + assertEquals(context.getBean("fileNameGenerator"), + TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator")); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")); - CachingSessionFactory sessionFactory = TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", CachingSessionFactory.class); - DefaultSftpSessionFactory clientFactory = TestUtils.getPropertyValue(sessionFactory, "sessionFactory", DefaultSftpSessionFactory.class); + CachingSessionFactory sessionFactory = TestUtils.getPropertyValue(handler, + "remoteFileTemplate.sessionFactory", CachingSessionFactory.class); + DefaultSftpSessionFactory clientFactory = TestUtils.getPropertyValue(sessionFactory, "sessionFactory", + DefaultSftpSessionFactory.class); assertEquals("localhost", TestUtils.getPropertyValue(clientFactory, "host")); assertEquals(2222, TestUtils.getPropertyValue(clientFactory, "port")); assertEquals(23, TestUtils.getPropertyValue(handler, "order")); @@ -91,13 +98,16 @@ public class OutboundChannelAdapterParserTests { TestUtils.getPropertyValue(channel, "dispatcher"), "handlers"); Iterator iterator = handlers.iterator(); - assertSame(TestUtils.getPropertyValue(context.getBean("sftpOutboundAdapterWithExpression"), "handler"), iterator.next()); + assertSame(TestUtils.getPropertyValue(context.getBean("sftpOutboundAdapterWithExpression"), "handler"), + iterator.next()); assertSame(handler, iterator.next()); + assertEquals(384, TestUtils.getPropertyValue(handler, "chmod")); + context.close(); } @Test public void testOutboundChannelAdapterWithWithRemoteDirectoryAndFileExpression(){ - ApplicationContext context = + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass()); Object consumer = context.getBean("sftpOutboundAdapterWithExpression"); assertTrue(consumer instanceof EventDrivenConsumer); @@ -113,32 +123,35 @@ public class OutboundChannelAdapterParserTests { assertEquals("payload.getName() + '-foo'", fileNameGeneratorExpression.getExpressionString()); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")); assertNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor")); - + context.close(); } @Test public void testOutboundChannelAdapterWithNoTemporaryFileName(){ - ApplicationContext context = + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass()); Object consumer = context.getBean("sftpOutboundAdapterWithNoTemporaryFileName"); FileTransferringMessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class); assertFalse((Boolean)TestUtils.getPropertyValue(handler,"remoteFileTemplate.useTemporaryFileName")); + context.close(); } @Test public void advised(){ - ApplicationContext context = + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass()); Object consumer = context.getBean("advised"); MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class); handler.handleMessage(new GenericMessage("foo")); assertEquals(1, adviceCalled); + context.close(); } @Test public void testFailWithRemoteDirAndExpression(){ try { - new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass()); + new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass()) + .close(); fail("Exception expected"); } catch (BeanDefinitionStoreException e) { @@ -149,8 +162,8 @@ public class OutboundChannelAdapterParserTests { @Test(expected=BeanDefinitionStoreException.class) public void testFailWithFileExpressionAndFileGenerator(){ - new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml", this.getClass()); - + new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml", + this.getClass()).close(); } public static class FooAdvice extends AbstractRequestHandlerAdvice { diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml index 200c807b1c..cc7dcf6611 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml @@ -73,6 +73,7 @@ auto-create-directory="true" filename-pattern="*.txt" expression="payload" + chmod="600" remote-directory="sftpTarget" reply-channel="output"/> diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java index e8f521b65a..d7262d2798 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java @@ -21,12 +21,13 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import java.io.ByteArrayOutputStream; import java.io.File; @@ -44,14 +45,15 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.remote.MessageSessionCallback; import org.springframework.integration.file.remote.SessionCallback; import org.springframework.integration.file.remote.session.Session; +import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.sftp.TestSftpServer; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; @@ -114,7 +116,7 @@ public class SftpServerOutboundTests { private DirectChannel inboundMPutRecursiveFiltered; @Autowired - private DefaultSftpSessionFactory sessionFactory; + private SessionFactory sessionFactory; @Autowired private DirectChannel appending; @@ -160,8 +162,8 @@ public class SftpServerOutboundTests { assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"), Matchers.containsString(dir.toUpperCase())); Session session2 = this.sessionFactory.getSession(); - assertSame(TestUtils.getPropertyValue(session, "jschSession"), - TestUtils.getPropertyValue(session2, "jschSession")); + assertSame(TestUtils.getPropertyValue(session, "targetSession.jschSession"), + TestUtils.getPropertyValue(session2, "targetSession.jschSession")); } @Test @@ -327,7 +329,13 @@ public class SftpServerOutboundTests { } @Test - public void testInt3088MPutNotRecursive() { + public void testInt3088MPutNotRecursive() throws Exception { + Session session = sessionFactory.getSession(); + session.close(); + session = TestUtils.getPropertyValue(session, "targetSession", Session.class); + ChannelSftp channel = spy(TestUtils.getPropertyValue(session, "channel", ChannelSftp.class)); + new DirectFieldAccessor(session).setPropertyValue("channel", channel); + String dir = "sftpSource/"; this.inboundMGetRecursive.send(new GenericMessage(dir + "*")); while (output.receive(0) != null) { } @@ -344,6 +352,8 @@ public class SftpServerOutboundTests { assertThat( out.getPayload().get(1), anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"))); + verify(channel).chmod(384, "sftpTarget/localSource1.txt"); // 384 = 600 octal + verify(channel).chmod(384, "sftpTarget/localSource2.txt"); } @Test @@ -419,6 +429,9 @@ public class SftpServerOutboundTests { @Test public void testStream() { + Session session = spy(this.sessionFactory.getSession()); + session.close(); + String dir = "sftpSource/"; this.inboundGetStream.send(new GenericMessage(dir + "sftpSource1.txt")); Message result = this.output.receive(1000); @@ -426,7 +439,7 @@ public class SftpServerOutboundTests { assertEquals("source1", result.getPayload()); assertEquals("sftpSource/", result.getHeaders().get(FileHeaders.REMOTE_DIRECTORY)); assertEquals("sftpSource1.txt", result.getHeaders().get(FileHeaders.REMOTE_FILE)); - assertFalse(((Session) result.getHeaders().get(FileHeaders.REMOTE_SESSION)).isOpen()); + verify(session).close(); } @Test @@ -449,6 +462,7 @@ public class SftpServerOutboundTests { assertEquals(6, files[0].getAttrs().getSize()); } + @SuppressWarnings("unused") private static final class TestMessageSessionCallback implements MessageSessionCallback { diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java index 5452fce1a8..3719af84cc 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java @@ -33,6 +33,7 @@ import org.springframework.integration.file.DefaultFileNameGenerator; import org.springframework.integration.file.remote.ClientCallbackWithoutResult; import org.springframework.integration.file.remote.SessionCallback; import org.springframework.integration.file.remote.SessionCallbackWithoutResult; +import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.file.remote.session.Session; import org.springframework.integration.sftp.TestSftpServer; import org.springframework.integration.sftp.TestSftpServerConfig; @@ -60,7 +61,7 @@ public class SftpRemoteFileTemplateTests { private TestSftpServer sftpServer; @Autowired - private DefaultSftpSessionFactory sessionFactory; + private CachingSessionFactory sessionFactory; @Before @After diff --git a/src/reference/asciidoc/sftp.adoc b/src/reference/asciidoc/sftp.adoc index 5207079eb9..5b06fd7966 100644 --- a/src/reference/asciidoc/sftp.adoc +++ b/src/reference/asciidoc/sftp.adoc @@ -456,6 +456,7 @@ Similar to the FTP outbound adapter, the _SFTP Outbound Channel Adapter_ support remote-filename-generator-expression="payload.getName() + '-foo'" filename-generator="fileNameGenerator" use-temporary-filename="true" + chmod="600" mode="REPLACE"/> ---- @@ -484,6 +485,10 @@ However, there may be situations where you don't want to use this technique (for For situations like this, you can disable this feature by setting `use-temporary-file-name` to `false` (default is `true`). When this attribute is `false`, the file is written with its final name and the consuming application will need some other mechanism to detect that the file is completely uploaded before accessing it. +_Version 4.3_ introduced the `chmod` attribute which changes the remote file permissions after upload. +Use the conventional Unix octal format, e.g. `600` allows read-write for the file owner only. +When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`. + [[sftp-outbound-gateway]] === SFTP Outbound Gateway @@ -605,6 +610,10 @@ Refer to the schema documentation for more information. The message payload resulting from a _put_ operation is a `String` representing the full path of the file on the server after transfer. +_Version 4.3_ introduced the `chmod` attribute which changes the remote file permissions after upload. +Use the conventional Unix octal format, e.g. `600` allows read-write for the file owner only. +When configuring the adapter using java, you can use `setChmod(0600)`. + *mput* _mput_ sends multiple files to the server and supports the following option: @@ -622,6 +631,10 @@ The message payload resulting from an _mget_ operation is a `List` objec See also <> +_Version 4.3_ introduced the `chmod` attribute which changes the remote file permissions after upload. +Use the conventional Unix octal format, e.g. `600` allows read-write for the file owner only. +When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`. + *rm* The _rm_ command has no options. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 5416f674c2..783274410d 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -115,9 +115,16 @@ See <> for more information. ==== SFTP Changes +===== Factory Bean A new factory bean is provided to simplify the configuration of Jsch proxies for SFTP. See <> for more information. +===== chmod + +The SFTP outbound gateway (for `put` and `mput` commands) and the SFTP outbound channel adapter now support the +`chmod` attribute to change the remote file permissions after uploading. +See <> and <> for more information. + ==== FTP Changes The `FtpSession` now supports `null` for the `list()` and `listNames()` method, since it is possible by the