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:
Artem Bilan
2013-10-15 18:03:22 +03:00
committed by Gary Russell
parent 06979d7678
commit dd479a3ce7
15 changed files with 816 additions and 60 deletions

View File

@@ -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");

View File

@@ -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());