From 78366e7b00f44ae97e2b2b4f22dd312c8524b89b Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 21 Oct 2021 14:12:50 -0400 Subject: [PATCH] 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 --- .../file/remote/RemoteFileTemplate.java | 11 +++++ .../AbstractRemoteFileOutboundGateway.java | 49 +++++++++++++++---- .../integration/sftp/dsl/SftpTests.java | 39 ++++++++++++--- src/reference/asciidoc/ftp.adoc | 6 ++- src/reference/asciidoc/sftp.adoc | 4 ++ src/reference/asciidoc/whats-new.adoc | 2 + 6 files changed, 94 insertions(+), 17 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java index aeffff37f9..00fc723ac0 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java @@ -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 implements RemoteFileOperations, 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 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 diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index 9d69b7071c..97dfd16cb4 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -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 extends AbstractReply String remoteFilePath = obtainRemoteFilePath(requestMessage); String remoteFilename = getRemoteFilename(remoteFilePath); String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename); + if (remoteDir == null) { + MessageProcessor 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 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 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; + }); } /** diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/dsl/SftpTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/dsl/SftpTests.java index a05bab3717..d1e0ec167f 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/dsl/SftpTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/dsl/SftpTests.java @@ -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 { diff --git a/src/reference/asciidoc/ftp.adoc b/src/reference/asciidoc/ftp.adoc index 9bca2de147..58f57ff28e 100644 --- a/src/reference/asciidoc/ftp.adoc +++ b/src/reference/asciidoc/ftp.adoc @@ -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. diff --git a/src/reference/asciidoc/sftp.adoc b/src/reference/asciidoc/sftp.adoc index 4dcf1d28bb..a7d5b69137 100644 --- a/src/reference/asciidoc/sftp.adoc +++ b/src/reference/asciidoc/sftp.adoc @@ -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. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index d6f7def2e4..f4dc30d219 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -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