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:
@@ -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}.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user