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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user