INT-3088 (S)FTP Outbound Gateway - PUT and MPUT
- Core support in file module - FTP Parser and Test https://jira.springsource.org/browse/INT-3088 INT-3088 Add SFTP Support for PUT, MPUT INT-3088 Polishing - PR Comments INT-3088 Docbook For PUT/MPUT
This commit is contained in:
committed by
Artem Bilan
parent
88de8117aa
commit
59d6f7dfc1
@@ -23,6 +23,8 @@ import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
|
||||
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,18 +44,20 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
|
||||
BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, false);
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName());
|
||||
|
||||
builder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
builder.addConstructorArgValue(templateDefinition);
|
||||
|
||||
builder.addConstructorArgValue(element.getAttribute("command"));
|
||||
builder.addConstructorArgValue(element.getAttribute(EXPRESSION_ATTRIBUTE));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "command-options", "options");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
|
||||
this.configureFilter(builder, element, parserContext);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
|
||||
this.configureFilter(builder, element, parserContext, "filter", "filename", "filter");
|
||||
this.configureFilter(builder, element, parserContext, "mput-filter", "mput", "mputFilter");
|
||||
|
||||
BeanDefinition localDirExpressionDef = IntegrationNamespaceUtils
|
||||
.createExpressionDefinitionFromValueOrExpression("local-directory", "local-directory-expression",
|
||||
@@ -74,10 +78,11 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
|
||||
String filter = element.getAttribute("filter");
|
||||
String fileNamePattern = element.getAttribute("filename-pattern");
|
||||
String fileNameRegex = element.getAttribute("filename-regex");
|
||||
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext,
|
||||
String filterAttribute, String patternPrefix, String propertyName) {
|
||||
String filter = element.getAttribute(filterAttribute);
|
||||
String fileNamePattern = element.getAttribute(patternPrefix + "-pattern");
|
||||
String fileNameRegex = element.getAttribute(patternPrefix + "-regex");
|
||||
boolean hasFilter = StringUtils.hasText(filter);
|
||||
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
|
||||
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
|
||||
@@ -85,23 +90,27 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
|
||||
count += hasFileNamePattern ? 1 : 0;
|
||||
count += hasFileNameRegex ? 1 : 0;
|
||||
if (count > 1) {
|
||||
parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
|
||||
"'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
|
||||
parserContext.getReaderContext().error("at most one of '" + patternPrefix + "-pattern', " +
|
||||
"'" + patternPrefix + "-regex', or '" + filterAttribute + "' is allowed on a remote file outbound gateway", element);
|
||||
}
|
||||
else if (hasFilter) {
|
||||
builder.addPropertyReference("filter", filter);
|
||||
builder.addPropertyReference(propertyName, filter);
|
||||
}
|
||||
else if (hasFileNamePattern) {
|
||||
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
this.getSimplePatternFileListFilterClassName());
|
||||
"filter".equals(filterAttribute) ?
|
||||
this.getSimplePatternFileListFilterClassName() :
|
||||
SimplePatternFileListFilter.class.getName());
|
||||
filterBuilder.addConstructorArgValue(fileNamePattern);
|
||||
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
|
||||
builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
|
||||
}
|
||||
else if (hasFileNameRegex) {
|
||||
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
this.getRegexPatternFileListFilterClassName());
|
||||
"filter".equals(filterAttribute) ?
|
||||
this.getRegexPatternFileListFilterClassName() :
|
||||
RegexPatternFileListFilter.class.getName());
|
||||
filterBuilder.addConstructorArgValue(fileNameRegex);
|
||||
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
|
||||
builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.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.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public final class FileParserUtils {
|
||||
|
||||
private FileParserUtils() {
|
||||
}
|
||||
|
||||
public static BeanDefinition parseRemoteFileTemplate(Element element, ParserContext parserContext,
|
||||
boolean atLeastOneRemoteDirectoryAttributeRequired) {
|
||||
BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(RemoteFileTemplate.class);
|
||||
|
||||
templateBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
// configure MessageHandler properties
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "use-temporary-file-name");
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "auto-create-directory");
|
||||
|
||||
BeanDefinition expressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("remote-directory",
|
||||
"remote-directory-expression", parserContext, element, atLeastOneRemoteDirectoryAttributeRequired);
|
||||
if (expressionDef != null) {
|
||||
templateBuilder.addPropertyValue("remoteDirectoryExpression", expressionDef);
|
||||
}
|
||||
expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("temporary-remote-directory",
|
||||
"temporary-remote-directory-expression", parserContext, element, false);
|
||||
if (expressionDef != null) {
|
||||
templateBuilder.addPropertyValue("temporaryRemoteDirectoryExpression", expressionDef);
|
||||
}
|
||||
|
||||
// configure remote FileNameGenerator
|
||||
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
|
||||
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
|
||||
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
|
||||
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
|
||||
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
|
||||
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
|
||||
parserContext.getReaderContext().error(
|
||||
"at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' "
|
||||
+ "is allowed on a remote file outbound adapter", element);
|
||||
}
|
||||
if (hasRemoteFileNameGenerator) {
|
||||
templateBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DefaultFileNameGenerator.class);
|
||||
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
|
||||
templateBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "charset");
|
||||
templateBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
|
||||
return templateBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,19 +18,12 @@ package org.springframework.integration.file.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -45,71 +38,10 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
|
||||
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class);
|
||||
|
||||
handlerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
// configure MessageHandler properties
|
||||
BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true);
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "use-temporary-file-name");
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory");
|
||||
|
||||
this.configureRemoteDirectories(element, handlerBuilder);
|
||||
|
||||
// configure remote FileNameGenerator
|
||||
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
|
||||
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
|
||||
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
|
||||
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
|
||||
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
|
||||
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
|
||||
throw new BeanDefinitionStoreException("at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' " +
|
||||
"is allowed on a remote file outbound adapter");
|
||||
}
|
||||
if (hasRemoteFileNameGenerator) {
|
||||
handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultFileNameGenerator.class);
|
||||
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
|
||||
handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset");
|
||||
handlerBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
|
||||
handlerBuilder.addConstructorArgValue(templateDefinition);
|
||||
return handlerBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private void configureRemoteDirectories(Element element, BeanDefinitionBuilder handlerBuilder){
|
||||
this.doConfigureRemoteDirectory(element, handlerBuilder, "remote-directory", "remote-directory-expression", "remoteDirectoryExpression", true);
|
||||
this.doConfigureRemoteDirectory(element, handlerBuilder, "temporary-remote-directory", "temporary-remote-directory-expression", "temporaryRemoteDirectoryExpression", false);
|
||||
}
|
||||
|
||||
private void doConfigureRemoteDirectory(Element element, BeanDefinitionBuilder handlerBuilder,
|
||||
String directoryAttribute, String directoryExpressionAttribute,
|
||||
String directoryExpressionPropertyName, boolean atLeastOneRequired){
|
||||
String remoteDirectory = element.getAttribute(directoryAttribute);
|
||||
String remoteDirectoryExpression = element.getAttribute(directoryExpressionAttribute);
|
||||
boolean hasRemoteDirectory = StringUtils.hasText(remoteDirectory);
|
||||
boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
|
||||
if (atLeastOneRequired){
|
||||
if (!(hasRemoteDirectory ^ hasRemoteDirectoryExpression)) {
|
||||
throw new BeanDefinitionStoreException("exactly one of '" + directoryAttribute + "' or '" + directoryExpressionAttribute + "' " +
|
||||
"is required on a remote file outbound adapter");
|
||||
}
|
||||
}
|
||||
|
||||
BeanDefinition remoteDirectoryExpressionDefinition = null;
|
||||
if (hasRemoteDirectory) {
|
||||
remoteDirectoryExpressionDefinition = new RootBeanDefinition(LiteralExpression.class);
|
||||
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectory);
|
||||
}
|
||||
else if (hasRemoteDirectoryExpression) {
|
||||
remoteDirectoryExpressionDefinition = new RootBeanDefinition(ExpressionFactoryBean.class);
|
||||
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectoryExpression);
|
||||
}
|
||||
if (remoteDirectoryExpressionDefinition != null){
|
||||
handlerBuilder.addPropertyValue(directoryExpressionPropertyName, remoteDirectoryExpressionDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends Abst
|
||||
* Override this method if you wish to use something other than the
|
||||
* modified timestamp to determine equality.
|
||||
* @param file The file.
|
||||
* @param value The current value for the key in the store
|
||||
* @return
|
||||
* @param value The current value for the key in the store.
|
||||
* @return true if equal.
|
||||
*/
|
||||
protected boolean isEqual(F file, String value) {
|
||||
return Long.valueOf(value).longValue() == this.modified(file);
|
||||
|
||||
@@ -29,11 +29,24 @@ public interface RemoteFileOperations<F> {
|
||||
/**
|
||||
* Send a file to a remote server, based on information in a message.
|
||||
*
|
||||
* @param message The message
|
||||
* @param message The message.
|
||||
* @return The remote path, or null if no local file was found.
|
||||
* @throws Exception
|
||||
*/
|
||||
void send(Message<?> message);
|
||||
String send(Message<?> message);
|
||||
|
||||
/**
|
||||
* Send a file to a remote server, based on information in a message.
|
||||
* The subDirectory is appended to the remote directory evaluated from
|
||||
* the message.
|
||||
*
|
||||
* @param message The message.
|
||||
* @param subDirectory The sub directory.
|
||||
* @return The remote path, or null if no local file was found.
|
||||
* @throws Exception
|
||||
*/
|
||||
|
||||
String send(Message<?> message, String subDirectory);
|
||||
/**
|
||||
* Retrieve a remote file as an InputStream, based on information in a message.
|
||||
*
|
||||
|
||||
@@ -99,6 +99,10 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
|
||||
this.remoteFileSeparator = remoteFileSeparator;
|
||||
}
|
||||
|
||||
public final String getRemoteFileSeparator() {
|
||||
return remoteFileSeparator;
|
||||
}
|
||||
|
||||
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
|
||||
Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
|
||||
this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class);
|
||||
@@ -173,18 +177,32 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(final Message<?> message) {
|
||||
public String send(final Message<?> message) {
|
||||
return this.send(message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String send(final Message<?> message, final String subDirectory) {
|
||||
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
|
||||
final StreamHolder inputStreamHolder = this.payloadToInputStream(message);
|
||||
if (inputStreamHolder != null) {
|
||||
this.execute(new SessionCallbackWithoutResult<F>() {
|
||||
return this.execute(new SessionCallback<F, String>() {
|
||||
|
||||
@Override
|
||||
public void doInSessionWithoutResult(Session<F> session) throws IOException {
|
||||
public String doInSession(Session<F> session) throws IOException {
|
||||
String fileName = inputStreamHolder.getName();
|
||||
try {
|
||||
String remoteDirectory = RemoteFileTemplate.this.directoryExpressionProcessor
|
||||
.processMessage(message);
|
||||
remoteDirectory = RemoteFileTemplate.this.normalizeDirectoryPath(remoteDirectory);
|
||||
if (StringUtils.hasText(subDirectory)) {
|
||||
if (subDirectory.startsWith(RemoteFileTemplate.this.remoteFileSeparator)) {
|
||||
remoteDirectory += subDirectory.substring(1);
|
||||
}
|
||||
else {
|
||||
remoteDirectory += RemoteFileTemplate.this.normalizeDirectoryPath(subDirectory);
|
||||
}
|
||||
}
|
||||
String temporaryRemoteDirectory = remoteDirectory;
|
||||
if (RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor != null) {
|
||||
temporaryRemoteDirectory = RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor
|
||||
@@ -193,6 +211,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
|
||||
fileName = RemoteFileTemplate.this.fileNameGenerator.generateFileName(message);
|
||||
RemoteFileTemplate.this.sendFileToRemoteDirectory(inputStreamHolder.getStream(),
|
||||
temporaryRemoteDirectory, remoteDirectory, fileName, session);
|
||||
return remoteDirectory + fileName;
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new MessageDeliveryException(message, "File [" + inputStreamHolder.getName()
|
||||
@@ -215,6 +234,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("File " + message.getPayload() + " does not exist");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.io.IOException;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
|
||||
/**
|
||||
* Callback invoked by {@code RemoteFileOperations.execute()) - allows multiple operations
|
||||
* Callback invoked by {@code RemoteFileOperations.execute()} - allows multiple operations
|
||||
* on a session.
|
||||
*
|
||||
* @author Gary Russell
|
||||
|
||||
@@ -92,7 +92,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
/**
|
||||
* Move (rename) a remote file.
|
||||
*/
|
||||
MV("mv");
|
||||
MV("mv"),
|
||||
|
||||
/**
|
||||
* Put a local file to the remote system.
|
||||
*/
|
||||
PUT("put"),
|
||||
|
||||
/**
|
||||
* Put multiple local files to the remote system.
|
||||
*/
|
||||
MPUT("mput");
|
||||
|
||||
private String command;
|
||||
|
||||
@@ -188,19 +198,20 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
protected volatile Set<Option> options = new HashSet<Option>();
|
||||
|
||||
private volatile String remoteFileSeparator = "/";
|
||||
|
||||
private volatile Expression localDirectoryExpression;
|
||||
|
||||
private volatile boolean autoCreateLocalDirectory = true;
|
||||
|
||||
private volatile String temporaryFileSuffix = ".writing";
|
||||
|
||||
/**
|
||||
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
|
||||
* A {@link FileListFilter} that runs against the <em>remote</em> file system view.
|
||||
*/
|
||||
private volatile FileListFilter<F> filter;
|
||||
|
||||
/**
|
||||
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
|
||||
* using MPUT.
|
||||
*/
|
||||
private volatile FileListFilter<File> mputFilter;
|
||||
|
||||
private volatile Expression localFilenameGeneratorExpression;
|
||||
|
||||
@@ -222,6 +233,23 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, String command,
|
||||
String expression) {
|
||||
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
|
||||
this.remoteFileTemplate = remoteFileTemplate;
|
||||
this.command = Command.toCommand(command);
|
||||
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, Command command,
|
||||
String expression) {
|
||||
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
|
||||
this.remoteFileTemplate = remoteFileTemplate;
|
||||
this.command = command;
|
||||
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options the options to set
|
||||
@@ -240,7 +268,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
* @param remoteFileSeparator the remoteFileSeparator to set
|
||||
*/
|
||||
public void setRemoteFileSeparator(String remoteFileSeparator) {
|
||||
this.remoteFileSeparator = remoteFileSeparator;
|
||||
this.remoteFileTemplate.setRemoteFileSeparator(remoteFileSeparator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +295,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
* @param temporaryFileSuffix the temporaryFileSuffix to set
|
||||
*/
|
||||
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
|
||||
this.temporaryFileSuffix = temporaryFileSuffix;
|
||||
this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,6 +305,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filter the filter to set
|
||||
*/
|
||||
public void setMputFilter(FileListFilter<File> filter) {
|
||||
this.mputFilter = filter;
|
||||
}
|
||||
|
||||
public void setRenameExpression(String expression) {
|
||||
Assert.notNull(expression, "'expression' cannot be null");
|
||||
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
@@ -350,6 +385,10 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
return doRm(requestMessage);
|
||||
case MV:
|
||||
return doMv(requestMessage);
|
||||
case PUT:
|
||||
return doPut(requestMessage);
|
||||
case MPUT:
|
||||
return doMput(requestMessage);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -357,8 +396,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
|
||||
private Object doLs(Message<?> requestMessage) {
|
||||
String dir = this.fileNameProcessor.processMessage(requestMessage);
|
||||
if (!dir.endsWith(this.remoteFileSeparator)) {
|
||||
dir += this.remoteFileSeparator;
|
||||
if (!dir.endsWith(this.remoteFileTemplate.getRemoteFileSeparator())) {
|
||||
dir += this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
}
|
||||
final String fullDir = dir;
|
||||
List<?> payload = this.remoteFileTemplate.execute(new SessionCallback<F, List<?>>() {
|
||||
@@ -435,6 +474,66 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
.build();
|
||||
}
|
||||
|
||||
private String doPut(Message<?> requestMessage) {
|
||||
return this.doPut(requestMessage, null);
|
||||
}
|
||||
|
||||
private String doPut(Message<?> requestMessage, String subDirectory) {
|
||||
String path = this.remoteFileTemplate.send(requestMessage, subDirectory);
|
||||
if (path == null) {
|
||||
throw new MessagingException(requestMessage, "No local file found for " + requestMessage);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private Object doMput(Message<?> requestMessage) {
|
||||
File file = null;
|
||||
if (requestMessage.getPayload() instanceof File) {
|
||||
file = (File) requestMessage.getPayload();
|
||||
}
|
||||
else if (requestMessage.getPayload() instanceof String) {
|
||||
file = new File((String) requestMessage.getPayload());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Only File or String payloads allowed for 'mput'");
|
||||
}
|
||||
if (!file.isDirectory()) {
|
||||
return this.doPut(requestMessage);
|
||||
}
|
||||
else {
|
||||
List<String> replies = this.putLocalDirectory(requestMessage, file, null);
|
||||
return replies;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> putLocalDirectory(Message<?> requestMessage, File file, String subDirectory) {
|
||||
File[] files = file.listFiles();
|
||||
List<File> filteredFiles = this.filterMputFiles(files);
|
||||
List<String> replies = new ArrayList<String>();
|
||||
for (File filteredFile : filteredFiles) {
|
||||
if (!filteredFile.isDirectory()) {
|
||||
String path = this.doPut(MessageBuilder.withPayload(filteredFile)
|
||||
.copyHeaders(requestMessage.getHeaders())
|
||||
.build(), subDirectory);
|
||||
if (path == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("File " + filteredFile.getAbsolutePath() + " removed before transfer; ignoring");
|
||||
}
|
||||
}
|
||||
else {
|
||||
replies.add(path);
|
||||
}
|
||||
}
|
||||
else if (this.options.contains(Option.RECURSIVE)){
|
||||
String newSubDirectory = (StringUtils.hasText(subDirectory) ?
|
||||
subDirectory + this.remoteFileTemplate.getRemoteFileSeparator() : "")
|
||||
+ filteredFile.getName();
|
||||
replies.addAll(this.putLocalDirectory(requestMessage, filteredFile, newSubDirectory));
|
||||
}
|
||||
}
|
||||
return replies;
|
||||
}
|
||||
|
||||
protected List<?> ls(Session<F> session, String dir) throws IOException {
|
||||
List<F> lsFiles = listFilesInRemoteDir(session, dir, "");
|
||||
if (!this.options.contains(Option.LINKS)) {
|
||||
@@ -483,7 +582,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
}
|
||||
if (recursion && this.isDirectory(file) && !(".".equals(fileName)) && !("..".equals(fileName))) {
|
||||
lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName + this.remoteFileSeparator));
|
||||
lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName
|
||||
+ this.remoteFileTemplate.getRemoteFileSeparator()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,6 +595,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
|
||||
}
|
||||
|
||||
protected final List<File> filterMputFiles(File[] files) {
|
||||
if (files == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (this.mputFilter != null) ? this.mputFilter.filterFiles(files) : Arrays.asList(files);
|
||||
}
|
||||
|
||||
protected void purgeLinks(List<F> lsFiles) {
|
||||
Iterator<F> iterator = lsFiles.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
@@ -536,7 +643,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
File localFile = new File(this.generateLocalDirectory(message, remoteDir), this.generateLocalFileName(message, remoteFilename));
|
||||
if (!localFile.exists()) {
|
||||
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
|
||||
String tempFileName = localFile.getAbsolutePath() + this.remoteFileTemplate.getTemporaryFileSuffix();
|
||||
File tempFile = new File(tempFileName);
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
|
||||
try {
|
||||
@@ -599,12 +706,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
+ " with pattern " + remoteFilename);
|
||||
}
|
||||
List<File> files = new ArrayList<File>();
|
||||
String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
for (String fileName : fileNames) {
|
||||
File file;
|
||||
if (fileName.contains(this.remoteFileSeparator) &&
|
||||
if (fileName.contains(remoteFileSeparator) &&
|
||||
fileName.startsWith(remoteDirectory)) { // the server returned the full path
|
||||
file = this.get(message, session, remoteDirectory, fileName,
|
||||
fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false);
|
||||
fileName.substring(fileName.lastIndexOf(remoteFileSeparator)), false);
|
||||
}
|
||||
else {
|
||||
file = this.get(message, session, remoteDirectory,
|
||||
@@ -642,21 +750,22 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
private String getRemoteDirectory(String remoteFilePath, String remoteFilename) {
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = this.remoteFileSeparator;
|
||||
remoteDir = this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
}
|
||||
return remoteDir;
|
||||
}
|
||||
|
||||
private String generateFullPath(String remoteDirectory, String remoteFilename) {
|
||||
String path;
|
||||
if (this.remoteFileSeparator.equals(remoteDirectory)) {
|
||||
String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
if (remoteFileSeparator.equals(remoteDirectory)) {
|
||||
path = remoteFilename;
|
||||
}
|
||||
else if (remoteDirectory.endsWith(this.remoteFileSeparator)) {
|
||||
else if (remoteDirectory.endsWith(remoteFileSeparator)) {
|
||||
path = remoteDirectory + remoteFilename;
|
||||
}
|
||||
else {
|
||||
path = remoteDirectory + this.remoteFileSeparator + remoteFilename;
|
||||
path = remoteDirectory + remoteFileSeparator + remoteFilename;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@@ -666,7 +775,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
*/
|
||||
protected String getRemoteFilename(String remoteFilePath) {
|
||||
String remoteFileName;
|
||||
int index = remoteFilePath.lastIndexOf(this.remoteFileSeparator);
|
||||
int index = remoteFilePath.lastIndexOf(this.remoteFileTemplate.getRemoteFileSeparator());
|
||||
if (index < 0) {
|
||||
remoteFileName = remoteFilePath;
|
||||
}
|
||||
@@ -709,4 +818,5 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
abstract protected List<AbstractFileInfo<F>> asFileInfoList(Collection<F> files);
|
||||
|
||||
abstract protected F enhanceNameWithSubDirectory(F file, String directory);
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
|
||||
}
|
||||
|
||||
public FileTransferringMessageHandler(RemoteFileTemplate<F> remoteFileTemplate) {
|
||||
Assert.notNull(remoteFileTemplate, "remoteFileTemplate must not be null");
|
||||
this.remoteFileTemplate = remoteFileTemplate;
|
||||
}
|
||||
|
||||
|
||||
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
|
||||
this.remoteFileTemplate.setAutoCreateDirectory(autoCreateDirectory);
|
||||
|
||||
@@ -645,7 +645,79 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
<xsd:enumeration value="rm"/>
|
||||
<xsd:enumeration value="mget"/>
|
||||
<xsd:enumeration value="mv"/>
|
||||
<xsd:enumeration value="put"/>
|
||||
<xsd:enumeration value="mput"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:attributeGroup name="remoteOutboundAttributeGroup">
|
||||
<xsd:attribute name="remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the directory
|
||||
path where the files will be transferred to
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the temporary directory
|
||||
path where files will be transferred to before they are moved to the remote-directory
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/temp/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-directory" type="xsd:string"
|
||||
default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether to automatically create the
|
||||
remote target directory if
|
||||
it doesn't exist.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.FileNameGenerator] bean.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.file.FileNameGenerator" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide SpEL expression which
|
||||
will compute file name of
|
||||
the remote file (e.g., assuming payload
|
||||
is java.io.File
|
||||
"payload.getName() + '.transfered'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to suppress using a temporary file name while writing the file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
@@ -15,10 +15,15 @@
|
||||
*/
|
||||
package org.springframework.integration.file.remote.gateway;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -36,16 +41,21 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
@@ -61,6 +71,9 @@ public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
private final String tmpDir = System.getProperty("java.io.tmpdir");
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testBad() throws Exception {
|
||||
@@ -987,6 +1000,120 @@ public class RemoteFileOutboundGatewayTests {
|
||||
out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.afterPropertiesSet();
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "put", null);
|
||||
FileTransferringMessageHandler<TestLsEntry> handler = new FileTransferringMessageHandler<TestLsEntry>(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.afterPropertiesSet();
|
||||
gw.afterPropertiesSet();
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
final AtomicReference<String> written = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
written.set((String) invocation.getArguments()[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).write(any(InputStream.class), anyString());
|
||||
Message<String> requestMessage = MessageBuilder.withPayload("hello")
|
||||
.setHeader(FileHeaders.FILENAME, "bar.txt")
|
||||
.build();
|
||||
String path = (String) gw.handleRequestMessage(requestMessage);
|
||||
assertEquals("foo/bar.txt", path);
|
||||
verify(session).rename("foo/bar.txt.writing", "foo/bar.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMput() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.afterPropertiesSet();
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
|
||||
gw.afterPropertiesSet();
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
final AtomicReference<String> written = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
written.set((String) invocation.getArguments()[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).write(any(InputStream.class), anyString());
|
||||
File file1 = tempFolder.newFile("baz.txt");
|
||||
File file2 = tempFolder.newFile("qux.txt");
|
||||
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
|
||||
assertEquals(2, out.size());
|
||||
assertThat(out.get(0),
|
||||
not(equalTo(out.get(1))));
|
||||
assertThat(out.get(0), anyOf(
|
||||
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
|
||||
assertThat(out.get(1), anyOf(
|
||||
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMputRecursive() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.afterPropertiesSet();
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
|
||||
gw.setOptions("-R");
|
||||
gw.afterPropertiesSet();
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
final AtomicReference<String> written = new AtomicReference<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
written.set((String) invocation.getArguments()[1]);
|
||||
return null;
|
||||
}
|
||||
}).when(session).write(any(InputStream.class), anyString());
|
||||
tempFolder.newFile("baz.txt");
|
||||
tempFolder.newFile("qux.txt");
|
||||
File dir1 = tempFolder.newFolder();
|
||||
File file3 = File.createTempFile("foo", ".txt", dir1);
|
||||
|
||||
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
|
||||
assertEquals(3, out.size());
|
||||
assertThat(out.get(0),
|
||||
not(equalTo(out.get(1))));
|
||||
assertThat(out.get(0), anyOf(
|
||||
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
|
||||
assertThat(out.get(1), anyOf(
|
||||
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
|
||||
assertThat(out.get(2), anyOf(
|
||||
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<TestLsEntry> {
|
||||
@@ -998,6 +1125,13 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
|
||||
this.setBeanFactory(mock(BeanFactory.class));
|
||||
}
|
||||
|
||||
public TestRemoteFileOutboundGateway(RemoteFileTemplate<TestLsEntry> remoteFileTemplate, String command,
|
||||
String expression) {
|
||||
super(remoteFileTemplate, command, expression);
|
||||
this.setBeanFactory(mock(BeanFactory.class));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(TestLsEntry file) {
|
||||
return file.isDirectory();
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.ftp.session.FtpFileInfo;
|
||||
@@ -40,6 +41,11 @@ public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFil
|
||||
super(sessionFactory, command, expression);
|
||||
}
|
||||
|
||||
private FtpOutboundGateway(RemoteFileTemplate<FTPFile> remoteFileTemplate, String command, String expression) {
|
||||
super(remoteFileTemplate, command, expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(FTPFile file) {
|
||||
return file.isDirectory();
|
||||
|
||||
@@ -26,73 +26,6 @@
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the directory
|
||||
path where the files will be transferred to
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the temporary directory
|
||||
path where files will be transferred to before they are moved to the remote-directory
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/temp/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-directory" type="xsd:string"
|
||||
default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether to automatically create the
|
||||
remote target directory if
|
||||
it doesn't exist.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.FileNameGenerator] bean.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.file.FileNameGenerator" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide SpEL expression which
|
||||
will compute file name of
|
||||
the remote file (e.g., assuming payload
|
||||
is java.io.File
|
||||
"payload.getName() + '.transfered'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to suppress using a temporary file name while writing the file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -113,6 +46,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -268,7 +202,7 @@
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="base-adapter-type">
|
||||
<xsd:extension base="base-ftp-adapter-type">
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
@@ -379,7 +313,9 @@
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter]
|
||||
bean.
|
||||
bean. This filter acts against the remote server view when using the 'ls'
|
||||
or 'mget' commands.
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -387,10 +323,11 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to
|
||||
determine the file names retrieved by the ls command
|
||||
determine the file names retrieved by the 'ls' and 'mget' commands
|
||||
and is based
|
||||
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
|
||||
etc.)
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -398,9 +335,49 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to
|
||||
determine the file names retrieved by the ls command.
|
||||
determine the file names retrieved by the 'ls' and 'mget' commands.
|
||||
(e.g.,
|
||||
"f[o]+\.txt" etc.)
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-filter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.file.filters.FileListFilter" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter]
|
||||
bean. This filter acts on the local file system when using the 'mput' command.
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-pattern" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to
|
||||
determine the file names sent by the 'mput' command
|
||||
and is based
|
||||
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
|
||||
etc.)
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-regex" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to
|
||||
determine the file names sent by the 'mput' command
|
||||
(e.g.,
|
||||
"f[o]+\.txt" etc.)
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -486,6 +463,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -101,7 +101,7 @@ public class TesFtpServer {
|
||||
fos.close();
|
||||
|
||||
targetFtpDirectory = new File(ftpRootFolder, "ftpTarget");
|
||||
targetFtpDirectory.mkdirs();
|
||||
targetFtpDirectory.mkdir();
|
||||
}
|
||||
};
|
||||
this.localFolder = new TemporaryFolder() {
|
||||
@@ -123,7 +123,7 @@ public class TesFtpServer {
|
||||
file.createNewFile();
|
||||
|
||||
targetLocalDirectory = new File(rootFolder, "localTarget");
|
||||
targetLocalDirectory.mkdirs();
|
||||
targetLocalDirectory.mkdir();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -183,14 +183,16 @@ public class TesFtpServer {
|
||||
}
|
||||
|
||||
|
||||
public static void recursiveDelete(File file) {
|
||||
public void recursiveDelete(File file) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File each : files) {
|
||||
recursiveDelete(each);
|
||||
}
|
||||
}
|
||||
file.delete();
|
||||
if (!(file.equals(this.targetFtpDirectory) || file.equals(this.targetLocalDirectory))) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
command-options="-1 -f"
|
||||
expression="payload"
|
||||
order="1"
|
||||
mput-regex=".*"
|
||||
/>
|
||||
|
||||
<bean id="fooString" class="java.lang.String">
|
||||
@@ -49,6 +50,7 @@
|
||||
order="2"
|
||||
requires-reply="false"
|
||||
local-filename-generator-expression="#remoteFileName.toUpperCase() + '.a' + @fooString"
|
||||
mput-pattern="*"
|
||||
>
|
||||
<int-ftp:request-handler-advice-chain>
|
||||
<bean class="org.springframework.integration.ftp.config.FtpOutboundGatewayParserTests$FooAdvice" />
|
||||
@@ -66,6 +68,26 @@
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-ftp:outbound-gateway id="gateway4"
|
||||
session-factory="csf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
command="mput"
|
||||
expression="payload"
|
||||
remote-directory="/foo"
|
||||
remote-file-separator="X"
|
||||
auto-create-directory="true"
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
temporary-remote-directory="/bar"
|
||||
rename-expression="'foo'"
|
||||
order="1"
|
||||
mput-regex=".*"
|
||||
/>
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
|
||||
<int-ftp:outbound-gateway id="withBeanExpression"
|
||||
local-directory="local-test-dir"
|
||||
session-factory="sf"
|
||||
|
||||
@@ -18,18 +18,25 @@ package org.springframework.integration.ftp.config;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
|
||||
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
|
||||
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;
|
||||
@@ -64,16 +71,22 @@ public class FtpOutboundGatewayParserTests {
|
||||
@Autowired
|
||||
AbstractEndpoint gateway3;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway4;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint withBeanExpression;
|
||||
|
||||
@Autowired
|
||||
FileNameGenerator generator;
|
||||
|
||||
private static volatile int adviceCalled;
|
||||
|
||||
@Test
|
||||
public void testGateway1() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
|
||||
@@ -89,13 +102,14 @@ public class FtpOutboundGatewayParserTests {
|
||||
Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class);
|
||||
assertEquals(Long.valueOf(777), sendTimeout);
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGateway2() throws Exception {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway2,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory") instanceof CachingSessionFactory);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
@@ -123,6 +137,7 @@ public class FtpOutboundGatewayParserTests {
|
||||
}
|
||||
});
|
||||
assertEquals("FOO.afoo", genMethod.get().invoke(gateway, new GenericMessage<String>(""), "foo"));
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(SimplePatternFileListFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -135,6 +150,24 @@ public class FtpOutboundGatewayParserTests {
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGatewayMPut() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway4,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(Command.MPUT, TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
|
||||
assertSame(generator, TestUtils.getPropertyValue(gateway, "remoteFileTemplate.fileNameGenerator"));
|
||||
assertEquals("/foo",
|
||||
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.directoryExpressionProcessor.expression", Expression.class)
|
||||
.getExpressionString());
|
||||
assertEquals("/bar",
|
||||
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.temporaryDirectoryExpressionProcessor.expression", Expression.class)
|
||||
.getExpressionString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBeanExpression() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(withBeanExpression,
|
||||
|
||||
@@ -68,4 +68,40 @@
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPut"/>
|
||||
|
||||
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPut"
|
||||
command="mput"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="ftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPutRecursive"/>
|
||||
|
||||
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPutRecursive"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="ftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPutRecursiveFiltered"/>
|
||||
|
||||
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPutRecursiveFiltered"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
mput-regex="(.*1.txt|sub.*)"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="ftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.integration.ftp.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -85,10 +88,19 @@ public class FtpServerOutboundTests {
|
||||
@Autowired
|
||||
private DirectChannel inboundMGetRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPut;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPutRecursive;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPutRecursiveFiltered;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
TesFtpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
|
||||
TesFtpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
|
||||
this.ftpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
|
||||
this.ftpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -231,5 +243,63 @@ public class FtpServerOutboundTests {
|
||||
assertEquals("source2", new String(baos2.toByteArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3088MPutNotRecursive() {
|
||||
this.inboundMPut.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3088MPutRecursive() {
|
||||
this.inboundMPutRecursive.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(3, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
|
||||
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
|
||||
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(2),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
|
||||
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3088MPutRecursiveFiltered() {
|
||||
this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
|
||||
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
|
||||
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
@@ -36,15 +37,15 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
*/
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEntry> {
|
||||
|
||||
/**
|
||||
* @param sessionFactory
|
||||
* @param command
|
||||
* @param expression
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory, String command, String expression) {
|
||||
super(sessionFactory, command, expression);
|
||||
}
|
||||
|
||||
private SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate, String command, String expression) {
|
||||
super(remoteFileTemplate, command, expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
|
||||
@@ -26,74 +26,6 @@
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the directory
|
||||
path
|
||||
where files will be transferred to
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-remote-directory-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide a SpEL expression which
|
||||
will compute the temporary directory
|
||||
path where files will be transferred to before they are moved to the remote-directory
|
||||
(e.g., "headers.['remote_dir'] +
|
||||
'/temp/myTransfers'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-directory" type="xsd:string"
|
||||
default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether to automatically create the
|
||||
remote target directory if
|
||||
it doesn't exist.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.FileNameGenerator] bean.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.file.FileNameGenerator" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-filename-generator-expression"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide SpEL expression which
|
||||
will compute file name of
|
||||
the remote file (e.g., assuming payload
|
||||
is java.io.File
|
||||
"payload.getName() + '.transfered'");
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to suppress using a temporary file name while writing the file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -114,6 +46,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -269,7 +202,7 @@
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="base-adapter-type">
|
||||
<xsd:extension base="base-sftp-adapter-type">
|
||||
<xsd:all>
|
||||
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
@@ -379,7 +312,9 @@
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter]
|
||||
bean.
|
||||
bean. This filter acts against the remote server view when using the 'ls'
|
||||
or 'mget' commands.
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -387,9 +322,11 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to
|
||||
determine the file names retrieved by the ls command
|
||||
determine the file names retrieved by the 'ls' and 'mget' commands
|
||||
and is based
|
||||
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt" etc.)
|
||||
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
|
||||
etc.)
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -397,9 +334,49 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to
|
||||
determine the file names retrieved by the ls command.
|
||||
determine the file names retrieved by the 'ls' and 'mget' commands.
|
||||
(e.g.,
|
||||
"f[o]+\.txt" etc.)
|
||||
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-filter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.file.filters.FileListFilter" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter]
|
||||
bean. This filter acts on the local file system when using the 'mput' command.
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-pattern" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to
|
||||
determine the file names sent by the 'mput' command
|
||||
and is based
|
||||
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
|
||||
etc.)
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mput-regex" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to
|
||||
determine the file names sent by the 'mput' command
|
||||
(e.g.,
|
||||
"f[o]+\.txt" etc.)
|
||||
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
@@ -483,6 +460,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.SimpleSftpSessionFactory">
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="knownHosts" value="local, foo.com, bar.foo"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
|
||||
@@ -21,11 +21,14 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
@@ -131,9 +134,15 @@ public class OutboundChannelAdapterParserTests {
|
||||
assertEquals(1, adviceCalled);
|
||||
}
|
||||
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
@Test
|
||||
public void testFailWithRemoteDirAndExpression(){
|
||||
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass());
|
||||
try {
|
||||
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass());
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertThat(e.getMessage(), Matchers.containsString("Only one of 'remote-directory'"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
command-options="-1 -f"
|
||||
expression="payload"
|
||||
order="1"
|
||||
mput-regex=".*"
|
||||
/>
|
||||
|
||||
<bean id="fooString" class="java.lang.String">
|
||||
@@ -49,6 +50,7 @@
|
||||
order="2"
|
||||
requires-reply="false"
|
||||
local-filename-generator-expression="#remoteFileName.toUpperCase() + '.a' + @fooString"
|
||||
mput-pattern="*"
|
||||
/>
|
||||
|
||||
<int-sftp:outbound-gateway id="gateway3"
|
||||
@@ -61,6 +63,30 @@
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-sftp:outbound-gateway id="gateway4"
|
||||
session-factory="csf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
command="mput"
|
||||
expression="payload"
|
||||
remote-directory="/foo"
|
||||
remote-file-separator="X"
|
||||
auto-create-directory="true"
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
temporary-remote-directory="/bar"
|
||||
rename-expression="'foo'"
|
||||
order="1"
|
||||
mput-filter="mputFilter"
|
||||
/>
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
|
||||
<bean id="mputFilter" class="org.springframework.integration.file.filters.RegexPatternFileListFilter">
|
||||
<constructor-arg value="(.*1.txt|sub*)"/>
|
||||
</bean>
|
||||
|
||||
<int-sftp:outbound-gateway id="advised"
|
||||
local-directory="local-test-dir"
|
||||
session-factory="sf"
|
||||
|
||||
@@ -18,18 +18,25 @@ package org.springframework.integration.sftp.config;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
|
||||
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
|
||||
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;
|
||||
@@ -62,16 +69,22 @@ public class SftpOutboundGatewayParserTests {
|
||||
@Autowired
|
||||
AbstractEndpoint gateway3;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway4;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint advised;
|
||||
|
||||
@Autowired
|
||||
FileNameGenerator generator;
|
||||
|
||||
private static volatile int adviceCalled;
|
||||
|
||||
@Test
|
||||
public void testGateway1() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
|
||||
@@ -86,13 +99,14 @@ public class SftpOutboundGatewayParserTests {
|
||||
|
||||
Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class);
|
||||
assertEquals(Long.valueOf(777), sendTimeout);
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGateway2() throws Exception {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway2,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory") instanceof CachingSessionFactory);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
@@ -118,6 +132,7 @@ public class SftpOutboundGatewayParserTests {
|
||||
}
|
||||
});
|
||||
assertEquals("FOO.afoo", genMethod.get().invoke(gateway, new GenericMessage<String>(""), "foo"));
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(SimplePatternFileListFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,6 +145,24 @@ public class SftpOutboundGatewayParserTests {
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGatewayMPut() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway4,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(Command.MPUT, TestUtils.getPropertyValue(gateway, "command"));
|
||||
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
|
||||
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
|
||||
assertSame(generator, TestUtils.getPropertyValue(gateway, "remoteFileTemplate.fileNameGenerator"));
|
||||
assertEquals("/foo",
|
||||
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.directoryExpressionProcessor.expression", Expression.class)
|
||||
.getExpressionString());
|
||||
assertEquals("/bar",
|
||||
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.temporaryDirectoryExpressionProcessor.expression", Expression.class)
|
||||
.getExpressionString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advised() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(advised,
|
||||
|
||||
@@ -64,6 +64,42 @@
|
||||
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPut"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPut"
|
||||
command="mput"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="sftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPutRecursive"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPutRecursive"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="sftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<int:channel id="inboundMPutRecursiveFiltered"/>
|
||||
|
||||
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
|
||||
request-channel="inboundMPutRecursiveFiltered"
|
||||
command="mput"
|
||||
command-options="-R"
|
||||
mput-regex="(.*1.txt|sub.*)"
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
remote-directory="sftpTarget"
|
||||
reply-channel="output"/>
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory" />
|
||||
</bean>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
@@ -51,6 +54,8 @@ import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
import org.springframework.test.annotation.ProfileValueUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
@@ -98,6 +103,15 @@ public class SftpServerOutboundTests {
|
||||
@Autowired
|
||||
private DirectChannel inboundMGetRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPut;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPutRecursive;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel inboundMPutRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory<SftpFileInfo> sessionFactory;
|
||||
|
||||
@@ -109,7 +123,9 @@ public class SftpServerOutboundTests {
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void setUpMocksIfNeeded() throws IOException {
|
||||
if (sessionFactory.toString().startsWith("Mock for")) {
|
||||
String profile = ProfileValueUtils.retrieveProfileValueSource(this.getClass()).get("spring.profiles.active");
|
||||
boolean usingMocks = profile == null || ! profile.startsWith("realSSH");
|
||||
if (usingMocks) {
|
||||
Session session = mock(Session.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
LsEntry entry1 = mock(LsEntry.class);
|
||||
@@ -276,82 +292,151 @@ public class SftpServerOutboundTests {
|
||||
* Only runs with a real server (see class javadocs).
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3100RawGET() throws Exception {
|
||||
if (!sessionFactory.toString().startsWith("Mock for")) {
|
||||
Session<?> session = this.sessionFactory.getSession();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource1.txt"), baos);
|
||||
assertTrue(session.finalizeRaw());
|
||||
assertEquals("source1", new String(baos.toByteArray()));
|
||||
Session<?> session = this.sessionFactory.getSession();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource1.txt"), baos);
|
||||
assertTrue(session.finalizeRaw());
|
||||
assertEquals("source1", new String(baos.toByteArray()));
|
||||
|
||||
baos = new ByteArrayOutputStream();
|
||||
FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource2.txt"), baos);
|
||||
assertTrue(session.finalizeRaw());
|
||||
assertEquals("source2", new String(baos.toByteArray()));
|
||||
baos = new ByteArrayOutputStream();
|
||||
FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource2.txt"), baos);
|
||||
assertTrue(session.finalizeRaw());
|
||||
assertEquals("source2", new String(baos.toByteArray()));
|
||||
|
||||
session.close();
|
||||
}
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSHSharedSession")
|
||||
public void testInt3047ConcurrentSharedSession() throws Exception {
|
||||
if ("realSSHSharedSession".equals(System.getProperty("spring.profiles.active"))) {
|
||||
final Session<?> session1 = this.sessionFactory.getSession();
|
||||
final Session<?> session2 = this.sessionFactory.getSession();
|
||||
final PipedInputStream pipe1 = new PipedInputStream();
|
||||
PipedOutputStream out1 = new PipedOutputStream(pipe1);
|
||||
final PipedInputStream pipe2 = new PipedInputStream();
|
||||
PipedOutputStream out2 = new PipedOutputStream(pipe2);
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
final Session<?> session1 = this.sessionFactory.getSession();
|
||||
final Session<?> session2 = this.sessionFactory.getSession();
|
||||
final PipedInputStream pipe1 = new PipedInputStream();
|
||||
PipedOutputStream out1 = new PipedOutputStream(pipe1);
|
||||
final PipedInputStream pipe2 = new PipedInputStream();
|
||||
PipedOutputStream out2 = new PipedOutputStream(pipe2);
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session1.write(pipe1, "foo.txt");
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
latch1.countDown();
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session1.write(pipe1, "foo.txt");
|
||||
}
|
||||
});
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session2.write(pipe2, "bar.txt");
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
latch2.countDown();
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
latch1.countDown();
|
||||
}
|
||||
});
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
|
||||
out1.write('a');
|
||||
out2.write('b');
|
||||
out1.write('c');
|
||||
out2.write('d');
|
||||
out1.write('e');
|
||||
out2.write('f');
|
||||
out1.close();
|
||||
out2.close();
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(latch2.await(10, TimeUnit.SECONDS));
|
||||
ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
|
||||
session1.read("foo.txt", bos1);
|
||||
session2.read("bar.txt", bos2);
|
||||
assertEquals("ace", new String(bos1.toByteArray()));
|
||||
assertEquals("bdf", new String(bos2.toByteArray()));
|
||||
session1.remove("foo.txt");
|
||||
session2.remove("bar.txt");
|
||||
session1.close();
|
||||
session2.close();
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session2.write(pipe2, "bar.txt");
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
latch2.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
out1.write('a');
|
||||
out2.write('b');
|
||||
out1.write('c');
|
||||
out2.write('d');
|
||||
out1.write('e');
|
||||
out2.write('f');
|
||||
out1.close();
|
||||
out2.close();
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(latch2.await(10, TimeUnit.SECONDS));
|
||||
ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
|
||||
session1.read("foo.txt", bos1);
|
||||
session2.read("bar.txt", bos2);
|
||||
assertEquals("ace", new String(bos1.toByteArray()));
|
||||
assertEquals("bdf", new String(bos2.toByteArray()));
|
||||
session1.remove("foo.txt");
|
||||
session2.remove("bar.txt");
|
||||
session1.close();
|
||||
session2.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutNotRecursive() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPut.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutRecursive() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPutRecursive.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(3, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(2),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@IfProfileValue(name="spring.profiles.active", value="realSSH")
|
||||
public void testInt3088MPutRecursiveFiltered() {
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
while (output.receive(0) != null) { }
|
||||
this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(new File("/tmp/sftpOutboundTests/sftpSource")));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
assertNotNull(out);
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertThat(out.getPayload().get(0),
|
||||
not(equalTo(out.getPayload().get(1))));
|
||||
assertThat(
|
||||
out.getPayload().get(0),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
assertThat(
|
||||
out.getPayload().get(1),
|
||||
anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
|
||||
equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -368,6 +368,8 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
<listitem>mget (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
<listitem>mv (move/rename file)</listitem>
|
||||
<listitem>put (send file)</listitem>
|
||||
<listitem>mput (send multiple files)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para><emphasis role="bold">ls</emphasis></para>
|
||||
@@ -422,6 +424,7 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
<para>
|
||||
<emphasis>mget</emphasis> retrieves multiple remote files based on a pattern and supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-P - preserve the timestamps of the remote files</listitem>
|
||||
<listitem>-x - Throw an exception if no files match the pattern (otherwise an empty
|
||||
list is returned)</listitem>
|
||||
</itemizedlist>
|
||||
@@ -457,6 +460,40 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
so that the remote directory structure is retained locally.
|
||||
</para>
|
||||
</note>
|
||||
<para><emphasis role="bold">put</emphasis></para>
|
||||
<para>
|
||||
<emphasis>put</emphasis> sends a file to the remote server; the payload of the message can be a
|
||||
<classname>java.io.File</classname>, a <classname>byte[]</classname> or a <classname>String</classname>.
|
||||
A <code>remote-filename-generator</code> (or expression) is used to name the remote file. Other available attributes include
|
||||
<code>remote-directory</code>, <code>temporary-remote-directory</code> (and their <code>*-expression</code>)
|
||||
equivalents, <code>use-temporary-file-name</code>, and <code>auto-create-directory</code>. Refer to the
|
||||
schema documentation for more information.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from a <emphasis>put</emphasis> operation is a
|
||||
<classname>String</classname> representing the full path of the file on the server after transfer.
|
||||
</para>
|
||||
<para><emphasis role="bold">mput</emphasis></para>
|
||||
<para>
|
||||
<emphasis>mput</emphasis> sends multiple files to the server and supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-R - Recursive - send all files (possibly filtered) in the directory and subdirectories</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
The message payload must be a <classname>java.io.File</classname> representing a local directory.
|
||||
</para>
|
||||
<para>
|
||||
The same attributes as the <code>put</code> command are supported. In addition, files in the local
|
||||
directory can be filtered with one of <code>mput-pattern</code>, <code>mput-regex</code> or
|
||||
<code>mput-filter</code>. The filter works with recursion, as long as the subdirectories themselves
|
||||
pass the filter. Subdirectories that do not pass the filter are not recursed.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>mget</emphasis> operation is a
|
||||
<classname>List<String></classname> object - a List of remote file paths resulting from
|
||||
the transfer.
|
||||
</para>
|
||||
<para><emphasis role="bold">rm</emphasis></para>
|
||||
<para>
|
||||
The <emphasis>rm</emphasis> command has no options.
|
||||
@@ -566,4 +603,15 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
|
||||
sessions are closed when they are returned to the cache. New requests for sessions will establish new sessions as necessary.
|
||||
</para>
|
||||
</section>
|
||||
<section id="ftp-rft">
|
||||
<title>RemoteFileTemplate</title>
|
||||
<para>
|
||||
Starting with <emphasis>Spring Integration version 3.0</emphasis> a new abstraction is provided over the
|
||||
<classname>FtpSession</classname> object. The template provides methods to send, retrieve (as an
|
||||
<classname>InputStream</classname>), remove, and rename files. In addition an <code>execute</code>
|
||||
method is provided allowing the caller to execute multiple operations on the session. In all cases,
|
||||
the template takes care of reliably closing the session.
|
||||
For more information, refer to the javadocs for <classname>RemoteFileTemplate</classname>.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -252,6 +252,18 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="sftp-rft">
|
||||
<title>RemoteFileTemplate</title>
|
||||
<para>
|
||||
Starting with <emphasis>Spring Integration version 3.0</emphasis> a new abstraction is provided over the
|
||||
<classname>SftpSession</classname> object. The template provides methods to send, retrieve (as an
|
||||
<classname>InputStream</classname>), remove, and rename files. In addition an <code>execute</code>
|
||||
method is provided allowing the caller to execute multiple operations on the session. In all cases,
|
||||
the template takes care of reliably closing the session.
|
||||
For more information, refer to the javadocs for <classname>RemoteFileTemplate</classname>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="sftp-inbound">
|
||||
<title>SFTP Inbound Channel Adapter</title>
|
||||
<para>
|
||||
@@ -437,6 +449,8 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
<listitem>mget (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
<listitem>mv (move/rename file)</listitem>
|
||||
<listitem>put (send file)</listitem>
|
||||
<listitem>mput (send multiple files)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para><emphasis role="bold">ls</emphasis></para>
|
||||
@@ -491,6 +505,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
<para>
|
||||
<emphasis>mget</emphasis> retrieves multiple remote files based on a pattern and supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-P - preserve the timestamps of the remote files</listitem>
|
||||
<listitem>-x - Throw an exception if no files match the pattern (otherwise an empty
|
||||
list is returned)</listitem>
|
||||
</itemizedlist>
|
||||
@@ -526,6 +541,40 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
so that the remote directory structure is retained locally.
|
||||
</para>
|
||||
</note>
|
||||
<para><emphasis role="bold">put</emphasis></para>
|
||||
<para>
|
||||
<emphasis>put</emphasis> sends a file to the remote server; the payload of the message can be a
|
||||
<classname>java.io.File</classname>, a <classname>byte[]</classname> or a <classname>String</classname>.
|
||||
A <code>remote-filename-generator</code> (or expression) is used to name the remote file. Other available attributes include
|
||||
<code>remote-directory</code>, <code>temporary-remote-directory</code> (and their <code>*-expression</code>)
|
||||
equivalents, <code>use-temporary-file-name</code>, and <code>auto-create-directory</code>. Refer to the
|
||||
schema documentation for more information.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from a <emphasis>put</emphasis> operation is a
|
||||
<classname>String</classname> representing the full path of the file on the server after transfer.
|
||||
</para>
|
||||
<para><emphasis role="bold">mput</emphasis></para>
|
||||
<para>
|
||||
<emphasis>mput</emphasis> sends multiple files to the server and supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-R - Recursive - send all files (possibly filtered) in the directory and subdirectories</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
The message payload must be a <classname>java.io.File</classname> representing a local directory.
|
||||
</para>
|
||||
<para>
|
||||
The same attributes as the <code>put</code> command are supported. In addition, files in the local
|
||||
directory can be filtered with one of <code>mput-pattern</code>, <code>mput-regex</code> or
|
||||
<code>mput-filter</code>. The filter works with recursion, as long as the subdirectories themselves
|
||||
pass the filter. Subdirectories that do not pass the filter are not recursed.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>mget</emphasis> operation is a
|
||||
<classname>List<String></classname> object - a List of remote file paths resulting from
|
||||
the transfer.
|
||||
</para>
|
||||
<para><emphasis role="bold">rm</emphasis></para>
|
||||
<para>
|
||||
The <emphasis>rm</emphasis> command has no options.
|
||||
|
||||
@@ -299,21 +299,27 @@
|
||||
<para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
The gateways now support the <code>mv</code> command, enabling the renaming of remote
|
||||
files.
|
||||
The gateways now support the <emphasis role="bold">mv</emphasis> command, enabling
|
||||
the renaming of remote files.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The gateways now support recursive <code>ls</code> and <code>mget</code> commands, enabling
|
||||
The gateways now support recursive <emphasis role="bold">ls</emphasis> and
|
||||
<emphasis role="bold">mget</emphasis> commands, enabling
|
||||
the retrieval of a remote file tree.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The gateways now support <emphasis role="bold">put</emphasis> and
|
||||
<emphasis role="bold">mput</emphasis> commands, enabling
|
||||
sending file(s) to the remote server.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The <code>local-filename-generator-expression</code> attribute is now supported,
|
||||
enabling the naming of local files during transfer. By default, the same
|
||||
enabling the naming of local files during retrieval. By default, the same
|
||||
name as the remote file is used.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The <code>local-directory-expression</code> attribute is now supported,
|
||||
enabling the naming of local directories during transfer based on the remote directory.
|
||||
enabling the naming of local directories during retrieval based on the remote directory.
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
@@ -321,6 +327,20 @@
|
||||
For more information, see <xref linkend="ftp-outbound-gateway"/> and <xref linkend="sftp-outbound-gateway"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-remote-file-template">
|
||||
<title>Remote File Template</title>
|
||||
<para>
|
||||
A new higher-level abstraction (<classname>RemoteFileTemplate</classname>) is provided over the
|
||||
<interfacename>Session</interfacename> implementations used by the FTP and SFTP modules. While it is
|
||||
used internally by endpoints, this abstraction can also be used programmatically and, like all
|
||||
Spring <code>*Template</code> implemenations, reliably closes the underlying session while allowing
|
||||
low level access to the session when needed.
|
||||
</para>
|
||||
<para>
|
||||
For more information, see
|
||||
<xref linkend="ftp-rft"/> and <xref linkend="sftp-rft"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-jdbc-mysql-v5_6_4">
|
||||
<title>JDBC Message Store Improvements</title>
|
||||
<para>
|
||||
|
||||
Reference in New Issue
Block a user