GH-3026 Support chmod with FTP
Resolves https://github.com/spring-projects/spring-integration/issues/3026 * Fix exception messages; remove test TODOs; test works on Windows
This commit is contained in:
committed by
Artem Bilan
parent
f256974dd8
commit
a4f7412cf5
@@ -20,6 +20,7 @@ import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser;
|
||||
import org.springframework.integration.file.remote.RemoteFileOperations;
|
||||
import org.springframework.integration.ftp.outbound.FtpMessageHandler;
|
||||
@@ -56,6 +57,7 @@ public class FtpOutboundChannelAdapterParser extends RemoteFileOutboundChannelAd
|
||||
.getValue();
|
||||
templateDefinition.getPropertyValues() // NOSONAR never null
|
||||
.add("existsMode", FtpRemoteFileTemplate.ExistsMode.NLST);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayP
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "working-dir-expression",
|
||||
"workingDirExpressionString");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.MessageSessionCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileOperations;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
@@ -299,4 +301,22 @@ public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFil
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChmodCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileOperations<FTPFile> remoteFileOperations, final String path, final int chmod) {
|
||||
remoteFileOperations.executeWithClient((ClientCallbackWithoutResult<FTPClient>) client -> {
|
||||
String chModCommand = "chmod " + Integer.toOctalString(chmod) + " " + path;
|
||||
try {
|
||||
client.sendSiteCommand(chModCommand);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to execute '" + chModCommand + "'", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
|
||||
package org.springframework.integration.ftp.outbound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
@@ -47,4 +52,22 @@ public class FtpMessageHandler extends FileTransferringMessageHandler<FTPFile> {
|
||||
super(remoteFileTemplate, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChmodCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileTemplate<FTPFile> remoteFileTemplate, final String path, final int chmod) {
|
||||
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<FTPClient>) client -> {
|
||||
String chModCommand = "chmod " + Integer.toOctalString(chmod) + " " + path;
|
||||
try {
|
||||
client.sendSiteCommand(chModCommand);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to execute '" + chModCommand + "'", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
<xsd:attributeGroup ref="int-file:chmod" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -517,6 +518,7 @@
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
|
||||
<xsd:attributeGroup ref="int-file:chmod" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
auto-create-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
expression="payload"
|
||||
chmod="600"
|
||||
remote-directory="ftpTarget"
|
||||
mput-filter="sortingFilter"
|
||||
reply-channel="output"/>
|
||||
@@ -141,6 +142,7 @@
|
||||
session-factory="ftpSessionFactory"
|
||||
channel="appending"
|
||||
mode="APPEND"
|
||||
chmod="600"
|
||||
use-temporary-file-name="false"
|
||||
remote-directory="ftpTarget"
|
||||
auto-create-directory="true"
|
||||
|
||||
@@ -21,6 +21,8 @@ import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -47,6 +49,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -399,7 +402,12 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3088MPutNotRecursive() {
|
||||
public void testInt3088MPutNotRecursive() throws IOException {
|
||||
Session<?> session = sessionFactory.getSession();
|
||||
session.close();
|
||||
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
|
||||
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("client", client);
|
||||
this.inboundMPut.send(new GenericMessage<File>(getSourceLocalDirectory()));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
|
||||
@@ -410,6 +418,8 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
.isIn("ftpTarget/localSource1.txt", "ftpTarget/localSource2.txt");
|
||||
assertThat(out.getPayload().get(1))
|
||||
.isIn("ftpTarget/localSource1.txt", "ftpTarget/localSource2.txt");
|
||||
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
|
||||
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -448,7 +458,12 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3412FileMode() {
|
||||
public void testInt3412FileMode() throws IOException {
|
||||
Session<?> session = sessionFactory.getSession();
|
||||
session.close();
|
||||
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
|
||||
FTPClient client = spy(TestUtils.getPropertyValue(session, "client", FTPClient.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("client", client);
|
||||
FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(ftpSessionFactory);
|
||||
assertThat(template.exists("ftpTarget/appending.txt")).isFalse();
|
||||
Message<String> m = MessageBuilder.withPayload("foo")
|
||||
@@ -468,7 +483,7 @@ public class FtpServerOutboundTests extends FtpTestSupport {
|
||||
catch (MessagingException e) {
|
||||
assertThat(e.getCause().getCause().getMessage()).contains("The destination file already exists");
|
||||
}
|
||||
|
||||
verify(client, times(2)).sendSiteCommand("chmod 600 ftpTarget/appending.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -158,7 +158,8 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry>
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -837,6 +837,7 @@ The following example shows how to configure an `outbound-channel-adapter`:
|
||||
temporary-remote-directory-expression="headers['temp_remote_dir']"
|
||||
filename-generator="fileNameGenerator"
|
||||
use-temporary-filename="true"
|
||||
chmod="600"
|
||||
mode="REPLACE"/>
|
||||
----
|
||||
====
|
||||
@@ -864,6 +865,11 @@ The modes are defined by the `FileExistsMode` enumeration, which includes the fo
|
||||
`IGNORE` and `FAIL` do not transfer the file.
|
||||
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).
|
||||
|
||||
Version 5.2 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
|
||||
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
|
||||
|
||||
==== Avoiding Partially Written Files
|
||||
|
||||
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
|
||||
|
||||
The message payload resulting from a `put` operation is a `String` that represents the full path of the file on the server after transfer.
|
||||
|
||||
Version 5.2 introduced the `chmod` attribute, which changes the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter using java, you can use `setChmod(0600)`.
|
||||
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
|
||||
|
||||
Using the `mput` Command
|
||||
|
||||
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
|
||||
|
||||
See also <<ftp-partial>>.
|
||||
|
||||
Version 5.2 introduced the `chmod` attribute, which lets you change the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
|
||||
Only applies if your FTP server supports the `SITE CHMOD` subcommand.
|
||||
|
||||
==== Using the `rm` Command
|
||||
|
||||
The `rm` command removes files.
|
||||
|
||||
@@ -855,6 +855,10 @@ The modes are defined by the `FileExistsMode` enumeration, which has the followi
|
||||
With `IGNORE` and `FAIL`, the file is not transferred.
|
||||
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).
|
||||
|
||||
Version 4.3 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
|
||||
|
||||
==== Avoiding Partially Written Files
|
||||
|
||||
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
|
||||
For situations like this, you can disable this feature by setting `use-temporary-file-name` to `false` (the default is `true`).
|
||||
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.
|
||||
|
||||
Version 4.3 introduced the `chmod` attribute, which you can use to change the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter using java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`.
|
||||
|
||||
==== Configuring with Java Configuration
|
||||
|
||||
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>>.
|
||||
|
||||
Version 4.3 introduced the `chmod` attribute, which lets you change the remote file permissions after upload.
|
||||
You can use the conventional Unix octal format (for example, `600` allows read-write for the file owner only).
|
||||
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmodDecimal(384)`.
|
||||
When configuring the adapter with Java, you can use `setChmodOctal("600")` or `setChmod(0600)`.
|
||||
|
||||
Using the `rm` Command
|
||||
|
||||
|
||||
@@ -61,6 +61,12 @@ See <<./ftp.adoc#ftp-server-events, Apache Mina FTP Server Events>> and <<./sftp
|
||||
Simple Apache Avro transformers are now provided.
|
||||
See <<./transformers.adoc#avro-transformers, Avro Transformers>> for more information.
|
||||
|
||||
[[x5.2-ftp-chmod]]
|
||||
==== FTP File Permissions
|
||||
|
||||
The FTP outbound endpoints now support `chmod` to change permissions on the uploaded file.
|
||||
(SFTP already supported it since version 4.3).
|
||||
See <<./ftp.adoc#ftp-outbound,FTP Outbound Channel Adapter>> and <<./ftp.adoc#ftp-outbound-gateway,FTP Outbound Gateway>> for more information.
|
||||
|
||||
[[x5.2-general]]
|
||||
=== General Changes
|
||||
|
||||
Reference in New Issue
Block a user