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
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user