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

# Conflicts:
#	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java
#	src/reference/asciidoc/whats-new.adoc
This commit is contained in:
Gary Russell
2019-08-15 17:38:24 -04:00
committed by Artem Bilan
parent f7032c7127
commit b04a8576fa
11 changed files with 94 additions and 10 deletions

View File

@@ -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");
}
}

View File

@@ -71,6 +71,7 @@ public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayP
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "working-dir-expression",
"workingDirExpressionString");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal");
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.ftp.gateway;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -31,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;
@@ -296,4 +299,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);
}
});
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -34,6 +34,8 @@ import static org.junit.Assert.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;
@@ -58,6 +60,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;
@@ -395,7 +398,12 @@ public class FtpServerOutboundTests extends FtpTestSupport {
}
@Test
public void testInt3088MPutNotRecursive() {
public void testInt3088MPutNotRecursive() throws IOException {
Session<?> session = ftpSessionFactory.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);
@@ -409,6 +417,8 @@ public class FtpServerOutboundTests extends FtpTestSupport {
assertThat(
out.getPayload().get(1),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
verify(client).sendSiteCommand("chmod 600 ftpTarget/localSource1.txt");
}
@Test
@@ -454,7 +464,12 @@ public class FtpServerOutboundTests extends FtpTestSupport {
}
@Test
public void testInt3412FileMode() {
public void testInt3412FileMode() throws IOException {
Session<?> session = ftpSessionFactory.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);
assertFalse(template.exists("ftpTarget/appending.txt"));
Message<String> m = MessageBuilder.withPayload("foo")
@@ -474,7 +489,7 @@ public class FtpServerOutboundTests extends FtpTestSupport {
catch (MessagingException e) {
assertThat(e.getCause().getCause().getMessage(), containsString("The destination file already exists"));
}
verify(client, times(2)).sendSiteCommand("chmod 600 ftpTarget/appending.txt");
}
@Test

View File

@@ -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);
}
});
}

View File

@@ -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);
}
});
}

View File

@@ -833,6 +833,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"/>
----
====
@@ -860,6 +861,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.
@@ -1185,6 +1191,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:
@@ -1204,6 +1215,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.

View File

@@ -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