SmbOutboundGateway: Other remote file operations

* Enhanced SmbOutboundGateway with supported remote file operations
* Updated SmbOutboundGateway code based on PR review feedback
* Fully implemented listNames() in SmbSession, added more JUnit tests
* Updated SmbSession code based on PR review comments
This commit is contained in:
Gregory Bragg
2022-05-20 16:37:39 -04:00
committed by GitHub
parent 274b27270f
commit 4203a2369f
4 changed files with 263 additions and 14 deletions

View File

@@ -21,6 +21,7 @@ import java.util.Comparator;
import org.springframework.integration.file.remote.MessageSessionCallback;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.smb.outbound.SmbOutboundGateway;
@@ -126,6 +127,70 @@ public final class Smb {
return new SmbMessageHandlerSpec(smbRemoteFileTemplate, fileExistsMode);
}
/**
* Produce a {@link SmbOutboundGatewaySpec} based on the {@link SessionFactory},
* {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the
* remoteFilePath.
* @param sessionFactory the {@link SessionFactory}.
* @param command the command to perform on the SMB.
* @param expression the remoteFilePath SpEL expression.
* @return the {@link SmbOutboundGatewaySpec}
*/
public static SmbOutboundGatewaySpec outboundGateway(SessionFactory<SmbFile> sessionFactory,
AbstractRemoteFileOutboundGateway.Command command, String expression) {
return outboundGateway(sessionFactory, command.getCommand(), expression);
}
/**
* Produce a {@link SmbOutboundGatewaySpec} based on the {@link SessionFactory},
* {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the
* remoteFilePath.
* @param sessionFactory the {@link SessionFactory}.
* @param command the command to perform on the SMB.
* @param expression the remoteFilePath SpEL expression.
* @return the {@link SmbOutboundGatewaySpec}
* @see RemoteFileTemplate
*/
public static SmbOutboundGatewaySpec outboundGateway(SessionFactory<SmbFile> sessionFactory,
String command, String expression) {
return new SmbOutboundGatewaySpec(new SmbOutboundGateway(sessionFactory, command, expression));
}
/**
* Produce a {@link SmbOutboundGatewaySpec} based on the {@link RemoteFileTemplate},
* {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the
* remoteFilePath.
* @param remoteFileTemplate the {@link RemoteFileTemplate}.
* @param command the command to perform on the SMB.
* @param expression the remoteFilePath SpEL expression.
* @return the {@link SmbOutboundGatewaySpec}
* @see RemoteFileTemplate
*/
public static SmbOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SmbFile> remoteFileTemplate,
AbstractRemoteFileOutboundGateway.Command command, String expression) {
return outboundGateway(remoteFileTemplate, command.getCommand(), expression);
}
/**
* Produce a {@link SmbOutboundGatewaySpec} based on the {@link RemoteFileTemplate},
* {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the
* remoteFilePath.
* @param remoteFileTemplate the {@link RemoteFileTemplate}.
* @param command the command to perform on the SMB.
* @param expression the remoteFilePath SpEL expression.
* @return the {@link SmbOutboundGatewaySpec}
* @see RemoteFileTemplate
*/
public static SmbOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SmbFile> remoteFileTemplate,
String command, String expression) {
return new SmbOutboundGatewaySpec(new SmbOutboundGateway(remoteFileTemplate, command, expression));
}
/**
* Produce a {@link SmbOutboundGatewaySpec} based on the
* {@link MessageSessionCallback}.

View File

@@ -72,6 +72,53 @@ public class SmbOutboundGateway extends AbstractRemoteFileOutboundGateway<SmbFil
super(remoteFileTemplate, messageSessionCallback);
}
/**
* Construct an instance with the supplied session factory, a command ('ls', 'get'
* etc), and an expression to determine the filename.
* @param sessionFactory the session factory.
* @param command the command.
* @param expression the filename expression.
*/
public SmbOutboundGateway(SessionFactory<SmbFile> sessionFactory, String command, String expression) {
this(new SmbRemoteFileTemplate(sessionFactory), command, expression);
remoteFileTemplateExplicitlySet(false);
}
/**
* Construct an instance with the supplied remote file template, a command ('ls',
* 'get' etc), and an expression to determine the filename.
* @param remoteFileTemplate the remote file template.
* @param command the command.
* @param expression the filename expression.
*/
public SmbOutboundGateway(RemoteFileTemplate<SmbFile> remoteFileTemplate, String command, String expression) {
super(remoteFileTemplate, command, expression);
}
/**
* Construct an instance with the supplied session factory
* and command ('ls', 'nlst', 'put' or 'mput').
* <p> The {@code remoteDirectory} expression is {@code null} assuming to use
* the {@code workingDirectory} from the SMB Client.
* @param sessionFactory the session factory.
* @param command the command.
*/
public SmbOutboundGateway(SessionFactory<SmbFile> sessionFactory, String command) {
this(sessionFactory, command, null);
}
/**
* Construct an instance with the supplied remote file template
* and command ('ls', 'nlst', 'put' or 'mput').
* <p> The {@code remoteDirectory} expression is {@code null} assuming to use
* the {@code workingDirectory} from the SMB Client.
* @param remoteFileTemplate the remote file template.
* @param command the command.
*/
public SmbOutboundGateway(RemoteFileTemplate<SmbFile> remoteFileTemplate, String command) {
this(remoteFileTemplate, command, null);
}
@Override
public String getComponentType() {
return "smb:outbound-gateway";

View File

@@ -97,7 +97,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Deletes the file or directory at the specified path.
* Delete the file or directory at the specified path.
* @param _path path to a remote file or directory
* @return true if delete successful, false if resource is non-existent
* @throws IOException on error conditions returned by a CIFS server
@@ -123,7 +123,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Returns the contents of the specified SMB resource as an array of SmbFile objects.
* Return the contents of the specified SMB resource as an array of SmbFile objects.
* In case the remote resource does not exist, an empty array is returned.
* @param _path path to a remote directory
* @return array of SmbFile objects
@@ -162,7 +162,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Reads the remote resource specified by path and copies its contents to the specified
* Read the remote resource specified by path and copies its contents to the specified
* {@link OutputStream}.
* @param _path path to a remote file
* @param _outputStream output stream
@@ -190,7 +190,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Writes contents of the specified {@link InputStream} to the remote resource
* Write contents of the specified {@link InputStream} to the remote resource
* specified by path. Remote directories are created implicitly as required.
* @param _inputStream input stream
* @param _path remote path (of a file) to write to
@@ -237,7 +237,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Creates the specified remote path if not yet exists.
* Create the specified remote path if not yet exists.
* If the specified resource is a file rather than a path, creates all directories leading
* to that file.
* @param _path remote path to create
@@ -268,7 +268,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Checks whether the remote resource exists.
* Check whether the remote resource exists.
* @param _path remote path
* @return true if exists, false otherwise
* @throws IOException on error conditions returned by a CIFS server
@@ -279,7 +279,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Checks whether the remote resource is a file.
* Check whether the remote resource is a file.
* @param _path remote path
* @return true if resource is a file, false otherwise
* @throws IOException on error conditions returned by a CIFS server
@@ -290,7 +290,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Checks whether the remote resource is a directory.
* Check whether the remote resource is a directory.
* @param _path remote path
* @return true if resource is a directory, false otherwise
* @throws IOException on error conditions returned by a CIFS server
@@ -387,8 +387,8 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Checks with this SMB session is open and ready for work by attempting
* to list remote files and checking for error conditions..
* Check whether this SMB session is open and ready for work by attempting
* to list remote files and checking for error conditions.
* @return true if the session is open, false otherwise
*/
@Override
@@ -455,7 +455,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Creates an SMB file object pointing to a remote file.
* Create an SMB file object pointing to a remote file.
* @param _path the remote file path
* @return the {@link SmbFile} for remote path
* @throws IOException the IO exception
@@ -465,7 +465,7 @@ public class SmbSession implements Session<SmbFile> {
}
/**
* Creates an SMB file object pointing to a remote directory.
* Create an SMB file object pointing to a remote directory.
* @param _path the remote directory path
* @return the {@link SmbFile} for remote path
* @throws IOException the IO exception
@@ -480,9 +480,43 @@ public class SmbSession implements Session<SmbFile> {
return url.getHost() + ":" + url.getPort();
}
/**
* Return the contents of the specified SMB resource as an array of SmbFile filenames.
* In case the remote resource does not exist, an empty array is returned.
* @param _path path to a remote directory
* @return array of SmbFile filenames
* @throws IOException on error conditions returned by a CIFS server or if the remote resource is not a directory.
*/
@Override
public String[] listNames(String path) {
throw new UnsupportedOperationException("Not implemented yet");
public String[] listNames(String _path) throws IOException {
String[] fileNames = new String[0];
try {
SmbFile smbDir = createSmbDirectoryObject(_path);
if (!smbDir.exists()) {
if (logger.isWarnEnabled()) {
logger.warn("Remote directory [" + _path + "] does not exist. Cannot list resources.");
}
return fileNames;
}
else if (!smbDir.isDirectory()) {
throw new IOException("Resource [" + _path + "] is not a directory. Cannot list resources.");
}
fileNames = smbDir.list();
}
catch (SmbException _ex) {
throw new IOException("Failed to list resources in [" + _path + "].", _ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Successfully listed " + fileNames.length + " resource(s) in [" + _path + "]"
+ ": " + Arrays.toString(fileNames));
}
else if (logger.isInfoEnabled()) {
logger.info("Successfully listed " + fileNames.length + " resource(s) in [" + _path + "]" + ".");
}
return fileNames;
}
}

View File

@@ -24,7 +24,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -48,14 +50,17 @@ import org.springframework.integration.file.DefaultDirectoryScanner;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.smb.SmbTestSupport;
import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizingMessageSource;
import org.springframework.integration.smb.inbound.SmbStreamingMessageSource;
import org.springframework.integration.smb.session.SmbFileInfo;
import org.springframework.integration.smb.session.SmbRemoteFileTemplate;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@@ -77,6 +82,7 @@ import jcifs.smb.SmbFile;
* |-- SMBSOURCE2.TXT.a
* |-- subSmbSource/
* |-- subSmbSource1.txt - contains 'subSource1'
* |-- subSmbSource2.txt - contains 'subSource2'
* smbTarget/
* </pre>
*
@@ -270,6 +276,103 @@ public class SmbTests extends SmbTestSupport {
registration.destroy();
}
@Test
@SuppressWarnings("unchecked")
public void testSmbMgetFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.fileExistsMode(FileExistsMode.IGNORE)
.filterExpression("name matches 'subSmbSource|.*.txt'")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')")
.charset(StandardCharsets.UTF_8.name())
.useTemporaryFileName(true))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String dir = "smbSource/subSmbSource/";
registration.getInputChannel().send(new GenericMessage<>(dir + "*"));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<File> localFiles = (List<File>) result.getPayload();
assertThat(localFiles.size()).as("unexpected local files " + localFiles).isEqualTo(2);
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/")).contains(dir);
}
assertThat(localFiles.get(1).getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"))
.contains(dir + "subSmbSource");
registration.destroy();
}
@Test
@SuppressWarnings("unchecked")
public void testSmbLsFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.LS, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.fileExistsMode(FileExistsMode.IGNORE)
.filterExpression("name matches 'subSmbSource|.*.txt'")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')")
.charset(StandardCharsets.UTF_8.name())
.useTemporaryFileName(true))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String dir = "smbSource/subSmbSource/";
registration.getInputChannel().send(new GenericMessage<>(dir));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<SmbFileInfo> localFiles = (List<SmbFileInfo>) result.getPayload();
assertThat(localFiles.size()).as("unexpected local files " + localFiles).isEqualTo(2);
for (SmbFileInfo fileInfo : localFiles) {
SmbFile file = fileInfo.getFileInfo();
assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/")).contains(dir);
}
assertThat(localFiles.get(1).getFileInfo().getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"))
.contains(dir + "subSmbSource");
registration.destroy();
}
@Test
@SuppressWarnings("unchecked")
public void testSmbNlstFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.NLST, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.ALL)
.fileExistsMode(FileExistsMode.IGNORE)
.filterExpression("name matches 'subSmbSource|.*.txt'")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')")
.charset(StandardCharsets.UTF_8.name())
.useTemporaryFileName(true))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String dir = "smbSource/subSmbSource/";
registration.getInputChannel().send(new GenericMessage<>(dir));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<String> localFilenames = (List<String>) result.getPayload();
assertThat(localFilenames.size()).as("unexpected local filenames " + localFilenames).isEqualTo(2);
for (String filename : localFilenames) {
assertThat(filename.contains("subSmbSource"));
}
registration.destroy();
}
@Configuration
@EnableIntegration
@EnableIntegrationManagement