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
@@ -57,6 +57,7 @@ subprojects { subproject ->
|
||||
log4jVersion = '1.2.12'
|
||||
mockitoVersion = '1.9.5'
|
||||
eaioUUIDVersion = '3.2'
|
||||
ftpServerVersion = '1.0.6'
|
||||
|
||||
springVersionDefault = '3.1.4.RELEASE'
|
||||
springVersion = project.hasProperty('springVersion') ? getProperty('springVersion') : springVersionDefault
|
||||
@@ -239,6 +240,7 @@ project('spring-integration-ftp') {
|
||||
compile "org.springframework:spring-context-support:$springVersion"
|
||||
compile("javax.activation:activation:$javaxActivationVersion", optional)
|
||||
testCompile project(":spring-integration-test")
|
||||
testCompile "org.apache.ftpserver:ftpserver-core:$ftpServerVersion"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.integration.file.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
@@ -53,7 +54,12 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
|
||||
this.configureFilter(builder, element, parserContext);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-directory");
|
||||
|
||||
BeanDefinition localDirExpressionDef = IntegrationNamespaceUtils
|
||||
.createExpressionDefinitionFromValueOrExpression("local-directory", "local-directory-expression",
|
||||
parserContext, element, false);
|
||||
builder.addPropertyValue("localDirectoryExpression", localDirExpressionDef);
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-local-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "rename-expression");
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
@@ -170,7 +171,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private volatile String remoteFileSeparator = "/";
|
||||
|
||||
private volatile File localDirectory;
|
||||
private volatile Expression localDirectoryExpression;
|
||||
|
||||
private volatile boolean autoCreateLocalDirectory = true;
|
||||
|
||||
@@ -225,7 +226,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
* @param localDirectory the localDirectory to set
|
||||
*/
|
||||
public void setLocalDirectory(File localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
if (localDirectory != null) {
|
||||
this.localDirectoryExpression = new LiteralExpression(localDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalDirectoryExpression(Expression localDirectoryExpression) {
|
||||
this.localDirectoryExpression = localDirectoryExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,28 +278,31 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
if (Command.GET.equals(this.command)
|
||||
|| Command.MGET.equals(this.command)) {
|
||||
Assert.notNull(this.localDirectory, "localDirectory must not be null");
|
||||
try {
|
||||
if (!this.localDirectory.exists()) {
|
||||
if (this.autoCreateLocalDirectory) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
|
||||
Assert.notNull(this.localDirectoryExpression, "localDirectory must not be null");
|
||||
if (this.localDirectoryExpression instanceof LiteralExpression) {
|
||||
File localDirectory = new File(this.localDirectoryExpression.getExpressionString());
|
||||
try {
|
||||
if (!localDirectory.exists()) {
|
||||
if (this.autoCreateLocalDirectory) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The '" + localDirectory + "' directory doesn't exist; Will create.");
|
||||
}
|
||||
if (!localDirectory.mkdirs()) {
|
||||
throw new IOException("Failed to make local directory: " + localDirectory);
|
||||
}
|
||||
}
|
||||
if (!this.localDirectory.mkdirs()) {
|
||||
throw new IOException("Failed to make local directory: " + this.localDirectory);
|
||||
else {
|
||||
throw new FileNotFoundException(localDirectory.getName());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException(this.localDirectory.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException(
|
||||
"Failure during initialization of: " + this.getComponentType(), e);
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException(
|
||||
"Failure during initialization of: " + this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.getBeanFactory() != null) {
|
||||
@@ -341,12 +351,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private Object doGet(Message<?> requestMessage, Session<F> session) throws IOException {
|
||||
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
File payload = get(requestMessage, session, remoteFilePath, remoteFilename, true);
|
||||
String remoteFilename = this.getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
File payload = this.get(requestMessage, session, remoteDir, remoteFilePath, remoteFilename, true);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
@@ -355,12 +362,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private Object doMget(Message<?> requestMessage, Session<F> session) throws IOException {
|
||||
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
List<File> payload = mGet(requestMessage, session, remoteDir, remoteFilename);
|
||||
String remoteFilename = this.getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
List<File> payload = this.mGet(requestMessage, session, remoteDir, remoteFilename);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
@@ -369,12 +373,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private Object doRm(Message<?> requestMessage, Session<F> session) throws IOException {
|
||||
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
boolean payload = rm(session, remoteFilePath);
|
||||
String remoteFilename = this.getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
boolean payload = this.rm(session, remoteFilePath);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
@@ -383,14 +384,12 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private Object doMv(Message<?> requestMessage, Session<F> session) throws IOException {
|
||||
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
String remoteFilename = this.getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
|
||||
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
mv(session, remoteFilePath, remoteFileNewPath);
|
||||
|
||||
this.mv(session, remoteFilePath, remoteFileNewPath);
|
||||
return MessageBuilder.withPayload(Boolean.TRUE)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
@@ -405,7 +404,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
Collection<F> filteredFiles = this.filterFiles(files);
|
||||
for (F file : filteredFiles) {
|
||||
if (file != null) {
|
||||
if (this.options.contains(Option.SUBDIRS) || !isDirectory(file)) {
|
||||
if (this.options.contains(Option.SUBDIRS) || !this.isDirectory(file)) {
|
||||
lsFiles.add(file);
|
||||
}
|
||||
}
|
||||
@@ -467,21 +466,25 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
/**
|
||||
* Copy a remote file to the configured local directory.
|
||||
*
|
||||
*
|
||||
* @param message
|
||||
* @param session
|
||||
* @param remoteFilePath
|
||||
* @throws IOException
|
||||
* @param remoteDir
|
||||
*@param remoteFilePath @throws IOException
|
||||
*/
|
||||
protected File get(Message<?> message, Session<F> session, String remoteFilePath, String remoteFilename, boolean lsFirst)
|
||||
protected File get(Message<?> message, Session<F> session, String remoteDir, String remoteFilePath, String remoteFilename, boolean lsFirst)
|
||||
throws IOException {
|
||||
F[] files = null;
|
||||
if (lsFirst) {
|
||||
files = session.list(remoteFilePath);
|
||||
if (files == null) {
|
||||
throw new MessagingException("Session returned null when listing " + remoteFilePath);
|
||||
}
|
||||
if (files.length != 1 || isDirectory(files[0]) || isLink(files[0])) {
|
||||
throw new MessagingException(remoteFilePath + " is not a file");
|
||||
}
|
||||
}
|
||||
File localFile = new File(this.localDirectory, this.generateLocalFileName(message, remoteFilename));
|
||||
File localFile = new File(this.generateLocalDirectory(message, remoteDir), this.generateLocalFileName(message, remoteFilename));
|
||||
if (!localFile.exists()) {
|
||||
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
|
||||
File tempFile = new File(tempFileName);
|
||||
@@ -520,7 +523,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
protected List<File> mGet(Message<?> message, Session<F> session, String remoteDirectory,
|
||||
String remoteFilename) throws IOException {
|
||||
String path = generateFullPath(remoteDirectory, remoteFilename);
|
||||
String path = this.generateFullPath(remoteDirectory, remoteFilename);
|
||||
String[] fileNames = session.listNames(path);
|
||||
if (fileNames == null) {
|
||||
fileNames = new String[0];
|
||||
@@ -534,17 +537,26 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
File file;
|
||||
if (fileName.contains(this.remoteFileSeparator) &&
|
||||
fileName.startsWith(remoteDirectory)) { // the server returned the full path
|
||||
file = this.get(message, session, fileName,
|
||||
file = this.get(message, session, remoteDirectory, fileName,
|
||||
fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false);
|
||||
}
|
||||
else {
|
||||
file = this.get(message, session, generateFullPath(remoteDirectory, fileName), fileName, false);
|
||||
file = this.get(message, session, remoteDirectory,
|
||||
this.generateFullPath(remoteDirectory, fileName), fileName, false);
|
||||
}
|
||||
files.add(file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private String getRemoteDirectory(String remoteFilePath, String remoteFilename) {
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
return remoteDir;
|
||||
}
|
||||
|
||||
private String generateFullPath(String remoteDirectory, String remoteFilename) {
|
||||
String path;
|
||||
if (this.remoteFileSeparator.equals(remoteDirectory)) {
|
||||
@@ -588,6 +600,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
session.rename(remoteFilePath, remoteFileNewPath);
|
||||
}
|
||||
|
||||
private File generateLocalDirectory(Message<?> message, String remoteDirectory) {
|
||||
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
|
||||
evaluationContext.setVariable("remoteDirectory", remoteDirectory);
|
||||
// TODO Change 'desiredResultType' as 'File.class' after fix of SPR-10953.
|
||||
File localDir = new File(this.localDirectoryExpression.getValue(evaluationContext, message, String.class));
|
||||
if (!localDir.exists()) {
|
||||
Assert.isTrue(localDir.mkdirs(), "Failed to make local directory: " + localDir);
|
||||
}
|
||||
return localDir;
|
||||
}
|
||||
|
||||
private String generateLocalFileName(Message<?> message, String remoteFileName){
|
||||
if (this.localFilenameGeneratorExpression != null){
|
||||
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
|
||||
|
||||
@@ -415,6 +415,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>
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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.ftp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.ftpserver.FtpServer;
|
||||
import org.apache.ftpserver.FtpServerFactory;
|
||||
import org.apache.ftpserver.ftplet.Authentication;
|
||||
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
|
||||
import org.apache.ftpserver.ftplet.FtpException;
|
||||
import org.apache.ftpserver.ftplet.User;
|
||||
import org.apache.ftpserver.ftplet.UserManager;
|
||||
import org.apache.ftpserver.listener.ListenerFactory;
|
||||
import org.apache.ftpserver.usermanager.impl.BaseUser;
|
||||
import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission;
|
||||
import org.apache.ftpserver.usermanager.impl.TransferRatePermission;
|
||||
import org.apache.ftpserver.usermanager.impl.WritePermission;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.integration.test.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FtpServerRule extends ExternalResource {
|
||||
|
||||
public static int FTP_PORT = SocketUtils.findAvailableServerSocket();
|
||||
|
||||
private final TemporaryFolder ftpFolder;
|
||||
|
||||
private final TemporaryFolder localFolder;
|
||||
|
||||
private volatile File ftpRootFolder;
|
||||
|
||||
private volatile File sourceFtpDirectory;
|
||||
|
||||
private volatile File targetFtpDirectory;
|
||||
|
||||
private volatile File sourceLocalDirectory;
|
||||
|
||||
private volatile File targetLocalDirectory;
|
||||
|
||||
private volatile FtpServer server;
|
||||
|
||||
public FtpServerRule(final String root) {
|
||||
this.ftpFolder = new TemporaryFolder() {
|
||||
|
||||
@Override
|
||||
public void create() throws IOException {
|
||||
super.create();
|
||||
ftpRootFolder = this.newFolder(root);
|
||||
sourceFtpDirectory = new File(ftpRootFolder, "ftpSource");
|
||||
sourceFtpDirectory.mkdir();
|
||||
File file = new File(sourceFtpDirectory, "ftpSource1.txt");
|
||||
file.createNewFile();
|
||||
file = new File(sourceFtpDirectory, "ftpSource2.txt");
|
||||
file.createNewFile();
|
||||
|
||||
File subSourceFtpDirectory = new File(sourceFtpDirectory, "subFtpSource");
|
||||
subSourceFtpDirectory.mkdir();
|
||||
file = new File(subSourceFtpDirectory, "subFtpSource1.txt");
|
||||
file.createNewFile();
|
||||
|
||||
targetFtpDirectory = new File(ftpRootFolder, "ftpTarget");
|
||||
targetFtpDirectory.mkdirs();
|
||||
}
|
||||
};
|
||||
this.localFolder = new TemporaryFolder() {
|
||||
|
||||
@Override
|
||||
public void create() throws IOException {
|
||||
super.create();
|
||||
File rootFolder = this.newFolder(root);
|
||||
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, "localTarget");
|
||||
targetLocalDirectory.mkdirs();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public File getSourceFtpDirectory() {
|
||||
return sourceFtpDirectory;
|
||||
}
|
||||
|
||||
public File getTargetFtpDirectory() {
|
||||
return targetFtpDirectory;
|
||||
}
|
||||
|
||||
public File getSourceLocalDirectory() {
|
||||
return sourceLocalDirectory;
|
||||
}
|
||||
|
||||
public File getTargetLocalDirectory() {
|
||||
return targetLocalDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
this.ftpFolder.create();
|
||||
this.localFolder.create();
|
||||
|
||||
FtpServerFactory serverFactory = new FtpServerFactory();
|
||||
serverFactory.setUserManager(new TestUserManager(this.ftpRootFolder.getAbsolutePath()));
|
||||
|
||||
ListenerFactory factory = new ListenerFactory();
|
||||
factory.setPort(FTP_PORT);
|
||||
serverFactory.addListener("default", factory.createListener());
|
||||
|
||||
server = serverFactory.createServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
this.server.stop();
|
||||
this.ftpFolder.delete();
|
||||
this.localFolder.delete();
|
||||
}
|
||||
|
||||
|
||||
public static void recursiveDelete(File file) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File each : files) {
|
||||
recursiveDelete(each);
|
||||
}
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
private class TestUserManager implements UserManager {
|
||||
|
||||
private final BaseUser testUser;
|
||||
|
||||
private TestUserManager(String homeDirectory) {
|
||||
this.testUser = new BaseUser();
|
||||
this.testUser.setAuthorities(Arrays.asList(new ConcurrentLoginPermission(1024, 1024),
|
||||
new WritePermission(),
|
||||
new TransferRatePermission(1024, 1024)));
|
||||
this.testUser.setHomeDirectory(homeDirectory);
|
||||
this.testUser.setName("TEST_USER");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public User getUserByName(String s) throws FtpException {
|
||||
return this.testUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAllUserNames() throws FtpException {
|
||||
return new String[]{"TEST_USER"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String s) throws FtpException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(User user) throws FtpException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesExist(String s) throws FtpException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(Authentication authentication) throws AuthenticationFailedException {
|
||||
return this.testUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdminName() throws FtpException {
|
||||
return "admin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdmin(String s) throws FtpException {
|
||||
return s.equals("admin");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class FtpOutboundGatewayParserTests {
|
||||
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"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals(Command.LS, TestUtils.getPropertyValue(gateway, "command"));
|
||||
@@ -100,7 +100,7 @@ public class FtpOutboundGatewayParserTests {
|
||||
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"));
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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-ftp="http://www.springframework.org/schema/integration/ftp"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
|
||||
http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.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">
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="port" value="#{T(org.springframework.integration.ftp.FtpServerRule).FTP_PORT}"/>
|
||||
<property name="username" value="foo"/>
|
||||
<property name="password" value="foo"/>
|
||||
</bean>
|
||||
|
||||
<int:spel-function id="localDir" class="org.springframework.integration.ftp.outbound.FtpServerOutboundTests" method="localDirectory"/>
|
||||
|
||||
<int:channel id="output">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int:channel id="inboundGet"/>
|
||||
|
||||
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundGet"
|
||||
command="get"
|
||||
expression="payload"
|
||||
local-directory-expression="#localDir() + #remoteDirectory.toUpperCase()"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="invalidDirExpression"/>
|
||||
|
||||
<int-ftp: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-ftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMGet"
|
||||
command="mget"
|
||||
expression="payload"
|
||||
local-directory-expression="#localDir() + #remoteDirectory"
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.ftp.outbound;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
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.ftp.FtpServerRule;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FtpServerOutboundTests {
|
||||
|
||||
@ClassRule
|
||||
public static final FtpServerRule FTP_SERVER = new FtpServerRule(FtpServerOutboundTests.class.getSimpleName());
|
||||
|
||||
@Autowired
|
||||
private PollableChannel output;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundGet;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel invalidDirExpression;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMGet;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
FtpServerRule.recursiveDelete(FTP_SERVER.getTargetLocalDirectory());
|
||||
FtpServerRule.recursiveDelete(FTP_SERVER.getTargetFtpDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2866LocalDirectoryExpressionGET() {
|
||||
String dir = "ftpSource/";
|
||||
this.inboundGet.send(new GenericMessage<Object>(dir + "ftpSource1.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 = "ftpSource/subFtpSource/";
|
||||
this.inboundGet.send(new GenericMessage<Object>(dir + "subFtpSource1.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>("/ftpSource/ftpSource1.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 = "ftpSource/";
|
||||
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 = "ftpSource/subFtpSource/";
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
public static String localDirectory() {
|
||||
return FTP_SERVER.getTargetLocalDirectory().getAbsolutePath() + File.separator;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -433,7 +433,16 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
defines a SpEL expression to generate the name of local file(s) during the transfer.
|
||||
The root object of the evaluation context is the request Message but, in addition, the <code>remoteFileName</code>
|
||||
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
|
||||
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>
|
||||
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>.
|
||||
</para>
|
||||
<para>
|
||||
The <emphasis>get</emphasis> and <emphasis>mget</emphasis> commands support
|
||||
the <emphasis>local-directory-expression</emphasis> attribute. It
|
||||
defines a SpEL expression to generate the name of local directory(ies) during the transfer.
|
||||
The root object of the evaluation context is the request Message but, in addition, the <code>remoteDirectory</code>
|
||||
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
|
||||
example: <code>local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.foo"</code>.
|
||||
This attribute is mutually exclusive with <emphasis>local-directory</emphasis> attribute.
|
||||
</para>
|
||||
<para>
|
||||
For all commands, the PATH that the command acts on is provided by the 'expression'
|
||||
|
||||
@@ -471,6 +471,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
|
||||
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>
|
||||
</para>
|
||||
<para>
|
||||
The <emphasis>get</emphasis> and <emphasis>mget</emphasis> commands support
|
||||
the <emphasis>local-directory-expression</emphasis> attribute. It
|
||||
defines a SpEL expression to generate the name of local directory(ies) during the transfer.
|
||||
The root object of the evaluation context is the request Message but, in addition, the <code>remoteDirectory</code>
|
||||
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
|
||||
example: <code>local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.foo"</code>.
|
||||
This attribute is mutually exclusive with <emphasis>local-directory</emphasis> attribute.
|
||||
</para>
|
||||
<para>
|
||||
For all commands, the PATH that the command acts on is provided by the 'expression'
|
||||
property of the gateway. For the mget command, the expression might evaluate to '*', meaning
|
||||
|
||||
@@ -244,8 +244,14 @@
|
||||
<para>
|
||||
The <code>local-filename-generator-expression</code> attribute is now supported,
|
||||
enabling the naming of local files during transfer. By default, the same
|
||||
name as the remote file is used. For more information, see
|
||||
<xref linkend="ftp-outbound-gateway"/> and <xref linkend="sftp-outbound-gateway"/>.
|
||||
name as the remote file is used.
|
||||
</para>
|
||||
<para>
|
||||
The <code>local-directory-expression</code> attribute is now supported,
|
||||
enabling the naming of local directories during transfer based on the remote directory.
|
||||
</para>
|
||||
<para>
|
||||
For more information, see <xref linkend="ftp-outbound-gateway"/> and <xref linkend="sftp-outbound-gateway"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-jdbc-mysql-v5_6_4">
|
||||
|
||||
Reference in New Issue
Block a user