Skip to content

Commit 78366e7

Browse files
authored
GH-3647: Use remoteDirExpression in MV command (#3651)
* GH-3647: Use remoteDirExpression in MV command Fixes #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
1 parent e84bab6 commit 78366e7

File tree

6 files changed

+94
-17
lines changed

6 files changed

+94
-17
lines changed

spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import org.springframework.integration.file.remote.session.SessionFactory;
4343
import org.springframework.integration.file.support.FileExistsMode;
4444
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
45+
import org.springframework.integration.handler.MessageProcessor;
46+
import org.springframework.lang.Nullable;
4547
import org.springframework.messaging.Message;
4648
import org.springframework.messaging.MessageDeliveryException;
4749
import org.springframework.messaging.MessagingException;
@@ -158,6 +160,15 @@ public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
158160
new ExpressionEvaluatingMessageProcessor<>(remoteDirectoryExpression, String.class);
159161
}
160162

163+
/**
164+
* Return the processor for remote directory SpEL expression if any.
165+
* @return the processor for remote directory SpEL expression.
166+
*/
167+
@Nullable
168+
public MessageProcessor<String> getDirectoryExpressionProcessor() {
169+
return this.directoryExpressionProcessor;
170+
}
171+
161172
/**
162173
* Set a temporary remote directory expression; used when transferring files to the remote
163174
* system. After a successful transfer the file is renamed using the

spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
import org.springframework.integration.file.support.FileExistsMode;
5454
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
5555
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
56+
import org.springframework.integration.handler.MessageProcessor;
57+
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
5658
import org.springframework.integration.support.MutableMessage;
5759
import org.springframework.integration.support.PartialSuccessException;
5860
import org.springframework.integration.support.utils.IntegrationUtils;
@@ -736,8 +738,45 @@ private Object doMv(Message<?> requestMessage) {
736738
String remoteFilePath = obtainRemoteFilePath(requestMessage);
737739
String remoteFilename = getRemoteFilename(remoteFilePath);
738740
String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename);
741+
if (remoteDir == null) {
742+
MessageProcessor<String> directoryExpressionProcessor =
743+
this.remoteFileTemplate.getDirectoryExpressionProcessor();
744+
if (directoryExpressionProcessor != null) {
745+
remoteDir = directoryExpressionProcessor.processMessage(requestMessage);
746+
if (remoteDir != null) {
747+
remoteFilePath = remoteDir + this.remoteFileTemplate.getRemoteFileSeparator() + remoteFilename;
748+
}
749+
}
750+
}
751+
752+
String remoteFileNewPath = buildRemoteFileNewPath(requestMessage, remoteDir);
753+
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
754+
755+
return executeMv(requestMessage, remoteFilename, remoteDir, remoteFilePath, remoteFileNewPath);
756+
}
757+
758+
private String obtainRemoteFilePath(Message<?> requestMessage) {
759+
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
760+
Assert.state(remoteFilePath != null,
761+
() -> "The 'fileNameProcessor' evaluated to null 'remoteFilePath' from message: " + requestMessage);
762+
return remoteFilePath;
763+
}
764+
765+
private String buildRemoteFileNewPath(Message<?> requestMessage, @Nullable String remoteDir) {
739766
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
740767
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
768+
if (remoteDir != null) {
769+
String remoteFilename = getRemoteFilename(remoteFileNewPath);
770+
String dir = getRemoteDirectory(remoteFileNewPath, remoteFilename);
771+
if (dir == null) {
772+
remoteFileNewPath = remoteDir + this.remoteFileTemplate.getRemoteFileSeparator() + remoteFilename;
773+
}
774+
}
775+
return remoteFileNewPath;
776+
}
777+
778+
private AbstractIntegrationMessageBuilder<Boolean> executeMv(Message<?> requestMessage, String remoteFilename,
779+
String remoteDir, String remoteFilePath, String remoteFileNewPath) {
741780

742781
return this.remoteFileTemplate.execute(session -> {
743782
Boolean result = mv(requestMessage, session, remoteFilePath, remoteFileNewPath);
@@ -747,15 +786,7 @@ private Object doMv(Message<?> requestMessage) {
747786
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
748787
.setHeader(FileHeaders.RENAME_TO, remoteFileNewPath)
749788
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
750-
}
751-
);
752-
}
753-
754-
private String obtainRemoteFilePath(Message<?> requestMessage) {
755-
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
756-
Assert.state(remoteFilePath != null,
757-
() -> "The 'fileNameProcessor' evaluated to null 'remoteFilePath' from message: " + requestMessage);
758-
return remoteFilePath;
789+
});
759790
}
760791

761792
/**

spring-integration-sftp/src/test/java/org/springframework/integration/sftp/dsl/SftpTests.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2020 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -79,7 +79,7 @@ public void testSftpInboundFlow() {
7979
.regexFilter(".*\\.txt$")
8080
.localFilenameExpression("#this.toUpperCase() + '.a'")
8181
.localDirectory(getTargetLocalDirectory())
82-
.remoteComparator(Comparator.naturalOrder()),
82+
.remoteComparator(Comparator.naturalOrder()),
8383
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
8484
.channel(out)
8585
.get();
@@ -105,10 +105,10 @@ public void testSftpInboundFlow() {
105105
public void testSftpInboundStreamFlow() throws Exception {
106106
QueueChannel out = new QueueChannel();
107107
StandardIntegrationFlow flow = IntegrationFlows.from(
108-
Sftp.inboundStreamingAdapter(new SftpRemoteFileTemplate(sessionFactory()))
109-
.remoteDirectory("sftpSource")
110-
.regexFilter(".*\\.txt$"),
111-
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
108+
Sftp.inboundStreamingAdapter(new SftpRemoteFileTemplate(sessionFactory()))
109+
.remoteDirectory("sftpSource")
110+
.regexFilter(".*\\.txt$"),
111+
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
112112
.channel(out)
113113
.get();
114114
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
@@ -230,7 +230,7 @@ public void testSftpMgetFlow() {
230230
QueueChannel out = new QueueChannel();
231231
IntegrationFlow flow = f -> f
232232
.handle(Sftp.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MGET,
233-
"payload")
233+
"payload")
234234
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
235235
.regexFileNameFilter("(subSftpSource|.*1.txt)")
236236
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
@@ -272,6 +272,31 @@ public void testSftpSessionCallback() {
272272
registration.destroy();
273273
}
274274

275+
276+
@Test
277+
public void testSftpMv() {
278+
QueueChannel out = new QueueChannel();
279+
IntegrationFlow flow = f -> f
280+
.handle(Sftp.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MV, "payload")
281+
.renameExpression("payload.concat('.done')")
282+
.remoteDirectoryExpression("'sftpSource'"))
283+
.channel(out);
284+
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
285+
registration.getInputChannel().send(new GenericMessage<>("sftpSource2.txt"));
286+
Message<?> receive = out.receive(10_000);
287+
assertThat(receive)
288+
.isNotNull()
289+
.extracting(Message::getPayload)
290+
.isEqualTo(Boolean.TRUE);
291+
292+
assertThat(receive.getHeaders())
293+
.containsEntry(FileHeaders.REMOTE_FILE, "sftpSource2.txt")
294+
.containsEntry(FileHeaders.REMOTE_DIRECTORY, "sftpSource")
295+
.containsEntry(FileHeaders.RENAME_TO, "sftpSource/sftpSource2.txt.done");
296+
297+
registration.destroy();
298+
}
299+
275300
@Configuration
276301
@EnableIntegration
277302
public static class ContextConfiguration {

src/reference/asciidoc/ftp.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,7 @@ The `file_remoteDirectory` header provides the remote directory, and the `file_r
12471247

12481248
==== Using the `mv` Command
12491249

1250-
The `mv` command moves files
1250+
The `mv` command moves files.
12511251

12521252
The `mv` command has no options.
12531253

@@ -1259,6 +1259,10 @@ The payload of the result message is `Boolean.TRUE`.
12591259
The `file_remoteDirectory` header provides the original remote directory, and `file_remoteFile` header provides the file name.
12601260
The new path is in the `file_renameTo` header.
12611261

1262+
Starting with version 5.5.6, the `remoteDirectoryExpression` can be used in the `mv` command for convenience.
1263+
If the "`from`" file is not a full file path, the result of `remoteDirectoryExpression` is used as the remote directory.
1264+
The same applies for the "`to`" file, for example, if the task is just to rename a remote file in some directory.
1265+
12621266
==== Additional Information about FTP Outbound Gateway Commands
12631267

12641268
The `get` and `mget` commands support the `local-filename-generator-expression` attribute.

src/reference/asciidoc/sftp.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,10 @@ The payload of the result message is `Boolean.TRUE`.
12141214
The the `file_remoteDirectory` header holds the original remote directory, and the `file_remoteFile` header holds the filename.
12151215
The `file_renameTo` header holds the new path.
12161216

1217+
Starting with version 5.5.6, the `remoteDirectoryExpression` can be used in the `mv` command for convenience.
1218+
If the "`from`" file is not a full file path, the result of `remoteDirectoryExpression` is used as the remote directory.
1219+
The same applies for the "`to`" file, for example, if the task is just to rename a remote file in some directory.
1220+
12171221
==== Additional Command Information
12181222

12191223
The `get` and `mget` commands support the `local-filename-generator-expression` attribute.

src/reference/asciidoc/whats-new.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ For this reason, the property is `false` by default; this may change in a future
9292

9393
The `FileInboundChannelAdapterSpec` has now a convenient `recursive(boolean)` option instead of requiring an explicit reference to the `RecursiveDirectoryScanner`.
9494

95+
The `remoteDirectoryExpression` can now be used in the `mv` command for convenience.
96+
9597
[[x5.5-mongodb]]
9698
==== MongoDb Changes
9799

0 commit comments

Comments
 (0)