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
This commit is contained in:
committed by
Artem Bilan
parent
2936e97ea3
commit
ad0839da8b
@@ -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);
|
||||
|
||||
@@ -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<? extends RemoteFileOperations<?>> getTemplateClass();
|
||||
|
||||
}
|
||||
|
||||
@@ -230,6 +230,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> 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<F> 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<F> remoteFileTemplate, String path, int chmod) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
private Object doMput(Message<?> requestMessage) {
|
||||
File file = null;
|
||||
if (requestMessage.getPayload() instanceof File) {
|
||||
|
||||
@@ -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<F> extends AbstractMessageHandler {
|
||||
|
||||
private final FileExistsMode mode;
|
||||
|
||||
private Integer chmod;
|
||||
|
||||
public FileTransferringMessageHandler(SessionFactory<F> sessionFactory) {
|
||||
Assert.notNull(sessionFactory, "sessionFactory must not be null");
|
||||
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
|
||||
@@ -131,6 +133,32 @@ public class FileTransferringMessageHandler<F> 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<F> 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<F> remoteFileTemplate, String path, int chmod) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<? extends RemoteFileOperations<?>> getTemplateClass() {
|
||||
return SftpRemoteFileTemplate.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<LsEnt
|
||||
return "sftp:outbound-gateway";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChmodCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileTemplate<LsEntry> remoteFileTemplate, final String path, final int chmod) {
|
||||
remoteFileTemplate.executeWithClient(new ClientCallbackWithoutResult<ChannelSftp>() {
|
||||
|
||||
@Override
|
||||
protected void doWithClientWithoutResult(ChannelSftp client) {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<LsEntry> {
|
||||
|
||||
/**
|
||||
* @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<LsEntry> sessionFactory) {
|
||||
super(sessionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChmodCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileTemplate<LsEntry> remoteFileTemplate, final String path, final int chmod) {
|
||||
remoteFileTemplate.executeWithClient(new ClientCallbackWithoutResult<ChannelSftp>() {
|
||||
|
||||
@Override
|
||||
protected void doWithClientWithoutResult(ChannelSftp client) {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides classes for the outbound channel adapter.
|
||||
*/
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides general support classes for sftp.
|
||||
*/
|
||||
package org.springframework.integration.sftp.support;
|
||||
@@ -66,6 +66,7 @@
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
<xsd:attributeGroup ref="chmod" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -523,6 +524,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="chmod" />
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
@@ -602,4 +604,15 @@
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:attributeGroup name="chmod">
|
||||
<xsd:attribute name="chmod" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Change the mode of the remote file after transferring. Integer value
|
||||
expressed in Octal, e.g. '644'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
@@ -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<LsEntry> 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<LsEntry>(factory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<LsEntry> sftpSessionFactory(TestSftpServer server) {
|
||||
return sftpServer().getSessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<property name="port" value="2222"/>
|
||||
<property name="user" value="oleg"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<int:channel id="inputChannel"/>
|
||||
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
@@ -26,8 +26,8 @@
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
remote-directory-expression="'foo' + '/' + 'bar'"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
temporary-file-suffix=".bar"
|
||||
remote-directory="foo/bar"
|
||||
temporary-remote-directory="foo/baz"
|
||||
chmod="600"
|
||||
order="23"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"
|
||||
|
||||
@@ -32,22 +32,22 @@ import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -61,26 +61,33 @@ public class OutboundChannelAdapterParserTests {
|
||||
|
||||
@Test
|
||||
public void testOutboundChannelAdapterWithId(){
|
||||
ApplicationContext context =
|
||||
ConfigurableApplicationContext context =
|
||||
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
|
||||
Object consumer = context.getBean("sftpOutboundAdapter");
|
||||
assertTrue(consumer instanceof EventDrivenConsumer);
|
||||
PublishSubscribeChannel channel = context.getBean("inputChannel", PublishSubscribeChannel.class);
|
||||
assertEquals(channel, TestUtils.getPropertyValue(consumer, "inputChannel"));
|
||||
assertEquals("sftpOutboundAdapter", ((EventDrivenConsumer)consumer).getComponentName());
|
||||
FileTransferringMessageHandler<?> 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<MessageHandler> 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<String>("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 {
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
chmod="600"
|
||||
remote-directory="sftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
|
||||
@@ -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<LsEntry> 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<Object>(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<Object>(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<LsEntry, Object> {
|
||||
|
||||
|
||||
@@ -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<LsEntry> sessionFactory;
|
||||
|
||||
@Before
|
||||
@After
|
||||
|
||||
@@ -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<String>` objec
|
||||
|
||||
See also <<sftp-partial>>
|
||||
|
||||
_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.
|
||||
|
||||
@@ -115,9 +115,16 @@ See <<http-inbound>> for more information.
|
||||
|
||||
==== SFTP Changes
|
||||
|
||||
===== Factory Bean
|
||||
A new factory bean is provided to simplify the configuration of Jsch proxies for SFTP.
|
||||
See <<sftp-proxy-factory-bean>> 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 <<sftp-outbound>> and <<sftp-outbound-gateway>> for more information.
|
||||
|
||||
==== FTP Changes
|
||||
|
||||
The `FtpSession` now supports `null` for the `list()` and `listNames()` method, since it is possible by the
|
||||
|
||||
Reference in New Issue
Block a user