From c224d445aca8ef4f91d69d58de3c02a063260a10 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Fri, 5 Apr 2013 14:44:48 -0400 Subject: [PATCH] INT-2981 - Add Rename (mv) to (S)FTP Gateway * (S)FTP Change Commands, Options to Enums * INT-2981 Add Support For 'mv' To File/Remote/GW * INT-2981 (S)FTP Namespace Support and Docs * INT-2981 Create Remote Dirs if Needed * INT-2981 Add 'mv' Command to What's New * INT-2981 Polishing - PR Comments * Add javadocs to enums. * Polish schemas to use an enumerated type for available gateway commands --- .../integration/file/FileHeaders.java | 4 +- ...stractRemoteFileOutboundGatewayParser.java | 6 +- .../file/remote/RemoteFileUtils.java | 76 +++++ .../AbstractRemoteFileOutboundGateway.java | 312 +++++++++++++----- .../FileTransferringMessageHandler.java | 38 +-- .../config/spring-integration-file-3.0.xsd | 10 + .../RemoteFileOutboundGatewayTests.java | 109 +++++- .../ftp/config/spring-integration-ftp-3.0.xsd | 20 +- .../FtpOutboundGatewayParserTests-context.xml | 11 + .../config/FtpOutboundGatewayParserTests.java | 29 +- .../config/spring-integration-sftp-3.0.xsd | 20 +- ...SftpOutboundGatewayParserTests-context.xml | 10 + .../SftpOutboundGatewayParserTests.java | 27 +- src/reference/docbook/ftp.xml | 18 +- src/reference/docbook/sftp.xml | 18 +- src/reference/docbook/whats-new.xml | 7 + 16 files changed, 557 insertions(+), 158 deletions(-) create mode 100644 spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileUtils.java diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java index 3f4113501d..e47d9ff31e 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileHeaders.java @@ -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"; + } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java index d85af224a5..1094015306 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java @@ -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; } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileUtils.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileUtils.java new file mode 100644 index 0000000000..22f9c23e76 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileUtils.java @@ -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 The session type. + * @param path The directory path. + * @param session The session. + * @throws IOException + */ + public static void makeDirectories(String path, Session session, String remoteFileSeparator, Log logger) + throws IOException { + + if (!session.exists(path)){ + + int nextSeparatorIndex = path.lastIndexOf(remoteFileSeparator); + + if (nextSeparatorIndex > -1){ + List pathsToCreate = new LinkedList(); + 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); + } + } + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index 3fbb8bdbfa..6163954459 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -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 extends AbstractReply protected final SessionFactory 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 fileNameProcessor; - private final Set supportedCommands = new HashSet(Arrays.asList( - COMMAND_LS, COMMAND_GET, COMMAND_RM, COMMAND_MGET)); + private volatile ExpressionEvaluatingMessageProcessor renameProcessor = + new ExpressionEvaluatingMessageProcessor( + new SpelExpressionParser().parseExpression("headers." + FileHeaders.RENAME_TO)); - private final ExpressionEvaluatingMessageProcessor processor; - - protected volatile Set options = new HashSet(); + protected volatile Set