INT-3412 (S)FTP Append, rmdir, Client Access
JIRA: https://jira.spring.io/browse/INT-3412 Initial commit - review only. TODO: - SFTP Tests - Namespace/Adapter support for file append - Docs INT-3412 Polishing - Addressed PR comments - Completed SFTP implementation - Added namespace/parser support for `FileExistsMode` (append, etc) - Added SFTP Tests - Created Embedded SFTP server for tests (similar to FTP) - Converted tests that needed a real server to use the embedded server INT-3412 Docs and Polish (PR Comments)
This commit is contained in:
committed by
Artem Bilan
parent
bc3db5d4a9
commit
403c91801d
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2014 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.file.FileSystemView;
|
||||
import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory;
|
||||
import org.apache.sshd.common.file.nativefs.NativeFileSystemView;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.sftp.SftpSubsystem;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
public class TestSftpServer implements InitializingBean, DisposableBean {
|
||||
|
||||
private final SshServer server = SshServer.setUpDefaultServer();
|
||||
|
||||
private final int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private final TemporaryFolder sftpFolder;
|
||||
|
||||
private final TemporaryFolder localFolder;
|
||||
|
||||
private volatile File sftpRootFolder;
|
||||
|
||||
private volatile File sourceSftpDirectory;
|
||||
|
||||
private volatile File targetSftpDirectory;
|
||||
|
||||
private volatile File sourceLocalDirectory;
|
||||
|
||||
private volatile File targetLocalDirectory;
|
||||
|
||||
public TestSftpServer() {
|
||||
this.sftpFolder = new TemporaryFolder() {
|
||||
|
||||
@Override
|
||||
public void create() throws IOException {
|
||||
super.create();
|
||||
sftpRootFolder = this.newFolder("test");
|
||||
sourceSftpDirectory = new File(sftpRootFolder, "sftpSource");
|
||||
sourceSftpDirectory.mkdir();
|
||||
File file = new File(sourceSftpDirectory, "sftpSource1.txt");
|
||||
file.createNewFile();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write("source1".getBytes());
|
||||
fos.close();
|
||||
file = new File(sourceSftpDirectory, "sftpSource2.txt");
|
||||
file.createNewFile();
|
||||
fos = new FileOutputStream(file);
|
||||
fos.write("source2".getBytes());
|
||||
fos.close();
|
||||
|
||||
File subSourceFtpDirectory = new File(sourceSftpDirectory, "subSftpSource");
|
||||
subSourceFtpDirectory.mkdir();
|
||||
file = new File(subSourceFtpDirectory, "subSftpSource1.txt");
|
||||
file.createNewFile();
|
||||
fos = new FileOutputStream(file);
|
||||
fos.write("subSource1".getBytes());
|
||||
fos.close();
|
||||
|
||||
targetSftpDirectory = new File(sftpRootFolder, "sftpTarget");
|
||||
targetSftpDirectory.mkdir();
|
||||
}
|
||||
};
|
||||
this.localFolder = new TemporaryFolder() {
|
||||
|
||||
@Override
|
||||
public void create() throws IOException {
|
||||
super.create();
|
||||
File rootFolder = this.newFolder("test");
|
||||
sourceLocalDirectory = new File(rootFolder, "localSource");
|
||||
sourceLocalDirectory.mkdirs();
|
||||
File file = new File(sourceLocalDirectory, "localSource1.txt");
|
||||
file.createNewFile();
|
||||
file = new File(sourceLocalDirectory, "localSource2.txt");
|
||||
file.createNewFile();
|
||||
|
||||
File subSourceLocalDirectory = new File(sourceLocalDirectory, "subLocalSource");
|
||||
subSourceLocalDirectory.mkdir();
|
||||
file = new File(subSourceLocalDirectory, "subLocalSource1.txt");
|
||||
file.createNewFile();
|
||||
|
||||
targetLocalDirectory = new File(rootFolder, "slocalTarget");
|
||||
targetLocalDirectory.mkdir();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
server.setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String arg0, String arg1, ServerSession arg2) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
server.setPort(port);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser"));
|
||||
SftpSubsystem.Factory sftp = new SftpSubsystem.Factory();
|
||||
server.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(sftp));
|
||||
server.setFileSystemFactory(new NativeFileSystemFactory() {
|
||||
|
||||
@Override
|
||||
public FileSystemView createFileSystemView(org.apache.sshd.common.Session session) {
|
||||
return new NativeFileSystemView(session.getUsername(), false) {
|
||||
|
||||
@Override
|
||||
public String getVirtualUserDir() {
|
||||
return sftpRootFolder.getAbsolutePath();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
this.sftpFolder.create();
|
||||
this.localFolder.create();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.server.stop();
|
||||
this.sftpFolder.delete();
|
||||
this.localFolder.delete();
|
||||
}
|
||||
|
||||
public File getSourceLocalDirectory() {
|
||||
return this.sourceLocalDirectory;
|
||||
}
|
||||
|
||||
public File getTargetLocalDirectory() {
|
||||
return this.targetLocalDirectory;
|
||||
}
|
||||
|
||||
public String getTargetLocalDirectoryName() {
|
||||
return this.targetLocalDirectory.getAbsolutePath() + File.separator;
|
||||
}
|
||||
|
||||
public File getTargetSftpDirectory() {
|
||||
return this.targetSftpDirectory;
|
||||
}
|
||||
|
||||
public void recursiveDelete(File file) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File each : files) {
|
||||
recursiveDelete(each);
|
||||
}
|
||||
}
|
||||
if (!(file.equals(this.targetSftpDirectory) || file.equals(this.targetLocalDirectory))) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultSftpSessionFactory getSessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
|
||||
factory.setHost("localhost");
|
||||
factory.setPort(this.port);
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("foo");
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2014 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;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class TestSftpServerConfig {
|
||||
|
||||
@Bean
|
||||
public TestSftpServer sftpServer() {
|
||||
return new TestSftpServer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultSftpSessionFactory sftpSessionFactory(TestSftpServer server) {
|
||||
return sftpServer().getSessionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,11 +45,11 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.metadata.PropertiesPersistingMetadataStore;
|
||||
import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpSession;
|
||||
import org.springframework.integration.sftp.session.SftpTestSessionFactory;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
@@ -93,7 +93,6 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
ftpSessionFactory.setPassword("frog");
|
||||
ftpSessionFactory.setHost("foo.com");
|
||||
|
||||
|
||||
SftpInboundFileSynchronizer synchronizer = spy(new SftpInboundFileSynchronizer(ftpSessionFactory));
|
||||
synchronizer.setDeleteRemoteFiles(true);
|
||||
synchronizer.setPreserveTimestamp(true);
|
||||
@@ -168,7 +167,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session<LsEntry> getSession() {
|
||||
public SftpSession getSession() {
|
||||
if (this.sftpEntries.size() == 0) {
|
||||
this.init();
|
||||
}
|
||||
@@ -184,7 +183,8 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import org.springframework.integration.file.remote.session.CachingSessionFactory
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpSession;
|
||||
import org.springframework.integration.sftp.session.SftpTestSessionFactory;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
@@ -375,7 +376,7 @@ public class SftpOutboundTests {
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
@Override
|
||||
public Session<LsEntry> getSession() {
|
||||
public SftpSession getSession() {
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
|
||||
<int:channel id="inboundGet"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundGet"
|
||||
command="get"
|
||||
expression="payload"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory.toUpperCase()"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
local-directory-expression="@sftpServer.targetLocalDirectoryName + #remoteDirectory.toUpperCase()"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('sftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="invalidDirExpression"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="invalidDirExpression"
|
||||
command="get"
|
||||
expression="payload"
|
||||
@@ -33,40 +33,40 @@
|
||||
|
||||
<int:channel id="inboundMGet"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMGet"
|
||||
command="mget"
|
||||
expression="payload"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
local-directory-expression="@sftpServer.targetLocalDirectoryName + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('sftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMGetRecursive"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMGetRecursive"
|
||||
command="mget"
|
||||
expression="payload"
|
||||
command-options="-R"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
local-directory-expression="@sftpServer.targetLocalDirectoryName + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('sftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMGetRecursiveFiltered"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMGetRecursiveFiltered"
|
||||
command="mget"
|
||||
expression="payload"
|
||||
command-options="-R"
|
||||
filename-regex="(subSftpSource|.*1.txt)"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
local-directory-expression="@sftpServer.targetLocalDirectoryName + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('sftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPut"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMPut"
|
||||
command="mput"
|
||||
auto-create-directory="true"
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<int:channel id="inboundMPutRecursive"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMPutRecursive"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<int:channel id="inboundMPutRecursiveFiltered"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
|
||||
request-channel="inboundMPutRecursiveFiltered"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
@@ -100,32 +100,37 @@
|
||||
remote-directory="sftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory" />
|
||||
</bean>
|
||||
<int:channel id="appending" />
|
||||
|
||||
<beans profile="realSSH">
|
||||
<bean id="ftpSessionFactory"
|
||||
class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="user" value="ftptest"/>
|
||||
<property name="password" value="ftptest"/>
|
||||
</bean>
|
||||
</beans>
|
||||
<bean id="sftpServerConfig" class="org.springframework.integration.sftp.TestSftpServerConfig" />
|
||||
|
||||
<beans profile="realSSHSharedSession">
|
||||
<bean id="ftpSessionFactory"
|
||||
class="org.springframework.integration.file.remote.session.CachingSessionFactory">
|
||||
<constructor-arg>
|
||||
<bean
|
||||
class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<constructor-arg value="true"/>
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="user" value="ftptest"/>
|
||||
<property name="password" value="ftptest"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</beans>
|
||||
<int-sftp:outbound-channel-adapter id="appender"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="appending"
|
||||
mode="APPEND"
|
||||
use-temporary-file-name="false"
|
||||
remote-directory="sftpTarget"
|
||||
auto-create-directory="true"
|
||||
remote-file-separator="/" />
|
||||
|
||||
<int:channel id="ignoring" />
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="ignore"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="ignoring"
|
||||
mode="IGNORE"
|
||||
remote-directory="sftpTarget"
|
||||
auto-create-directory="true"
|
||||
remote-file-separator="/" />
|
||||
|
||||
<int:channel id="failing" />
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="fail"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="failing"
|
||||
mode="FAIL"
|
||||
remote-directory="sftpTarget"
|
||||
auto-create-directory="true"
|
||||
remote-file-separator="/" />
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -25,8 +26,6 @@ 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.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -44,29 +43,28 @@ 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.SessionCallback;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
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;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
import org.springframework.test.annotation.ProfileValueUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* Run with -Dspring-profiles-active=realSSH to run with a real SSH server.
|
||||
*
|
||||
* Assumes ftptest account on localhost with the following directory tree in the user's root...
|
||||
* Runs against an embedded SFTP Server with the following directory tree:
|
||||
*
|
||||
* <pre class="code">
|
||||
* $ tree sftpSource/
|
||||
@@ -113,81 +111,30 @@ public class SftpServerOutboundTests {
|
||||
private DirectChannel inboundMPutRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory<SftpFileInfo> sessionFactory;
|
||||
private DefaultSftpSessionFactory sessionFactory;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel appending;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel ignoring;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel failing;
|
||||
|
||||
@Autowired
|
||||
private TestSftpServer sftpServer;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
purge();
|
||||
setUpMocksIfNeeded();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void setUpMocksIfNeeded() throws IOException {
|
||||
String profile = ProfileValueUtils.retrieveProfileValueSource(this.getClass()).get("spring.profiles.active");
|
||||
boolean usingMocks = profile == null || ! profile.startsWith("realSSH");
|
||||
if (usingMocks) {
|
||||
Session session = mock(Session.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
LsEntry entry1 = mock(LsEntry.class);
|
||||
SftpATTRS attrs1 = mock(SftpATTRS.class);
|
||||
when(entry1.getAttrs()).thenReturn(attrs1);
|
||||
when(entry1.getFilename()).thenReturn("sftpSource1.txt");
|
||||
LsEntry entry2 = mock(LsEntry.class);
|
||||
SftpATTRS attrs2 = mock(SftpATTRS.class);
|
||||
when(entry2.getAttrs()).thenReturn(attrs2);
|
||||
when(entry2.getFilename()).thenReturn("sftpSource2.txt");
|
||||
LsEntry entry3 = mock(LsEntry.class);
|
||||
when(entry3.getFilename()).thenReturn("subSftpSource");
|
||||
SftpATTRS attrs3 = mock(SftpATTRS.class);
|
||||
when(entry3.getAttrs()).thenReturn(attrs3);
|
||||
when(attrs3.isDir()).thenReturn(true);
|
||||
LsEntry entry4 = mock(LsEntry.class);
|
||||
SftpATTRS attrs4 = mock(SftpATTRS.class);
|
||||
when(entry4.getAttrs()).thenReturn(attrs4);
|
||||
// recursion uses a DFA to update the filename to include the subdirectory
|
||||
new DirectFieldAccessor(entry4).setPropertyValue("filename", "subSftpSource1.txt");
|
||||
when(entry4.getFilename()).thenCallRealMethod();
|
||||
when(session.list("sftpSource/sftpSource1.txt")).thenReturn(new LsEntry[] {
|
||||
entry1
|
||||
});
|
||||
when(session.list("sftpSource/")).thenReturn(new LsEntry[] {
|
||||
entry1, entry2, entry3
|
||||
});
|
||||
when(session.list("sftpSource/subSftpSource/")).thenReturn(new LsEntry[] {
|
||||
entry4
|
||||
});
|
||||
when(session.list("sftpSource/subSftpSource/subSftpSource1.txt")).thenReturn(new LsEntry[] {
|
||||
entry4
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void purge() {
|
||||
File local = new File("/tmp/sftpOutboundTests/");
|
||||
purge(local);
|
||||
local.delete();
|
||||
}
|
||||
|
||||
private void purge(File local) {
|
||||
File[] files = local.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
this.purge(file);
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
public void setup() {
|
||||
this.sftpServer.recursiveDelete(sftpServer.getTargetLocalDirectory());
|
||||
this.sftpServer.recursiveDelete(sftpServer.getTargetSftpDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2866LocalDirectoryExpressionGET() {
|
||||
Session<?> session = null;
|
||||
boolean sharedSession = "realSSHSharedSession".equals(System.getProperty("spring.profiles.active"));
|
||||
if (sharedSession) {
|
||||
session = this.sessionFactory.getSession();
|
||||
}
|
||||
Session<?> session = this.sessionFactory.getSession();
|
||||
String dir = "sftpSource/";
|
||||
this.inboundGet.send(new GenericMessage<Object>(dir + "sftpSource1.txt"));
|
||||
Message<?> result = this.output.receive(1000);
|
||||
@@ -203,11 +150,9 @@ public class SftpServerOutboundTests {
|
||||
localFile = (File) result.getPayload();
|
||||
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
|
||||
Matchers.containsString(dir.toUpperCase()));
|
||||
if (sharedSession) {
|
||||
Session<?> session2 = this.sessionFactory.getSession();
|
||||
assertSame(TestUtils.getPropertyValue(session, "targetSession.jschSession"),
|
||||
TestUtils.getPropertyValue(session2, "targetSession.jschSession"));
|
||||
}
|
||||
Session<?> session2 = this.sessionFactory.getSession();
|
||||
assertSame(TestUtils.getPropertyValue(session, "jschSession"),
|
||||
TestUtils.getPropertyValue(session2, "jschSession"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -294,7 +239,6 @@ public class SftpServerOutboundTests {
|
||||
* Only runs with a real server (see class javadocs).
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3100RawGET() throws Exception {
|
||||
Session<?> session = this.sessionFactory.getSession();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
@@ -311,7 +255,6 @@ public class SftpServerOutboundTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSHSharedSession")
|
||||
public void testInt3047ConcurrentSharedSession() throws Exception {
|
||||
final Session<?> session1 = this.sessionFactory.getSession();
|
||||
final Session<?> session2 = this.sessionFactory.getSession();
|
||||
@@ -371,12 +314,11 @@ public class SftpServerOutboundTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutNotRecursive() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPut.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
this.inboundMPut.send(new GenericMessage<File>(this.sftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
@@ -385,19 +327,18 @@ public class SftpServerOutboundTests {
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutRecursive() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPutRecursive.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
this.inboundMPutRecursive.send(new GenericMessage<File>(this.sftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
@@ -406,25 +347,24 @@ public class SftpServerOutboundTests {
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
|
||||
equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
|
||||
equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(2),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
|
||||
equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutRecursiveFiltered() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(this.sftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
@@ -433,12 +373,47 @@ public class SftpServerOutboundTests {
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
|
||||
equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
|
||||
equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3412FileMode() {
|
||||
Message<String> m = MessageBuilder.withPayload("foo")
|
||||
.setHeader(FileHeaders.FILENAME, "appending.txt")
|
||||
.build();
|
||||
appending.send(m);
|
||||
appending.send(m);
|
||||
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
|
||||
assertLength6(template);
|
||||
|
||||
ignoring.send(m);
|
||||
assertLength6(template);
|
||||
try {
|
||||
failing.send(m);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertThat(e.getCause().getCause().getMessage(), containsString("The destination file already exists"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void assertLength6(SftpRemoteFileTemplate template) {
|
||||
LsEntry[] files = template.execute(new SessionCallback<LsEntry, LsEntry[]>() {
|
||||
|
||||
@Override
|
||||
public LsEntry[] doInSession(Session<LsEntry> session) throws IOException {
|
||||
return session.list("sftpTarget/appending.txt");
|
||||
}
|
||||
});
|
||||
assertEquals(1, files.length);
|
||||
assertEquals(6, files[0].getAttrs().getSize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2014 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.session;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
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.Session;
|
||||
import org.springframework.integration.sftp.TestSftpServer;
|
||||
import org.springframework.integration.sftp.TestSftpServerConfig;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration(classes=TestSftpServerConfig.class)
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class SftpRemoteFileTemplateTests {
|
||||
|
||||
@Autowired
|
||||
private TestSftpServer sftpServer;
|
||||
|
||||
@Autowired
|
||||
private DefaultSftpSessionFactory sessionFactory;
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void setup() {
|
||||
this.sftpServer.recursiveDelete(sftpServer.getTargetLocalDirectory());
|
||||
this.sftpServer.recursiveDelete(sftpServer.getTargetSftpDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testINT3412AppendStatRmdir() {
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
|
||||
DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
|
||||
fileNameGenerator.setExpression("'foobar.txt'");
|
||||
template.setFileNameGenerator(fileNameGenerator);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
template.setUseTemporaryFileName(false);
|
||||
template.execute(new SessionCallback<LsEntry, Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean doInSession(Session<LsEntry> session) throws IOException {
|
||||
session.mkdir("foo/");
|
||||
return session.mkdir("foo/bar/");
|
||||
}
|
||||
|
||||
});
|
||||
template.append(new GenericMessage<String>("foo"));
|
||||
template.append(new GenericMessage<String>("bar"));
|
||||
assertTrue(template.exists("foo/foobar.txt"));
|
||||
template.executeWithClient(new ClientCallbackWithoutResult<ChannelSftp>() {
|
||||
|
||||
@Override
|
||||
public void doWithClientWithoutResult(ChannelSftp client) {
|
||||
try {
|
||||
SftpATTRS file = client.lstat("foo/foobar.txt");
|
||||
assertEquals(6, file.getSize());
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
template.execute(new SessionCallbackWithoutResult<LsEntry>() {
|
||||
|
||||
@Override
|
||||
public void doInSessionWithoutResult(Session<LsEntry> session) throws IOException {
|
||||
assertTrue(session.remove("foo/foobar.txt"));
|
||||
assertTrue(session.rmdir("foo/bar/"));
|
||||
LsEntry[] files = session.list("foo/");
|
||||
assertEquals(0, files.length);
|
||||
assertTrue(session.rmdir("foo/"));
|
||||
}
|
||||
});
|
||||
assertFalse(template.exists("foo"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -15,17 +15,15 @@
|
||||
*/
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
* @author Gary Russell
|
||||
*
|
||||
*/
|
||||
public class SftpTestSessionFactory {
|
||||
|
||||
public static Session<LsEntry> createSftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
public static SftpSession createSftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
SftpSession sftpSession = new SftpSession(jschSession);
|
||||
sftpSession.connect();
|
||||
return sftpSession;
|
||||
|
||||
Reference in New Issue
Block a user