Merge pull request #779 from garyrussell/INT-2981
* garyrussell-INT-2981: INT-2981 - Add Rename (mv) to (S)FTP Gateway
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -35,4 +35,6 @@ public abstract class FileHeaders {
|
||||
|
||||
public static final String REMOTE_FILE = PREFIX + "remoteFile";
|
||||
|
||||
public static final String RENAME_TO = PREFIX + "renameTo";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
package org.springframework.integration.file.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.file.remote.session.SessionFactoryFactoryBean;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -60,6 +59,7 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-local-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "rename-expression");
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-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.file.remote;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
|
||||
/**
|
||||
* Utility methods for supporting remote file operations.
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class RemoteFileUtils {
|
||||
|
||||
private RemoteFileUtils() {}
|
||||
|
||||
/**
|
||||
* Recursively create remote directories.
|
||||
* @param <F> The session type.
|
||||
* @param path The directory path.
|
||||
* @param session The session.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static <F> void makeDirectories(String path, Session<F> session, String remoteFileSeparator, Log logger)
|
||||
throws IOException {
|
||||
|
||||
if (!session.exists(path)){
|
||||
|
||||
int nextSeparatorIndex = path.lastIndexOf(remoteFileSeparator);
|
||||
|
||||
if (nextSeparatorIndex > -1){
|
||||
List<String> pathsToCreate = new LinkedList<String>();
|
||||
while (nextSeparatorIndex > -1){
|
||||
String pathSegment = path.substring(0, nextSeparatorIndex);
|
||||
if (pathSegment.length() == 0 || session.exists(pathSegment)) {
|
||||
// no more paths to create
|
||||
break;
|
||||
}
|
||||
else {
|
||||
pathsToCreate.add(0, pathSegment);
|
||||
nextSeparatorIndex = pathSegment.lastIndexOf(remoteFileSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
for (String pathToCreate : pathsToCreate) {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Creating '" + pathToCreate + "'");
|
||||
}
|
||||
session.mkdir(pathToCreate);
|
||||
}
|
||||
}
|
||||
else {
|
||||
session.mkdir(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.RemoteFileUtils;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
@@ -54,36 +55,114 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
protected final SessionFactory<F> sessionFactory;
|
||||
|
||||
protected final String command;
|
||||
protected final Command command;
|
||||
|
||||
public static final String COMMAND_LS = "ls";
|
||||
/**
|
||||
* Enumeration of commands supported by the gateways.
|
||||
*/
|
||||
public static enum Command {
|
||||
/**
|
||||
* List remote files.
|
||||
*/
|
||||
LS("ls"),
|
||||
/**
|
||||
* Retrieve a remote file.
|
||||
*/
|
||||
GET("get"),
|
||||
/**
|
||||
* Remove a remote file (path - including wildcards).
|
||||
*/
|
||||
RM("rm"),
|
||||
/**
|
||||
* Retrieve multiple files matching a wildcard path.
|
||||
*/
|
||||
MGET("mget"),
|
||||
/**
|
||||
* Move (rename) a remote file.
|
||||
*/
|
||||
MV("mv");
|
||||
|
||||
public static final String COMMAND_GET = "get";
|
||||
private String command;
|
||||
|
||||
public static final String COMMAND_RM = "rm";
|
||||
private Command(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public static final String COMMAND_MGET = "mget";
|
||||
public String getCommand() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
public static final String OPTION_NAME_ONLY = "-1";
|
||||
public static Command toCommand(String cmd) {
|
||||
for (Command command : values()) {
|
||||
if (command.getCommand().equals(cmd)) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No Command with value '" + cmd + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public static final String OPTION_ALL = "-a";
|
||||
/**
|
||||
* Enumeration of options supported by various commands.
|
||||
*
|
||||
*/
|
||||
public static enum Option {
|
||||
/**
|
||||
* Don't return full file information; just the name (ls).
|
||||
*/
|
||||
NAME_ONLY("-1"),
|
||||
/**
|
||||
* Include directories {@code .} and {@code ..} in the results (ls).
|
||||
*/
|
||||
ALL("-a"),
|
||||
/**
|
||||
* Do not sort the results (ls with NAME_ONLY).
|
||||
*/
|
||||
NOSORT("-f"),
|
||||
/**
|
||||
* Include directories in the results (ls).
|
||||
*/
|
||||
SUBDIRS("-dirs"),
|
||||
/**
|
||||
* Include links in the results (ls).
|
||||
*/
|
||||
LINKS("-links"),
|
||||
/**
|
||||
* Preserve the server timestamp (get, mget).
|
||||
*/
|
||||
PRESERVE_TIMESTAMP("-P"),
|
||||
/**
|
||||
* Throw an exception if no files returned (mget).
|
||||
*/
|
||||
EXCEPTION_WHEN_EMPTY("-x");
|
||||
|
||||
public static final String OPTION_NOSORT = "-f";
|
||||
private String option;
|
||||
|
||||
public static final String OPTION_SUBDIRS = "-dirs";
|
||||
private Option(String option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
public static final String OPTION_LINKS = "-links";
|
||||
public String getOption() {
|
||||
return this.option;
|
||||
}
|
||||
|
||||
public static final String OPTION_PRESERVE_TIMESTAMP = "-P";
|
||||
public static Option toOption(String opt) {
|
||||
for (Option option : values()) {
|
||||
if (option.getOption().equals(opt)) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No option with value '" + opt + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public static final String OPTION_EXCEPTION_WHEN_EMPTY = "-x";
|
||||
private final ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
|
||||
|
||||
private final Set<String> supportedCommands = new HashSet<String>(Arrays.asList(
|
||||
COMMAND_LS, COMMAND_GET, COMMAND_RM, COMMAND_MGET));
|
||||
private volatile ExpressionEvaluatingMessageProcessor<String> renameProcessor =
|
||||
new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression("headers." + FileHeaders.RENAME_TO));
|
||||
|
||||
private final ExpressionEvaluatingMessageProcessor<String> processor;
|
||||
|
||||
protected volatile Set<String> options = new HashSet<String>();
|
||||
protected volatile Set<Option> options = new HashSet<Option>();
|
||||
|
||||
private volatile String remoteFileSeparator = "/";
|
||||
|
||||
@@ -102,8 +181,16 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, String command,
|
||||
String expression) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.command = Command.toCommand(command);
|
||||
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, Command command,
|
||||
String expression) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.command = command;
|
||||
this.processor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
@@ -114,7 +201,10 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
public void setOptions(String options) {
|
||||
String[] opts = options.split("\\s");
|
||||
for (String opt : opts) {
|
||||
this.options.add(opt.trim());
|
||||
String trimmedOpt = opt.trim();
|
||||
if (StringUtils.hasLength(trimmedOpt)) {
|
||||
this.options.add(Option.toOption(trimmedOpt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,21 +243,22 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public void setRenameExpression(String expression) {
|
||||
Assert.notNull(expression, "'expression' cannot be null");
|
||||
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
Assert.notNull(this.command, "command must not be null");
|
||||
Assert.isTrue(
|
||||
this.supportedCommands.contains(this.command),
|
||||
"command must be one of "
|
||||
+ StringUtils
|
||||
.collectionToCommaDelimitedString(this.supportedCommands));
|
||||
if (COMMAND_RM.equals(this.command) || COMMAND_MGET.equals(this.command) ||
|
||||
COMMAND_GET.equals(this.command)) {
|
||||
if (Command.RM.equals(this.command) || Command.MGET.equals(this.command) ||
|
||||
Command.GET.equals(this.command)) {
|
||||
Assert.isNull(this.filter, "Filters are not supported with the rm, get, and mget commands");
|
||||
}
|
||||
if (COMMAND_GET.equals(this.command)
|
||||
|| COMMAND_MGET.equals(this.command)) {
|
||||
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()) {
|
||||
@@ -193,7 +284,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
}
|
||||
if (this.getBeanFactory() != null) {
|
||||
this.processor.setBeanFactory(this.getBeanFactory());
|
||||
this.fileNameProcessor.setBeanFactory(this.getBeanFactory());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,65 +292,99 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
Session<F> session = this.sessionFactory.getSession();
|
||||
try {
|
||||
if (COMMAND_LS.equals(this.command)) {
|
||||
String dir = this.processor.processMessage(requestMessage);
|
||||
if (!dir.endsWith(this.remoteFileSeparator)) {
|
||||
dir += this.remoteFileSeparator;
|
||||
}
|
||||
List<?> payload = ls(session, dir);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir)
|
||||
.build();
|
||||
}
|
||||
else if (COMMAND_GET.equals(this.command)) {
|
||||
String remoteFilePath = this.processor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
File payload = get(session, remoteFilePath, remoteFilename, true);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
else if (COMMAND_MGET.equals(this.command)) {
|
||||
String remoteFilePath = this.processor.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(session, remoteDir, remoteFilename);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
else if (COMMAND_RM.equals(this.command)) {
|
||||
String remoteFilePath = this.processor.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);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
else {
|
||||
switch (this.command) {
|
||||
case LS:
|
||||
return doLs(requestMessage, session);
|
||||
case GET:
|
||||
return doGet(requestMessage, session);
|
||||
case MGET:
|
||||
return doMget(requestMessage, session);
|
||||
case RM:
|
||||
return doRm(requestMessage, session);
|
||||
case MV:
|
||||
return doMv(requestMessage, session);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new MessagingException(requestMessage, e);
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Object doLs(Message<?> requestMessage, Session<F> session) throws IOException {
|
||||
String dir = this.fileNameProcessor.processMessage(requestMessage);
|
||||
if (!dir.endsWith(this.remoteFileSeparator)) {
|
||||
dir += this.remoteFileSeparator;
|
||||
}
|
||||
List<?> payload = ls(session, dir);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir)
|
||||
.build();
|
||||
}
|
||||
|
||||
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(session, remoteFilePath, remoteFilename, true);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
|
||||
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(session, remoteDir, remoteFilename);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
|
||||
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);
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
}
|
||||
|
||||
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 remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
|
||||
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
}
|
||||
mv(session, remoteFilePath, remoteFileNewPath);
|
||||
return MessageBuilder.withPayload(Boolean.TRUE)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.RENAME_TO, remoteFileNewPath)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected List<?> ls(Session<F> session, String dir) throws IOException {
|
||||
List<F> lsFiles = new ArrayList<F>();
|
||||
F[] files = session.list(dir);
|
||||
@@ -267,7 +392,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) || !isDirectory(file)) {
|
||||
lsFiles.add(file);
|
||||
}
|
||||
}
|
||||
@@ -276,18 +401,18 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
else {
|
||||
return lsFiles;
|
||||
}
|
||||
if (!this.options.contains(OPTION_LINKS)) {
|
||||
if (!this.options.contains(Option.LINKS)) {
|
||||
purgeLinks(lsFiles);
|
||||
}
|
||||
if (!this.options.contains(OPTION_ALL)) {
|
||||
if (!this.options.contains(Option.ALL)) {
|
||||
purgeDots(lsFiles);
|
||||
}
|
||||
if (this.options.contains(OPTION_NAME_ONLY)) {
|
||||
if (this.options.contains(Option.NAME_ONLY)) {
|
||||
List<String> results = new ArrayList<String>();
|
||||
for (F file : lsFiles) {
|
||||
results.add(getFilename(file));
|
||||
}
|
||||
if (!this.options.contains(OPTION_NOSORT)) {
|
||||
if (!this.options.contains(Option.NOSORT)) {
|
||||
Collections.sort(results);
|
||||
}
|
||||
return results;
|
||||
@@ -297,7 +422,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
for (AbstractFileInfo<F> file : canonicalFiles) {
|
||||
file.setRemoteDirectory(dir);
|
||||
}
|
||||
if (!this.options.contains(OPTION_NOSORT)) {
|
||||
if (!this.options.contains(Option.NOSORT)) {
|
||||
Collections.sort(canonicalFiles);
|
||||
}
|
||||
return canonicalFiles;
|
||||
@@ -367,7 +492,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
if (!tempFile.renameTo(localFile)) {
|
||||
throw new MessagingException("Failed to rename local file");
|
||||
}
|
||||
if (lsFirst && this.options.contains(OPTION_PRESERVE_TIMESTAMP)) {
|
||||
if (lsFirst && this.options.contains(Option.PRESERVE_TIMESTAMP)) {
|
||||
localFile.setLastModified(getModified(files[0]));
|
||||
}
|
||||
return localFile;
|
||||
@@ -384,7 +509,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
if (fileNames == null) {
|
||||
fileNames = new String[0];
|
||||
}
|
||||
if (fileNames.length == 0 && this.options.contains(OPTION_EXCEPTION_WHEN_EMPTY)) {
|
||||
if (fileNames.length == 0 && this.options.contains(Option.EXCEPTION_WHEN_EMPTY)) {
|
||||
throw new MessagingException("No files found at " + remoteDirectory
|
||||
+ " with pattern " + remoteFilename);
|
||||
}
|
||||
@@ -438,6 +563,15 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
return session.remove(remoteFilePath);
|
||||
}
|
||||
|
||||
protected void mv(Session<?> session, String remoteFilePath, String remoteFileNewPath) throws IOException {
|
||||
int lastSeparator = remoteFileNewPath.lastIndexOf(this.remoteFileSeparator);
|
||||
if (lastSeparator > 0) {
|
||||
String remoteFileDirectory = remoteFileNewPath.substring(0, lastSeparator + 1);
|
||||
RemoteFileUtils.makeDirectories(remoteFileDirectory, session, this.remoteFileSeparator, this.logger);
|
||||
}
|
||||
session.rename(remoteFilePath, remoteFileNewPath);
|
||||
}
|
||||
|
||||
abstract protected boolean isDirectory(F file);
|
||||
|
||||
abstract protected boolean isLink(F file);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -20,8 +20,6 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
@@ -31,6 +29,7 @@ import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.remote.RemoteFileUtils;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
@@ -246,7 +245,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
|
||||
if (this.autoCreateDirectory) {
|
||||
try {
|
||||
this.makeDirectories(remoteDirectory, session);
|
||||
RemoteFileUtils.makeDirectories(remoteDirectory, session, this.remoteFileSeparator, this.logger);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility
|
||||
@@ -280,35 +279,4 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
private void makeDirectories(String path, Session<F> session) throws IOException {
|
||||
if (!session.exists(path)){
|
||||
|
||||
int nextSeparatorIndex = path.lastIndexOf(this.remoteFileSeparator);
|
||||
|
||||
if (nextSeparatorIndex > -1){
|
||||
List<String> pathsToCreate = new LinkedList<String>();
|
||||
while (nextSeparatorIndex > -1){
|
||||
String pathSegment = path.substring(0, nextSeparatorIndex);
|
||||
if (pathSegment.length() == 0 || session.exists(pathSegment)) {
|
||||
// no more paths to create
|
||||
break;
|
||||
}
|
||||
else {
|
||||
pathsToCreate.add(0, pathSegment);
|
||||
nextSeparatorIndex = pathSegment.lastIndexOf(this.remoteFileSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
for (String pathToCreate : pathsToCreate) {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Creating '" + pathToCreate + "'");
|
||||
}
|
||||
session.mkdir(pathToCreate);
|
||||
}
|
||||
}
|
||||
else {
|
||||
session.mkdir(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,4 +562,14 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="remoteGatewayCommand">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="ls"/>
|
||||
<xsd:enumeration value="get"/>
|
||||
<xsd:enumeration value="rm"/>
|
||||
<xsd:enumeration value="mget"/>
|
||||
<xsd:enumeration value="mv"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -32,8 +34,11 @@ import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
@@ -42,6 +47,7 @@ import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
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.support.MessageBuilder;
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,7 +58,7 @@ import org.springframework.integration.message.GenericMessage;
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
private String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
private final String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
@@ -292,6 +298,91 @@ public class RemoteFileOutboundGatewayTests {
|
||||
gw.handleRequestMessage(new GenericMessage<String>("testremote/*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMove() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "mv", "payload");
|
||||
Session<?> session = mock(Session.class);
|
||||
final AtomicReference<String> args = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
Object[] arguments = invocation.getArguments();
|
||||
args.set((String) arguments[0] + (String) arguments[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).rename(anyString(), anyString());
|
||||
when (sessionFactory.getSession()).thenReturn(session);
|
||||
Message<String> requestMessage = MessageBuilder.withPayload("foo")
|
||||
.setHeader(FileHeaders.RENAME_TO, "bar")
|
||||
.build();
|
||||
Message<?> out = (Message<?>) gw.handleRequestMessage(requestMessage);
|
||||
assertEquals("foo", out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
assertEquals("foobar", args.get());
|
||||
assertEquals(Boolean.TRUE, out.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveWithExpression() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "mv", "payload");
|
||||
gw.setRenameExpression("payload.substring(1)");
|
||||
Session<?> session = mock(Session.class);
|
||||
final AtomicReference<String> args = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
Object[] arguments = invocation.getArguments();
|
||||
args.set((String) arguments[0] + (String) arguments[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).rename(anyString(), anyString());
|
||||
when (sessionFactory.getSession()).thenReturn(session);
|
||||
Message<?> out = (Message<?>) gw.handleRequestMessage(new GenericMessage<String>("foo"));
|
||||
assertEquals("oo", out.getHeaders().get(FileHeaders.RENAME_TO));
|
||||
assertEquals("foo", out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
assertEquals("foooo", args.get());
|
||||
assertEquals(Boolean.TRUE, out.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveWithMkDirs() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "mv", "payload");
|
||||
gw.setRenameExpression("'foo/bar/baz'");
|
||||
Session<?> session = mock(Session.class);
|
||||
final AtomicReference<String> args = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
Object[] arguments = invocation.getArguments();
|
||||
args.set((String) arguments[0] + (String) arguments[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).rename(anyString(), anyString());
|
||||
final List<String> madeDirs = new ArrayList<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
madeDirs.add((String) invocation.getArguments()[0]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).mkdir(anyString());
|
||||
when (sessionFactory.getSession()).thenReturn(session);
|
||||
Message<String> requestMessage = MessageBuilder.withPayload("foo")
|
||||
.setHeader(FileHeaders.RENAME_TO, "bar")
|
||||
.build();
|
||||
Message<?> out = (Message<?>) gw.handleRequestMessage(requestMessage);
|
||||
assertEquals("foo", out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
assertEquals("foofoo/bar/baz", args.get());
|
||||
assertEquals(Boolean.TRUE, out.getPayload());
|
||||
assertEquals(2, madeDirs.size());
|
||||
assertEquals("foo", madeDirs.get(0));
|
||||
assertEquals("foo/bar", madeDirs.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
@@ -650,7 +741,7 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public TestRemoteFileOutboundGateway(SessionFactory sessionFactory,
|
||||
String command, String expression) {
|
||||
super(sessionFactory, command, expression);
|
||||
super(sessionFactory, Command.toCommand(command), expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -683,12 +774,12 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
|
||||
|
||||
class TestLsEntry extends AbstractFileInfo<TestLsEntry> {
|
||||
|
||||
private String filename;
|
||||
private int size;
|
||||
private boolean dir;
|
||||
private boolean link;
|
||||
private long modified;
|
||||
private String permissions;
|
||||
private final String filename;
|
||||
private final int size;
|
||||
private final boolean dir;
|
||||
private final boolean link;
|
||||
private final long modified;
|
||||
private final String permissions;
|
||||
|
||||
public TestLsEntry(String filename, int size, boolean dir, boolean link,
|
||||
long modified, String permissions) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:tool="http://www.springframework.org/schema/tool"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-file="http://www.springframework.org/schema/integration/file"
|
||||
targetNamespace="http://www.springframework.org/schema/integration/ftp"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||
|
||||
@@ -10,6 +11,8 @@
|
||||
<xsd:import namespace="http://www.springframework.org/schema/tool" />
|
||||
<xsd:import namespace="http://www.springframework.org/schema/integration"
|
||||
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-3.0.xsd" />
|
||||
<xsd:import namespace="http://www.springframework.org/schema/integration/file"
|
||||
schemaLocation="http://www.springframework.org/schema/integration/file/spring-integration-file-3.0.xsd" />
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
@@ -238,12 +241,15 @@
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="command" use="required" type="xsd:string">
|
||||
<xsd:attribute name="command" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
FTP command - ls, get or rm
|
||||
FTP command.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="int-file:remoteGatewayCommand xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command-options" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
@@ -270,6 +276,15 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="rename-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
SpEL expression representing the path for the
|
||||
new filename when using the 'mv' command. Defaults
|
||||
to "headers.['file_renameTo']".
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
@@ -507,5 +522,4 @@
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
@@ -47,6 +47,17 @@
|
||||
</int-ftp:request-handler-advice-chain>
|
||||
</int-ftp:outbound-gateway>
|
||||
|
||||
<int-ftp:outbound-gateway id="gateway3"
|
||||
session-factory="sf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
auto-startup="false"
|
||||
command="mv"
|
||||
expression="payload"
|
||||
rename-expression="'foo'"
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-ftp:outbound-gateway id="withBeanExpression"
|
||||
local-directory="local-test-dir"
|
||||
session-factory="sf"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -28,6 +28,8 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Option;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.ftp.gateway.FtpOutboundGateway;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
@@ -56,6 +58,9 @@ public class FtpOutboundGatewayParserTests {
|
||||
@Autowired
|
||||
AbstractEndpoint gateway2;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway3;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint withBeanExpression;
|
||||
|
||||
@@ -71,11 +76,11 @@ public class FtpOutboundGatewayParserTests {
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("ls", TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals(Command.LS, TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-1"));
|
||||
assertTrue(options.contains("-f"));
|
||||
assertTrue(options.contains(Option.NAME_ONLY));
|
||||
assertTrue(options.contains(Option.NOSORT));
|
||||
|
||||
Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class);
|
||||
assertEquals(Long.valueOf(777), sendTimeout);
|
||||
@@ -91,19 +96,29 @@ public class FtpOutboundGatewayParserTests {
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertEquals("get", TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals(Command.GET, TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-P"));
|
||||
assertTrue(options.contains(Option.PRESERVE_TIMESTAMP));
|
||||
gateway.handleMessage(new GenericMessage<String>("foo"));
|
||||
assertEquals(1, adviceCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGatewayMv() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway3,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(Command.MV, TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBeanExpression() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(withBeanExpression,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
ExpressionEvaluatingMessageProcessor<?> processor = TestUtils.getPropertyValue(gateway, "processor",
|
||||
ExpressionEvaluatingMessageProcessor<?> processor = TestUtils.getPropertyValue(gateway, "fileNameProcessor",
|
||||
ExpressionEvaluatingMessageProcessor.class);
|
||||
assertNotNull(processor);
|
||||
assertEquals("foo", processor.processMessage(MessageBuilder.withPayload("bar").build()));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:tool="http://www.springframework.org/schema/tool"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-file="http://www.springframework.org/schema/integration/file"
|
||||
targetNamespace="http://www.springframework.org/schema/integration/sftp"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||
|
||||
@@ -10,7 +11,8 @@
|
||||
<xsd:import namespace="http://www.springframework.org/schema/tool" />
|
||||
<xsd:import namespace="http://www.springframework.org/schema/integration"
|
||||
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-3.0.xsd" />
|
||||
|
||||
<xsd:import namespace="http://www.springframework.org/schema/integration/file"
|
||||
schemaLocation="http://www.springframework.org/schema/integration/file/spring-integration-file-3.0.xsd" />
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
@@ -240,12 +242,15 @@
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="command" use="required" type="xsd:string">
|
||||
<xsd:attribute name="command" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
sftp command - ls, get or rm
|
||||
sftp command.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="int-file:remoteGatewayCommand xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command-options" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
@@ -272,6 +277,15 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="rename-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
SpEL expression representing the path for the
|
||||
new filename when using the 'mv' command. Defaults
|
||||
to "headers.['file_renameTo']".
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
|
||||
@@ -43,6 +43,16 @@
|
||||
order="2"
|
||||
/>
|
||||
|
||||
<int-sftp:outbound-gateway id="gateway3"
|
||||
session-factory="sf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
command="mv"
|
||||
expression="payload"
|
||||
rename-expression="'foo'"
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-sftp:outbound-gateway id="advised"
|
||||
local-directory="local-test-dir"
|
||||
session-factory="sf"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -28,6 +28,8 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Option;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
@@ -54,6 +56,9 @@ public class SftpOutboundGatewayParserTests {
|
||||
@Autowired
|
||||
AbstractEndpoint gateway2;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway3;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint advised;
|
||||
|
||||
@@ -69,11 +74,11 @@ public class SftpOutboundGatewayParserTests {
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("ls", TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals(Command.LS, TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-1"));
|
||||
assertTrue(options.contains("-f"));
|
||||
assertTrue(options.contains(Option.NAME_ONLY));
|
||||
assertTrue(options.contains(Option.NOSORT));
|
||||
|
||||
Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class);
|
||||
assertEquals(Long.valueOf(777), sendTimeout);
|
||||
@@ -89,10 +94,20 @@ public class SftpOutboundGatewayParserTests {
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertEquals("get", TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals(Command.GET, TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-P"));
|
||||
assertTrue(options.contains(Option.PRESERVE_TIMESTAMP));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGatewayMv() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway3,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(Command.MV, TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -327,6 +327,7 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
<listitem>get (retrieve file)</listitem>
|
||||
<listitem>mget (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
<listitem>mv (move/rename file)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para><emphasis>ls</emphasis></para>
|
||||
@@ -397,8 +398,23 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
The remote directory is provided in the <classname>file_remoteDirectory</classname> header, and the filename is
|
||||
provided in the <classname>file_remoteFile</classname> header.
|
||||
</para>
|
||||
<para><emphasis>mv</emphasis></para>
|
||||
<para>
|
||||
In each case, the PATH that these commands act on is provided by the 'expression'
|
||||
The <emphasis>mv</emphasis> command has no options.
|
||||
</para>
|
||||
<para>
|
||||
The <emphasis>expression</emphasis> attribute defines the "from" path and the
|
||||
<emphasis>rename-expression</emphasis> attribute defines the "to" path. By default, the
|
||||
<emphasis>rename-expression</emphasis> is <code>headers['file_renameTo']</code>. This
|
||||
expression must not evaluate to null, or an empty <code>String</code>. If necessary,
|
||||
any remote directories needed will be created.
|
||||
The payload of the result message is <code>Boolean.TRUE</code>.
|
||||
The original remote directory is provided in the <code>file_remoteDirectory</code> header, and the filename is
|
||||
provided in the <code>file_remoteFile</code> header. The new path is in
|
||||
the <code>file_renameTo</code> header.
|
||||
</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
|
||||
retrieve all files, or 'somedirectory/*' etc.
|
||||
</para>
|
||||
|
||||
@@ -360,6 +360,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
<listitem>get (retrieve file)</listitem>
|
||||
<listitem>mget (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
<listitem>mv (move/rename file)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para><emphasis>ls</emphasis></para>
|
||||
@@ -430,8 +431,23 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
The remote directory is provided in the <classname>file_remoteDirectory</classname> header, and the filename is
|
||||
provided in the <classname>file_remoteFile</classname> header.
|
||||
</para>
|
||||
<para><emphasis>mv</emphasis></para>
|
||||
<para>
|
||||
In each case, the PATH that these commands act on is provided by the 'expression'
|
||||
The <emphasis>mv</emphasis> command has no options.
|
||||
</para>
|
||||
<para>
|
||||
The <emphasis>expression</emphasis> attribute defines the "from" path and the
|
||||
<emphasis>rename-expression</emphasis> attribute defines the "to" path. By default, the
|
||||
<emphasis>rename-expression</emphasis> is <code>headers['file_renameTo']</code>. This
|
||||
expression must not evaluate to null, or an empty <code>String</code>. If necessary,
|
||||
any remote directories needed will be created.
|
||||
The payload of the result message is <code>Boolean.TRUE</code>.
|
||||
The original remote directory is provided in the <code>file_remoteDirectory</code> header, and the filename is
|
||||
provided in the <code>file_remoteFile</code> header. The new path is in
|
||||
the <code>file_renameTo</code> header.
|
||||
</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
|
||||
retrieve all files, or 'somedirectory/*' etc.
|
||||
</para>
|
||||
|
||||
@@ -78,6 +78,13 @@
|
||||
URI-schemes supported by Spring Web Services. For more information see <xref linkend="outbound-uri"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-xFTP-gw">
|
||||
<title>(S)FTP(S) Gateways</title>
|
||||
<para>
|
||||
The gateways now support the <code>mv</code> command, enabling the renaming of remote
|
||||
files.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user