Skip to content

Commit b04a857

Browse files
garyrussellartembilan
authored andcommitted
GH-3026 Support chmod with FTP
Resolves #3026 * Fix exception messages; remove test TODOs; test works on Windows # Conflicts: # spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java # src/reference/asciidoc/whats-new.adoc
1 parent f7032c7 commit b04a857

File tree

11 files changed

+94
-10
lines changed

11 files changed

+94
-10
lines changed

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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.io.UncheckedIOException;
2122
import java.util.ArrayList;
2223
import java.util.Collection;
2324
import java.util.List;
@@ -31,7 +32,9 @@
3132
import org.springframework.expression.spel.support.StandardEvaluationContext;
3233
import org.springframework.integration.expression.ExpressionUtils;
3334
import org.springframework.integration.file.remote.AbstractFileInfo;
35+
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
3436
import org.springframework.integration.file.remote.MessageSessionCallback;
37+
import org.springframework.integration.file.remote.RemoteFileOperations;
3538
import org.springframework.integration.file.remote.RemoteFileTemplate;
3639
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
3740
import org.springframework.integration.file.remote.session.Session;
@@ -296,4 +299,22 @@ else if (e instanceof RuntimeException) {
296299
}
297300
}
298301

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

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

Lines changed: 23 additions & 0 deletions
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.1.xsd

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import static org.mockito.Mockito.doAnswer;
3535
import static org.mockito.Mockito.mock;
3636
import static org.mockito.Mockito.spy;
37+
import static org.mockito.Mockito.times;
38+
import static org.mockito.Mockito.verify;
3739

3840
import java.io.ByteArrayInputStream;
3941
import java.io.ByteArrayOutputStream;
@@ -58,6 +60,7 @@
5860
import org.junit.runner.RunWith;
5961
import org.mockito.Mockito;
6062

63+
import org.springframework.beans.DirectFieldAccessor;
6164
import org.springframework.beans.factory.BeanFactory;
6265
import org.springframework.beans.factory.annotation.Autowired;
6366
import org.springframework.beans.factory.annotation.Qualifier;
@@ -395,7 +398,12 @@ public void testRawGETWithTemplate() throws Exception {
395398
}
396399

397400
@Test
398-
public void testInt3088MPutNotRecursive() {
401+
public void testInt3088MPutNotRecursive() throws IOException {
402+
Session<?> session = ftpSessionFactory.getSession();
403+
session.close();
404+
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
405+
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
406+
new DirectFieldAccessor(session).setPropertyValue("client", client);
399407
this.inboundMPut.send(new GenericMessage<File>(getSourceLocalDirectory()));
400408
@SuppressWarnings("unchecked")
401409
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
@@ -409,6 +417,8 @@ public void testInt3088MPutNotRecursive() {
409417
assertThat(
410418
out.getPayload().get(1),
411419
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
420+
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
421+
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
412422
}
413423

414424
@Test
@@ -454,7 +464,12 @@ public void testInt3088MPutRecursiveFiltered() {
454464
}
455465

456466
@Test
457-
public void testInt3412FileMode() {
467+
public void testInt3412FileMode() throws IOException {
468+
Session<?> session = ftpSessionFactory.getSession();
469+
session.close();
470+
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
471+
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
472+
new DirectFieldAccessor(session).setPropertyValue("client", client);
458473
FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(ftpSessionFactory);
459474
assertFalse(template.exists("ftpTarget/appending.txt"));
460475
Message<String> m = MessageBuilder.withPayload("foo")
@@ -474,7 +489,7 @@ public void testInt3412FileMode() {
474489
catch (MessagingException e) {
475490
assertThat(e.getCause().getCause().getMessage(), containsString("The destination file already exists"));
476491
}
477-
492+
verify(client, times(2)).sendSiteCommand("chmod 600 ftpTarget/appending.txt");
478493
}
479494

480495
@Test

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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ The following example shows how to configure an `outbound-channel-adapter`:
833833
temporary-remote-directory-expression="headers['temp_remote_dir']"
834834
filename-generator="fileNameGenerator"
835835
use-temporary-filename="true"
836+
chmod="600"
836837
mode="REPLACE"/>
837838
----
838839
====
@@ -860,6 +861,11 @@ The modes are defined by the `FileExistsMode` enumeration, which includes the fo
860861
`IGNORE` and `FAIL` do not transfer the file.
861862
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).
862863

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

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

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

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

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

12051216
See also <<ftp-partial>>.
12061217

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

12091225
The `rm` command removes files.

0 commit comments

Comments
 (0)