INT-2866: Add (S)FTP local-directory-expression
* Add `local-directory-expression` to (S)FTP Outbound Gateways * Add `FtpServerRule` to Apache Mina embedded FtpServer * Add tests for (M)GET and `local-directory-expression`: FTP tests uses `FtpServerRule`, SFTP tests need testing on real SFTP server JIRA: https://jira.springsource.org/browse/INT-2866 INT-2866: Documentation INT-2866 Polishing - Remove leading / - Change \ to / in invalid test - Clean up after sftp Tested with real SSH. INT-2866 Polishing - Add Mock SFTP Test Run with -Dspring-profiles-active=realSSH to run with a real SSH server. Assumes ftptest/ftptest account on localhost with the following directory tree in the user's root... $ tree sftpSource/ sftpSource/ ├── sftpSource1.txt ├── sftpSource2.txt └── subSftpSource └── subSftpSource1.txt INT-2866: Polishing INT-2866: change `remotePath` to `remoteDirectory` Doc Polishing.
This commit is contained in:
committed by
Gary Russell
parent
06979d7678
commit
dd479a3ce7
@@ -414,6 +414,23 @@
|
||||
Identifies directory path (e.g.,
|
||||
"/local/mytransfers") where file will be
|
||||
transferred TO.
|
||||
This attribute is mutually exclusive with 'local-directory-expression'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="local-directory-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies SpEL expression to
|
||||
generate the directory path where file will be
|
||||
transferred TO, when using 'get' and 'mget' commands.
|
||||
The root object of the SpEL evaluation is the request Message,
|
||||
but the name of the source
|
||||
remote directory is also provided as the 'remoteDirectory' variable.
|
||||
For example, a valid expression might be:
|
||||
"'/local/' + #remoteDirectory.toUpperCase() + headers.foo".
|
||||
Only used with 'get' and 'mget' commands.
|
||||
This attribute is mutually exclusive with 'local-directory'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -75,7 +75,7 @@ public class SftpOutboundGatewayParserTests {
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
@@ -97,7 +97,7 @@ public class SftpOutboundGatewayParserTests {
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertEquals(Command.GET, TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertFalse(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<int:channel id="output">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int:channel id="inboundGet"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundGet"
|
||||
command="get"
|
||||
expression="payload"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory.toUpperCase()"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="invalidDirExpression"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="invalidDirExpression"
|
||||
command="get"
|
||||
expression="payload"
|
||||
local-directory-expression="T(java.io.File).separator + #remoteDirectory + '?:'"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMGet"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMGet"
|
||||
command="mget"
|
||||
expression="payload"
|
||||
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory" />
|
||||
</bean>
|
||||
|
||||
<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>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
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.integration.Message;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
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...
|
||||
*
|
||||
* <pre class="code">
|
||||
* $ tree sftpSource/
|
||||
* sftpSource/
|
||||
* ├── sftpSource1.txt
|
||||
* ├── sftpSource2.txt
|
||||
* └── subSftpSource
|
||||
* └── subSftpSource1.txt
|
||||
* </pre>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class SftpServerOutboundTests {
|
||||
|
||||
@Autowired
|
||||
private PollableChannel output;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundGet;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel invalidDirExpression;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMGet;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory<SftpFileInfo> sessionFactory;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
purge();
|
||||
setUpMocksIfNeeded();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void setUpMocksIfNeeded() throws IOException {
|
||||
if (sessionFactory.toString().startsWith("Mock for")) {
|
||||
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);
|
||||
when(entry4.getFilename()).thenReturn("subSftpSource1.txt");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2866LocalDirectoryExpressionGET() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundGet.send(new GenericMessage<Object>(dir + "sftpSource1.txt"));
|
||||
Message<?> result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
File localFile = (File) result.getPayload();
|
||||
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
|
||||
Matchers.containsString(dir.toUpperCase()));
|
||||
|
||||
dir = "sftpSource/subSftpSource/";
|
||||
this.inboundGet.send(new GenericMessage<Object>(dir + "subSftpSource1.txt"));
|
||||
result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
localFile = (File) result.getPayload();
|
||||
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
|
||||
Matchers.containsString(dir.toUpperCase()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2866InvalidLocalDirectoryExpression() {
|
||||
try {
|
||||
this.invalidDirExpression.send(new GenericMessage<Object>("sftpSource/sftpSource1.txt"));
|
||||
fail("Exception expected.");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Throwable cause = e.getCause();
|
||||
assertThat(cause, Matchers.instanceOf(IllegalArgumentException.class));
|
||||
assertThat(cause.getMessage(), Matchers.startsWith("Failed to make local directory"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testInt2866LocalDirectoryExpressionMGET() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
|
||||
Message<?> result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
List<File> localFiles = (List<File>) result.getPayload();
|
||||
|
||||
for (File file : localFiles) {
|
||||
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
|
||||
Matchers.containsString(dir));
|
||||
}
|
||||
|
||||
dir = "sftpSource/subSftpSource/";
|
||||
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
|
||||
result = this.output.receive(1000);
|
||||
assertNotNull(result);
|
||||
localFiles = (List<File>) result.getPayload();
|
||||
|
||||
for (File file : localFiles) {
|
||||
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
|
||||
Matchers.containsString(dir));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user