diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java index 59c2f170c4..e9079757b5 100644 --- a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java @@ -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 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 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 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 remoteFileTemplate, + String command, String expression) { + + return new SmbOutboundGatewaySpec(new SmbOutboundGateway(remoteFileTemplate, command, expression)); + } + + /** * Produce a {@link SmbOutboundGatewaySpec} based on the * {@link MessageSessionCallback}. diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java index 75a99c5d07..a400721718 100644 --- a/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java @@ -72,6 +72,53 @@ public class SmbOutboundGateway extends AbstractRemoteFileOutboundGateway 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 remoteFileTemplate, String command, String expression) { + super(remoteFileTemplate, command, expression); + } + + /** + * Construct an instance with the supplied session factory + * and command ('ls', 'nlst', 'put' or 'mput'). + *

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 sessionFactory, String command) { + this(sessionFactory, command, null); + } + + /** + * Construct an instance with the supplied remote file template + * and command ('ls', 'nlst', 'put' or 'mput'). + *

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 remoteFileTemplate, String command) { + this(remoteFileTemplate, command, null); + } + @Override public String getComponentType() { return "smb:outbound-gateway"; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java index e5ee809b4c..1aee19bff8 100644 --- a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java @@ -97,7 +97,7 @@ public class SmbSession implements Session { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { } /** - * 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 { 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; } } diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java index 2f24a3a7e2..f45f4a7292 100644 --- a/spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java @@ -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/ * * @@ -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 localFiles = (List) 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 localFiles = (List) 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 localFilenames = (List) 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