Skip to content

Commit a4f7412

Browse files
garyrussellartembilan
authored andcommitted
GH-3026 Support chmod with FTP
Resolves #3026 * Fix exception messages; remove test TODOs; test works on Windows
1 parent f256974 commit a4f7412

File tree

12 files changed

+99
-10
lines changed

12 files changed

+99
-10
lines changed

spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParser.java

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.beans.factory.config.BeanDefinition;
2222
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
23+
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
2324
import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser;
2425
import org.springframework.integration.file.remote.RemoteFileOperations;
2526
import org.springframework.integration.ftp.outbound.FtpMessageHandler;
@@ -56,6 +57,7 @@ protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element
5657
.getValue();
5758
templateDefinition.getPropertyValues() // NOSONAR never null
5859
.add("existsMode", FtpRemoteFileTemplate.ExistsMode.NLST);
60+
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
5961
}
6062

6163
}

spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element
7171

7272
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "working-dir-expression",
7373
"workingDirExpressionString");
74+
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
7475
}
7576

7677
}

spring-integration-ftp/src/main/java/org/springframework/integration/ftp/gateway/FtpOutboundGateway.java

+20
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import org.springframework.expression.spel.support.StandardEvaluationContext;
3333
import org.springframework.integration.expression.ExpressionUtils;
3434
import org.springframework.integration.file.remote.AbstractFileInfo;
35+
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
3536
import org.springframework.integration.file.remote.MessageSessionCallback;
37+
import org.springframework.integration.file.remote.RemoteFileOperations;
3638
import org.springframework.integration.file.remote.RemoteFileTemplate;
3739
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
3840
import org.springframework.integration.file.remote.session.Session;
@@ -299,4 +301,22 @@ else if (e instanceof RuntimeException) {
299301
}
300302
}
301303

304+
@Override
305+
public boolean isChmodCapable() {
306+
return true;
307+
}
308+
309+
@Override
310+
protected void doChmod(RemoteFileOperations<FTPFile> remoteFileOperations, final String path, final int chmod) {
311+
remoteFileOperations.executeWithClient((ClientCallbackWithoutResult<FTPClient>) client -> {
312+
String chModCommand = "chmod " + Integer.toOctalString(chmod) + " " + path;
313+
try {
314+
client.sendSiteCommand(chModCommand);
315+
}
316+
catch (IOException e) {
317+
throw new UncheckedIOException("Failed to execute '" + chModCommand + "'", e);
318+
}
319+
});
320+
}
321+
302322
}

spring-integration-ftp/src/main/java/org/springframework/integration/ftp/outbound/FtpMessageHandler.java

+23
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
package org.springframework.integration.ftp.outbound;
1818

19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
21+
22+
import org.apache.commons.net.ftp.FTPClient;
1923
import org.apache.commons.net.ftp.FTPFile;
2024

25+
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
2126
import org.springframework.integration.file.remote.RemoteFileTemplate;
2227
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
2328
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -47,4 +52,22 @@ public FtpMessageHandler(RemoteFileTemplate<FTPFile> remoteFileTemplate, FileExi
4752
super(remoteFileTemplate, mode);
4853
}
4954

55+
@Override
56+
public boolean isChmodCapable() {
57+
return true;
58+
}
59+
60+
@Override
61+
protected void doChmod(RemoteFileTemplate<FTPFile> remoteFileTemplate, final String path, final int chmod) {
62+
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<FTPClient>) client -> {
63+
String chModCommand = "chmod " + Integer.toOctalString(chmod) + " " + path;
64+
try {
65+
client.sendSiteCommand(chModCommand);
66+
}
67+
catch (IOException e) {
68+
throw new UncheckedIOException("Failed to execute '" + chModCommand + "'", e);
69+
}
70+
});
71+
}
72+
5073
}

spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-5.2.xsd

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
</xsd:annotation>
6464
</xsd:attribute>
6565
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
66+
<xsd:attributeGroup ref="int-file:chmod" />
6667
</xsd:extension>
6768
</xsd:complexContent>
6869
</xsd:complexType>
@@ -517,6 +518,7 @@
517518
</xsd:annotation>
518519
</xsd:attribute>
519520
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
521+
<xsd:attributeGroup ref="int-file:chmod" />
520522
</xsd:extension>
521523
</xsd:complexContent>
522524
</xsd:complexType>

spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests-context.xml

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
auto-create-directory="true"
8787
filename-pattern="*.txt"
8888
expression="payload"
89+
chmod="600"
8990
remote-directory="ftpTarget"
9091
mput-filter="sortingFilter"
9192
reply-channel="output"/>
@@ -141,6 +142,7 @@
141142
session-factory="ftpSessionFactory"
142143
channel="appending"
143144
mode="APPEND"
145+
chmod="600"
144146
use-temporary-file-name="false"
145147
remote-directory="ftpTarget"
146148
auto-create-directory="true"

spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java

+18-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static org.mockito.Mockito.doAnswer;
2222
import static org.mockito.Mockito.mock;
2323
import static org.mockito.Mockito.spy;
24+
import static org.mockito.Mockito.times;
25+
import static org.mockito.Mockito.verify;
2426

2527
import java.io.ByteArrayInputStream;
2628
import java.io.ByteArrayOutputStream;
@@ -47,6 +49,7 @@
4749
import org.junit.runner.RunWith;
4850
import org.mockito.Mockito;
4951

52+
import org.springframework.beans.DirectFieldAccessor;
5053
import org.springframework.beans.factory.BeanFactory;
5154
import org.springframework.beans.factory.annotation.Autowired;
5255
import org.springframework.beans.factory.annotation.Qualifier;
@@ -399,7 +402,12 @@ public void testRawGETWithTemplate() throws Exception {
399402
}
400403

401404
@Test
402-
public void testInt3088MPutNotRecursive() {
405+
public void testInt3088MPutNotRecursive() throws IOException {
406+
Session<?> session = sessionFactory.getSession();
407+
session.close();
408+
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
409+
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
410+
new DirectFieldAccessor(session).setPropertyValue("client", client);
403411
this.inboundMPut.send(new GenericMessage<File>(getSourceLocalDirectory()));
404412
@SuppressWarnings("unchecked")
405413
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
@@ -410,6 +418,8 @@ public void testInt3088MPutNotRecursive() {
410418
.isIn("ftpTarget/localSource1.txt", "ftpTarget/localSource2.txt");
411419
assertThat(out.getPayload().get(1))
412420
.isIn("ftpTarget/localSource1.txt", "ftpTarget/localSource2.txt");
421+
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
422+
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
413423
}
414424

415425
@Test
@@ -448,7 +458,12 @@ public void testInt3088MPutRecursiveFiltered() {
448458
}
449459

450460
@Test
451-
public void testInt3412FileMode() {
461+
public void testInt3412FileMode() throws IOException {
462+
Session<?> session = sessionFactory.getSession();
463+
session.close();
464+
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
465+
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
466+
new DirectFieldAccessor(session).setPropertyValue("client", client);
452467
FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(ftpSessionFactory);
453468
assertThat(template.exists("ftpTarget/appending.txt")).isFalse();
454469
Message<String> m = MessageBuilder.withPayload("foo")
@@ -468,7 +483,7 @@ public void testInt3412FileMode() {
468483
catch (MessagingException e) {
469484
assertThat(e.getCause().getCause().getMessage()).contains("The destination file already exists");
470485
}
471-
486+
verify(client, times(2)).sendSiteCommand("chmod 600 ftpTarget/appending.txt");
472487
}
473488

474489
@Test

spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ protected void doChmod(RemoteFileOperations<LsEntry> remoteFileOperations, final
158158
client.chmod(chmod, path);
159159
}
160160
catch (SftpException e) {
161-
throw new GeneralSftpException("Failed to execute chmod", e);
161+
throw new GeneralSftpException(
162+
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
162163
}
163164
});
164165
}

spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ protected void doChmod(RemoteFileTemplate<LsEntry> remoteFileTemplate, final Str
7878
client.chmod(chmod, path);
7979
}
8080
catch (SftpException e) {
81-
throw new GeneralSftpException("Failed to execute chmod", e);
81+
throw new GeneralSftpException(
82+
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
8283
}
8384
});
8485
}

src/reference/asciidoc/ftp.adoc

+16
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ The following example shows how to configure an `outbound-channel-adapter`:
837837
temporary-remote-directory-expression="headers['temp_remote_dir']"
838838
filename-generator="fileNameGenerator"
839839
use-temporary-filename="true"
840+
chmod="600"
840841
mode="REPLACE"/>
841842
----
842843
====
@@ -864,6 +865,11 @@ The modes are defined by the `FileExistsMode` enumeration, which includes the fo
864865
`IGNORE` and `FAIL` do not transfer the file.
865866
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).
866867

868+
Version 5.2 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
869+
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
870+
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
871+
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
872+
867873
==== Avoiding Partially Written Files
868874

869875
One of the common problems that arises when dealing with file transfers is the possibility of processing a partial file.
@@ -1189,6 +1195,11 @@ See the https://github.com/spring-projects/spring-integration/tree/master/spring
11891195

11901196
The message payload resulting from a `put` operation is a `String` that represents the full path of the file on the server after transfer.
11911197

1198+
Version 5.2 introduced the `chmod` attribute, which changes the remote file permissions after upload.
1199+
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
1200+
When configuring the adapter using java, you can use `setChmod(0600)`.
1201+
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
1202+
11921203
Using the `mput` Command
11931204

11941205
The `mput` sends multiple files to the server and supports only one option:
@@ -1208,6 +1219,11 @@ The message payload resulting from an `mget` operation is a `List<String>` objec
12081219

12091220
See also <<ftp-partial>>.
12101221

1222+
Version 5.2 introduced the `chmod` attribute, which lets you change the remote file permissions after upload.
1223+
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
1224+
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
1225+
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
1226+
12111227
==== Using the `rm` Command
12121228

12131229
The `rm` command removes files.

src/reference/asciidoc/sftp.adoc

+5-5
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,10 @@ The modes are defined by the `FileExistsMode` enumeration, which has the followi
855855
With `IGNORE` and `FAIL`, the file is not transferred.
856856
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).
857857

858+
Version 4.3 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
859+
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
860+
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
861+
858862
==== Avoiding Partially Written Files
859863

860864
One of the common problems when dealing with file transfers is the possibility of processing a partial file.
@@ -869,10 +873,6 @@ However, there may be situations where you do not want to use this technique (fo
869873
For situations like this, you can disable this feature by setting `use-temporary-file-name` to `false` (the default is `true`).
870874
When this attribute is `false`, the file is written with its final name, and the consuming application needs some other mechanism to detect that the file is completely uploaded before accessing it.
871875

872-
Version 4.3 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
873-
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
874-
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`.
875-
876876
==== Configuring with Java Configuration
877877

878878
The following Spring Boot application shows an example of how to configure the outbound adapter with Java:
@@ -1174,7 +1174,7 @@ See also <<sftp-partial>>.
11741174

11751175
Version 4.3 introduced the `chmod` attribute, which lets you change the remote file permissions after upload.
11761176
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
1177-
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`.
1177+
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
11781178

11791179
Using the `rm` Command
11801180

src/reference/asciidoc/whats-new.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ See <<./ftp.adoc#ftp-server-events, Apache Mina FTP Server Events>> and <<./sftp
6161
Simple Apache Avro transformers are now provided.
6262
See <<./transformers.adoc#avro-transformers, Avro Transformers>> for more information.
6363

64+
[[x5.2-ftp-chmod]]
65+
==== FTP File Permissions
66+
67+
The FTP outbound endpoints now support `chmod` to change permissions on the uploaded file.
68+
(SFTP already supported it since version 4.3).
69+
See <<./ftp.adoc#ftp-outbound,FTP Outbound Channel Adapter>> and <<./ftp.adoc#ftp-outbound-gateway,FTP Outbound Gateway>> for more information.
6470

6571
[[x5.2-general]]
6672
=== General Changes

0 commit comments

Comments
 (0)