GH-3647: Use remoteDirExpression in MV command (#3651)

* GH-3647: Use remoteDirExpression in MV command

Fixes https://github.com/spring-projects/spring-integration/issues/3647

To simplify a source and renameTo remote file expressions, the `remoteDirectoryExpression`
is consulted now, when they are not full paths.
This is useful when we want just to rename a remote file in some dir

* * Add JavaDoc for `getDirectoryExpressionProcessor()`
* Fix language in docs
This commit is contained in:
Artem Bilan
2021-10-21 14:12:50 -04:00
committed by GitHub
parent e84bab6e0a
commit 78366e7b00
6 changed files with 94 additions and 17 deletions

View File

@@ -42,6 +42,8 @@ import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
@@ -158,6 +160,15 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
new ExpressionEvaluatingMessageProcessor<>(remoteDirectoryExpression, String.class);
}
/**
* Return the processor for remote directory SpEL expression if any.
* @return the processor for remote directory SpEL expression.
*/
@Nullable
public MessageProcessor<String> getDirectoryExpressionProcessor() {
return this.directoryExpressionProcessor;
}
/**
* Set a temporary remote directory expression; used when transferring files to the remote
* system. After a successful transfer the file is renamed using the

View File

@@ -53,6 +53,8 @@ import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.MutableMessage;
import org.springframework.integration.support.PartialSuccessException;
import org.springframework.integration.support.utils.IntegrationUtils;
@@ -736,8 +738,45 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
String remoteFilePath = obtainRemoteFilePath(requestMessage);
String remoteFilename = getRemoteFilename(remoteFilePath);
String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename);
if (remoteDir == null) {
MessageProcessor<String> directoryExpressionProcessor =
this.remoteFileTemplate.getDirectoryExpressionProcessor();
if (directoryExpressionProcessor != null) {
remoteDir = directoryExpressionProcessor.processMessage(requestMessage);
if (remoteDir != null) {
remoteFilePath = remoteDir + this.remoteFileTemplate.getRemoteFileSeparator() + remoteFilename;
}
}
}
String remoteFileNewPath = buildRemoteFileNewPath(requestMessage, remoteDir);
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
return executeMv(requestMessage, remoteFilename, remoteDir, remoteFilePath, remoteFileNewPath);
}
private String obtainRemoteFilePath(Message<?> requestMessage) {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
Assert.state(remoteFilePath != null,
() -> "The 'fileNameProcessor' evaluated to null 'remoteFilePath' from message: " + requestMessage);
return remoteFilePath;
}
private String buildRemoteFileNewPath(Message<?> requestMessage, @Nullable String remoteDir) {
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
if (remoteDir != null) {
String remoteFilename = getRemoteFilename(remoteFileNewPath);
String dir = getRemoteDirectory(remoteFileNewPath, remoteFilename);
if (dir == null) {
remoteFileNewPath = remoteDir + this.remoteFileTemplate.getRemoteFileSeparator() + remoteFilename;
}
}
return remoteFileNewPath;
}
private AbstractIntegrationMessageBuilder<Boolean> executeMv(Message<?> requestMessage, String remoteFilename,
String remoteDir, String remoteFilePath, String remoteFileNewPath) {
return this.remoteFileTemplate.execute(session -> {
Boolean result = mv(requestMessage, session, remoteFilePath, remoteFileNewPath);
@@ -747,15 +786,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.setHeader(FileHeaders.RENAME_TO, remoteFileNewPath)
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
}
);
}
private String obtainRemoteFilePath(Message<?> requestMessage) {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
Assert.state(remoteFilePath != null,
() -> "The 'fileNameProcessor' evaluated to null 'remoteFilePath' from message: " + requestMessage);
return remoteFilePath;
});
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2020 the original author or authors.
* Copyright 2014-2021 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.
@@ -79,7 +79,7 @@ public class SftpTests extends SftpTestSupport {
.regexFilter(".*\\.txt$")
.localFilenameExpression("#this.toUpperCase() + '.a'")
.localDirectory(getTargetLocalDirectory())
.remoteComparator(Comparator.naturalOrder()),
.remoteComparator(Comparator.naturalOrder()),
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
.channel(out)
.get();
@@ -105,10 +105,10 @@ public class SftpTests extends SftpTestSupport {
public void testSftpInboundStreamFlow() throws Exception {
QueueChannel out = new QueueChannel();
StandardIntegrationFlow flow = IntegrationFlows.from(
Sftp.inboundStreamingAdapter(new SftpRemoteFileTemplate(sessionFactory()))
.remoteDirectory("sftpSource")
.regexFilter(".*\\.txt$"),
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
Sftp.inboundStreamingAdapter(new SftpRemoteFileTemplate(sessionFactory()))
.remoteDirectory("sftpSource")
.regexFilter(".*\\.txt$"),
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
.channel(out)
.get();
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
@@ -230,7 +230,7 @@ public class SftpTests extends SftpTestSupport {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(Sftp.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MGET,
"payload")
"payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.regexFileNameFilter("(subSftpSource|.*1.txt)")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
@@ -272,6 +272,31 @@ public class SftpTests extends SftpTestSupport {
registration.destroy();
}
@Test
public void testSftpMv() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(Sftp.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MV, "payload")
.renameExpression("payload.concat('.done')")
.remoteDirectoryExpression("'sftpSource'"))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
registration.getInputChannel().send(new GenericMessage<>("sftpSource2.txt"));
Message<?> receive = out.receive(10_000);
assertThat(receive)
.isNotNull()
.extracting(Message::getPayload)
.isEqualTo(Boolean.TRUE);
assertThat(receive.getHeaders())
.containsEntry(FileHeaders.REMOTE_FILE, "sftpSource2.txt")
.containsEntry(FileHeaders.REMOTE_DIRECTORY, "sftpSource")
.containsEntry(FileHeaders.RENAME_TO, "sftpSource/sftpSource2.txt.done");
registration.destroy();
}
@Configuration
@EnableIntegration
public static class ContextConfiguration {

View File

@@ -1247,7 +1247,7 @@ The `file_remoteDirectory` header provides the remote directory, and the `file_r
==== Using the `mv` Command
The `mv` command moves files
The `mv` command moves files.
The `mv` command has no options.
@@ -1259,6 +1259,10 @@ The payload of the result message is `Boolean.TRUE`.
The `file_remoteDirectory` header provides the original remote directory, and `file_remoteFile` header provides the file name.
The new path is in the `file_renameTo` header.
Starting with version 5.5.6, the `remoteDirectoryExpression` can be used in the `mv` command for convenience.
If the "`from`" file is not a full file path, the result of `remoteDirectoryExpression` is used as the remote directory.
The same applies for the "`to`" file, for example, if the task is just to rename a remote file in some directory.
==== Additional Information about FTP Outbound Gateway Commands
The `get` and `mget` commands support the `local-filename-generator-expression` attribute.

View File

@@ -1214,6 +1214,10 @@ The payload of the result message is `Boolean.TRUE`.
The the `file_remoteDirectory` header holds the original remote directory, and the `file_remoteFile` header holds the filename.
The `file_renameTo` header holds the new path.
Starting with version 5.5.6, the `remoteDirectoryExpression` can be used in the `mv` command for convenience.
If the "`from`" file is not a full file path, the result of `remoteDirectoryExpression` is used as the remote directory.
The same applies for the "`to`" file, for example, if the task is just to rename a remote file in some directory.
==== Additional Command Information
The `get` and `mget` commands support the `local-filename-generator-expression` attribute.

View File

@@ -92,6 +92,8 @@ For this reason, the property is `false` by default; this may change in a future
The `FileInboundChannelAdapterSpec` has now a convenient `recursive(boolean)` option instead of requiring an explicit reference to the `RecursiveDirectoryScanner`.
The `remoteDirectoryExpression` can now be used in the `mv` command for convenience.
[[x5.5-mongodb]]
==== MongoDb Changes