[[smb]]
== SMB Support

Spring Integration provides support for file transfer operations with SMB.

The https://en.wikipedia.org/wiki/Server_Message_Block[Server Message Block] (SMB) is a simple network protocol that lets you transfer files to a shared file server.

You need to include this dependency into your project:

====
[source, xml, subs="normal", role="primary"]
.Maven
----
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-smb</artifactId>
    <version>{project-version}</version>
</dependency>
----
[source, groovy, subs="normal", role="secondary"]
.Gradle
----
compile "org.springframework.integration:spring-integration-smb:{project-version}"
----
====

=== Overview

The https://github.com/codelibs/jcifs[Java CIFS] Client Library has been chosen as a Java implementation for the CIFS/SMB networking protocol.
Its `SmbFile` abstraction is simply wrapped to the Spring Integration "Remote File" foundations like `SmbSession`, `SmbRemoteFileTemplate`, etc.

The SMB Channel Adapters and support classes implementations are fully similar to existing components for (S)FTP or AWS S3 protocols.
So, if you familiar with those components it is pretty straightforward to use.

Spring Integration supports sending and receiving files over SMB by providing three client-side endpoints: inbound channel adapter, outbound channel adapter, and outbound gateway.
It also provides convenient namespace-based configuration options for defining these client components.

To use the SMB namespace, add the following to the header of your XML file:

====
[source,xml]
----
xmlns:int-smb="http://www.springframework.org/schema/integration/smb"
xsi:schemaLocation="http://www.springframework.org/schema/integration/smb
    https://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd"

----
====

[[smb-session-factory]]
=== SMB Session Factory

Before configuring the SMB adapter, you must configure an SMB session factory.
You can configure the SMB session factory with a regular bean definition, as the following examples show:

The `SmbSessionFactory` exposes options to set the SMB protocol with Min/Max versions.
For example, supporting a minimum version of SMB 2.1 and a maximum version of the SMB 3.1.1:

[source,java]
----
@Bean
public SmbSessionFactory smbSessionFactory() {
    SmbSessionFactory smbSession = new SmbSessionFactory();
    smbSession.setHost("myHost");
    smbSession.setPort(445);
    smbSession.setDomain("myDomain");
    smbSession.setUsername("myUser");
    smbSession.setPassword("myPassword");
    smbSession.setShareAndDir("myShareAndDir");
    smbSession.setSmbMinVersion(DialectVersion.SMB210);
    smbSession.setSmbMaxVersion(DialectVersion.SMB311);
    return smbSession;
}
----

The `SmbSessionFactory` can be initialized with a custom `jcifs.CIFSContext`.

NOTE: Setting of the SMB protocol Min/Max versions must be done in your implementation of `jcifs.CIFSContext`.

[source,java]
----
@Bean
public SmbSessionFactory smbSessionFactory() {
    SmbSessionFactory smbSession = new SmbSessionFactory(new MyCIFSContext());
    smbSession.setHost("myHost");
    smbSession.setPort(445);
    smbSession.setDomain("myDomain");
    smbSession.setUsername("myUser");
    smbSession.setPassword("myPassword");
    smbSession.setShareAndDir("myShareAndDir");
    return smbSession;
}
----

[[smb-inbound]]
=== SMB Inbound Channel Adapter

To download SMB files locally the `SmbInboundFileSynchronizingMessageSource` is provided.
It is simple extension of the `AbstractInboundFileSynchronizingMessageSource` which  requires `SmbInboundFileSynchronizer` injection.
For filtering remote files you still can use any existing `FileListFilter` implementations, but particular `SmbRegexPatternFileListFilter` and `SmbSimplePatternFileListFilter` are provided.

[source,java]
----
@Bean
public SmbInboundFileSynchronizer smbInboundFileSynchronizer() {
    SmbInboundFileSynchronizer fileSynchronizer =
        new SmbInboundFileSynchronizer(smbSessionFactory());
    fileSynchronizer.setFilter(compositeFileListFilter());
    fileSynchronizer.setRemoteDirectory("mySharedDirectoryPath");
    fileSynchronizer.setDeleteRemoteFiles(true);
    return fileSynchronizer;
}

@Bean
public CompositeFileListFilter<SmbFile> compositeFileListFilter() {
    CompositeFileListFilter<SmbFile> filters = new CompositeFileListFilter<>();
    filters.addFilter(new SmbRegexPatternFileListFilter("^(?i).+((\\.txt))$"));
    return filters;
}

@Bean
public MessageChannel smbFileInputChannel() {
    return new DirectChannel();
}

@Bean
@InboundChannelAdapter(value = "smbFileInputChannel",
                       poller = @Poller(fixedDelay = "2000"))
public MessageSource<File> smbMessageSource() {
    SmbInboundFileSynchronizingMessageSource messageSource =
        new SmbInboundFileSynchronizingMessageSource(smbInboundFileSynchronizer());
    messageSource.setLocalDirectory(new File("myLocalDirectoryPath"));
    messageSource.setAutoCreateLocalDirectory(true);
    return messageSource;
}
----

For XML configuration the `<int-smb:inbound-channel-adapter>` component is provided.

==== Configuring with the Java DSL

The following Spring Boot application shows an example of how to configure the inbound adapter with the Java DSL:

====
[source, java]
----
@SpringBootApplication
public class SmbJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SmbJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public SmbSessionFactory smbSessionFactory() {
        SmbSessionFactory smbSession = new SmbSessionFactory();
        smbSession.setHost("myHost");
        smbSession.setPort(445);
        smbSession.setDomain("myDomain");
        smbSession.setUsername("myUser");
        smbSession.setPassword("myPassword");
        smbSession.setShareAndDir("myShareAndDir");
        smbSession.setSmbMinVersion(DialectVersion.SMB210);
        smbSession.setSmbMaxVersion(DialectVersion.SMB311);
        return smbSession;
    }

    @Bean
    public IntegrationFlow smbInboundFlow() {
        return IntegrationFlow
            .from(Smb.inboundAdapter(smbSessionFactory())
                    .preserveTimestamp(true)
                    .remoteDirectory("smbSource")
                    .regexFilter(".*\\.txt$")
                    .localFilename(f -> f.toUpperCase() + ".a")
                    .localDirectory(new File("d:\\smb_files")),
                        e -> e.id("smbInboundAdapter")
                    .autoStartup(true)
                    .poller(Pollers.fixedDelay(5000)))
            .handle(m -> System.out.println(m.getPayload()))
            .get();
    }
}
----
====

[[smb-streaming-inbound]]
=== SMB Streaming Inbound Channel Adapter

This adapter produces message with payloads of type `InputStream`, letting files be fetched without writing to the local file system.
Since the session remains open, the consuming application is responsible for closing the session when the file has been consumed.
The session is provided in the `closeableResource` header (`IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE`).
Standard framework components, such as the `FileSplitter` and `StreamTransformer`, automatically close the session.
See <<./file.adoc#file-splitter,File Splitter>> and <<./transformer.adoc#stream-transformer,Stream Transformer>> for more information about these components.
The following example shows how to configure an `inbound-streaming-channel-adapter`:

====
[source, xml]
----
<int-smb:inbound-streaming-channel-adapter id="smbInbound"
            channel="smbChannel"
            session-factory="sessionFactory"
            filename-pattern="*.txt"
            filename-regex=".*\.txt"
            filter="filter"
            filter-expression="@myFilterBean.check(#root)"
            remote-file-separator="/"
            comparator="comparator"
            max-fetch-size="1"
            remote-directory-expression="'foo/bar'">
        <int:poller fixed-rate="1000" />
</int-smb:inbound-streaming-channel-adapter>
----
====

Only one of `filename-pattern`, `filename-regex`, `filter`, or `filter-expression` is allowed.

The `SmbStreamingMessageSource` adapter prevents duplicates for remote files with `SmbPersistentAcceptOnceFileListFilter` based on the in-memory `SimpleMetadataStore`.
By default, this filter is also applied with the filename pattern (or regex).
If you need to allow duplicates, you can use `AcceptAllFileListFilter`.
Any other use cases can be handled by `CompositeFileListFilter` (or `ChainFileListFilter`).
The Java configuration (<<smb-streaming-java,later in the document>>) shows one technique to remove the remote file after processing to avoid duplicates.

For more information about the `SmbPersistentAcceptOnceFileListFilter`, and how it is used, see <<./file.adoc#remote-persistent-flf,Remote Persistent File List Filters>>.

Use the `max-fetch-size` attribute to limit the number of files fetched on each poll when a fetch is necessary.
Set it to `1` and use a persistent filter when running in a clustered environment.
See <<smb-max-fetch>> for more information.

The adapter puts the remote directory and file name in the `FileHeaders.REMOTE_DIRECTORY` and `FileHeaders.REMOTE_FILE` headers, respectively.
The `FileHeaders.REMOTE_FILE_INFO` header provides additional remote file information (represented in JSON by default).
If you set the `fileInfoJson` property on the `SmbStreamingMessageSource` to `false`, the header contains an `SmbFileInfo` object.

[[smb-streaming-java]]
==== Configuring with Java Configuration

The following Spring Boot application shows an example of how to configure the inbound adapter with Java configuration:

====
[source, java]
----
@SpringBootApplication
public class SmbJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SmbJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    @InboundChannelAdapter(channel = "stream")
    public MessageSource<InputStream> smbMessageSource() {
        SmbStreamingMessageSource messageSource = new SmbStreamingMessageSource(template());
        messageSource.setRemoteDirectory("smbSource/");
        messageSource.setFilter(new AcceptAllFileListFilter<>());
        messageSource.setMaxFetchSize(1);
        return messageSource;
    }

    @Bean
    @Transformer(inputChannel = "stream", outputChannel = "data")
    public org.springframework.integration.transformer.Transformer transformer() {
        return new StreamTransformer("UTF-8");
    }

    @Bean
    public SmbRemoteFileTemplate template() {
        return new SmbRemoteFileTemplate(smbSessionFactory());
    }

    @ServiceActivator(inputChannel = "data", adviceChain = "after")
    @Bean
    public MessageHandler handle() {
        return System.out::println;
    }

    @Bean
    public ExpressionEvaluatingRequestHandlerAdvice after() {
        ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
        advice.setOnSuccessExpression(
                "@template.remove(headers['file_remoteDirectory'] + headers['file_remoteFile'])");
        advice.setPropagateEvaluationFailures(true);
        return advice;
    }

}
----
====

Notice that, in this example, the message handler downstream of the transformer has an `advice` that removes the remote file after processing.

[[smb-max-fetch]]
=== Inbound Channel Adapters: Controlling Remote File Fetching

There are two properties that you should consider when you configure inbound channel adapters.
`max-messages-per-poll`, as with all pollers, can be used to limit the number of messages emitted on each poll (if more than the configured value are ready).
`max-fetch-size` can limit the number of files retrieved from the remote server at one time.

The following scenarios assume the starting state is an empty local directory:

* `max-messages-per-poll=2` and `max-fetch-size=1`: The adapter fetches one file, emits it, fetches the next file, emits it, and then sleeps until the next poll.
* `max-messages-per-poll=2` and `max-fetch-size=2`): The adapter fetches both files and then emits each one.
* `max-messages-per-poll=2` and `max-fetch-size=4`: The adapter fetches up to four files (if available) and emits the first two (if there are at least two).
The next two files are emitted on the next poll.
* `max-messages-per-poll=2` and `max-fetch-size` not specified: The adapter fetches all remote files and emits the first two (if there are at least two).
The subsequent files are emitted on subsequent polls (two at a time).
When all files are consumed, the remote fetch is attempted again, to pick up any new files.

IMPORTANT: When you deploy multiple instances of an application, we recommend a small `max-fetch-size`, to avoid one instance "`grabbing`" all the files and starving other instances.

Another use for `max-fetch-size` is if you want to stop fetching remote files but continue to process files that have already been fetched.
Setting the `maxFetchSize` property on the `MessageSource` (programmatically, with JMX, or with a <<./control-bus.adoc#control-bus,control bus>>) effectively stops the adapter from fetching more files but lets the poller continue to emit messages for files that have previously been fetched.
If the poller is active when the property is changed, the change takes effect on the next poll.

The synchronizer can be provided with a `Comparator<SmbFile>`.
This is useful when restricting the number of files fetched with `maxFetchSize`.

[[smb-outbound]]
=== SMB Outbound Channel Adapter

For writing files to an SMB share, and for XML `<int-smb:outbound-channel-adapter>` component we use the `SmbMessageHandler`.
In case of Java configuration a `SmbMessageHandler` should be supplied with the `SmbSessionFactory` (or `SmbRemoteFileTemplate`).

[source,java]
----
@Bean
@ServiceActivator(inputChannel = "storeToSmbShare")
public MessageHandler smbMessageHandler(SmbSessionFactory smbSessionFactory) {
    SmbMessageHandler handler = new SmbMessageHandler(smbSessionFactory);
    handler.setRemoteDirectoryExpression(
        new LiteralExpression("remote-target-dir"));
    handler.setFileNameGenerator(m ->
        m.getHeaders().get(FileHeaders.FILENAME, String.class) + ".test");
    handler.setAutoCreateDirectory(true);
    return handler;
}
----

==== Configuring with the Java DSL

The following Spring Boot application shows an example of how to configure the outbound adapter using the Java DSL:

====
[source, java]
----
@SpringBootApplication
@IntegrationComponentScan
public class SmbJavaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            new SpringApplicationBuilder(SmbJavaApplication.class)
                .web(false)
                .run(args);
        MyGateway gateway = context.getBean(MyGateway.class);
        gateway.sendToSmb(new File("/foo/bar.txt"));
    }

    @Bean
    public SmbSessionFactory smbSessionFactory() {
        SmbSessionFactory smbSession = new SmbSessionFactory();
        smbSession.setHost("myHost");
        smbSession.setPort(445);
        smbSession.setDomain("myDomain");
        smbSession.setUsername("myUser");
        smbSession.setPassword("myPassword");
        smbSession.setShareAndDir("myShareAndDir");
        smbSession.setSmbMinVersion(DialectVersion.SMB210);
        smbSession.setSmbMaxVersion(DialectVersion.SMB311);
        return smbSession;
    }

    @Bean
    public IntegrationFlow smbOutboundFlow() {
        return IntegrationFlow.from("toSmbChannel")
                .handle(Smb.outboundAdapter(smbSessionFactory(), FileExistsMode.REPLACE)
                        .useTemporaryFileName(false)
                        .fileNameExpression("headers['" + FileHeaders.FILENAME + "']")
                        .remoteDirectory("smbTarget")
                ).get();
    }

    @MessagingGateway
    public interface MyGateway {

         @Gateway(requestChannel = "toSmbChannel")
         void sendToSmb(File file);
    }

}
----
====

[[smb-outbound-gateway]]
=== SMB Outbound Gateway

The SMB outbound gateway provides a limited set of commands to interact with a remote SMB server.
The supported commands are:

* `ls` (list files)
* `nlst` (list file names)
* `get` (retrieve file)
* `mget` (retrieve file(s))
* `rm` (remove file(s))
* `mv` (move/rename file)
* `put` (send file)
* `mput` (send multiple files)

==== Using the `ls` Command

`ls` lists remote files and supports the following options:

* `-1`: Retrieve a list of filenames.
The default is to retrieve a list of `FileInfo` objects
* `-a`: Include all files (including those starting with '.')
* `-f`: Do not sort the list
* `-dirs`: Include directories (excluded by default)
* `-links`: Include symbolic links (excluded by default)
* `-R`: List the remote directory recursively

In addition, filename filtering is provided in the same manner as the `inbound-channel-adapter`.

The message payload resulting from an `ls` operation is a list of file names or a list of `FileInfo` objects (depending on whether you usr the `-1` switch).
These objects provide information such as modified time, permissions, and others.

The remote directory that the `ls` command acted on is provided in the `file_remoteDirectory` header.

When using the recursive option (`-R`), the `fileName` includes any subdirectory elements and represents the relative path to the file (relative to the remote directory).
If you use the `-dirs` option, each recursive directory is also returned as an element in the list.
In this case, we recommend that you not use the `-1` option, because you would not be able to distinguish files from directories, which you can do when you use `FileInfo` objects.

==== Using `nlst` Command

`nlst` lists remote file names and supports only one option:

* `-f`: Do not sort the list

The message payload resulting from an `nlst` operation is a list of file names.

The `file_remoteDirectory` header holds the remote directory on which the `nlst` command acted.

==== Using the `get` Command

`get` retrieves a remote file and supports the following options:

* `-P`: Preserve the timestamp of the remote file.
* `-stream`: Retrieve the remote file as a stream.
* `-D`: Delete the remote file after successful transfer.
The remote file is not deleted if the transfer is ignored, because the `FileExistsMode` is `IGNORE` and the local file already exists.

The `file_remoteDirectory` header holds the remote directory, and the `file_remoteFile` header holds the filename.

The message payload resulting from a `get` operation is a `File` object representing the retrieved file.
If you use the `-stream` option, the payload is an `InputStream` rather than a `File`.
For text files, a common use case is to combine this operation with a <<./file.adoc#file-splitter,file splitter>> or a <<./transformer.adoc#stream-transformer,stream transformer>>.
When consuming remote files as streams, you are responsible for closing the `Session` after the stream is consumed.
For convenience, the `Session` is provided in the `closeableResource` header, and `IntegrationMessageHeaderAccessor` offers convenience method:

====
[source, java]
----
Closeable closeable = new IntegrationMessageHeaderAccessor(message).getCloseableResource();
if (closeable != null) {
    closeable.close();
}
----
====

Framework components, such as the <<./file.adoc#file-splitter,File Splitter>> and <<./transformer.adoc#stream-transformer,Stream Transformer>>, automatically close the session after the data is transferred.

The following example shows how to consume a file as a stream:

====
[source, xml]
----
<int-smb:outbound-gateway session-factory="smbSessionFactory"
                            request-channel="inboundGetStream"
                            command="get"
                            command-options="-stream"
                            expression="payload"
                            remote-directory="smbTarget"
                            reply-channel="stream" />

<int-file:splitter input-channel="stream" output-channel="lines" />
----
====

NOTE: If you consume the input stream in a custom component, you must close the `Session`.
You can either do that in your custom code or route a copy of the message to a `service-activator` and use SpEL, as the following example shows:

====
[source, xml]
----
<int:service-activator input-channel="closeSession"
    expression="headers['closeableResource'].close()" />
----
====

==== Using the `mget` Command

`mget` retrieves multiple remote files based on a pattern and supports the following options:

* `-P`: Preserve the timestamps of the remote files.

* `-R`: Retrieve the entire directory tree recursively.

* `-x`: Throw an exception if no files match the pattern (otherwise, an empty list is returned).

* `-D`: Delete each remote file after successful transfer.
If the transfer is ignored, the remote file is not deleted, because the `FileExistsMode` is `IGNORE` and the local file already exists.

The message payload resulting from an `mget` operation is a `List<File>` object (that is, a `List` of `File` objects, each representing a retrieved file).

IMPORTANT: If the `FileExistsMode` is `IGNORE`, the payload of the output message no longer contain files that were not fetched due to the file already existing.
Previously, the array contained all files, including those that already existed.

The expression you use determine the remote path should produce a result that ends with `*` for example `myfiles/*` fetches the complete tree under `myfiles`.

You can use a recursive `MGET`, combined with the `FileExistsMode.REPLACE_IF_MODIFIED` mode, to periodically synchronize an entire remote directory tree locally.
This mode sets the local file's last modified timestamp to the remote file's timestamp, regardless of the `-P` (preserve timestamp) option.

.Notes for when using recursion (`-R`)
[IMPORTANT]
=====

The pattern is ignored and `*` is assumed.
By default, the entire remote tree is retrieved.
However, you can filter files in the tree by providing a `FileListFilter`.
You can also filter directories in the tree this way.
A `FileListFilter` can be provided by reference or by `filename-pattern` or `filename-regex` attributes.
For example, `filename-regex="(subDir|.*1.txt)"` retrieves all files ending with `1.txt` in the remote directory and the subdirectory `subDir`.
However, we describe an alternative available after this note.

If you filter a subdirectory, no additional traversal of that subdirectory is performed.

The `-dirs` option is not allowed (the recursive `mget` uses the recursive `ls` to obtain the directory tree and the directories themselves cannot be included in the list).

Typically, you would use the `#remoteDirectory` variable in the `local-directory-expression` so that the remote directory structure is retained locally.
=====

The persistent file list filters now have a boolean property `forRecursion`.
Setting this property to `true`, also sets `alwaysAcceptDirectories`, which means that the recursive operation on the outbound gateways (`ls` and `mget`) will now always traverse the full directory tree each time.
This is to solve a problem where changes deep in the directory tree were not detected.
In addition, `forRecursion=true` causes the full path to files to be used as the metadata store keys; this solves a problem where the filter did not work properly if a file with the same name appears multiple times in different directories.
IMPORTANT: This means that existing keys in a persistent metadata store will not be found for files beneath the top level directory.
For this reason, the property is `false` by default; this may change in a future release.

You can configure the `SmbSimplePatternFileListFilter` and `SmbRegexPatternFileListFilter` to always pass directories by setting the `alwaysAcceptDirectorties` to `true`.
Doing so allows recursion for a simple pattern, as the following examples show:

====
[source, xml]
----
<bean id="starDotTxtFilter"
            class="org.springframework.integration.smb.filters.SmbSimplePatternFileListFilter">
    <constructor-arg value="*.txt" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>

<bean id="dotStarDotTxtFilter"
            class="org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter">
    <constructor-arg value="^.*\.txt$" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>
----
====

You can provide one of these filters by using the `filter` property on the gateway.

See also <<smb-partial>>.

[[smb-put-command]]
==== Using the `put` Command

`put` sends a file to the remote server.
The payload of the message can be a `java.io.File`, a `byte[]`, or a `String`.
A `remote-filename-generator` (or expression) is used to name the remote file.
Other available attributes include `remote-directory`, `temporary-remote-directory` and their `*-expression` equivalents: `use-temporary-file-name` and `auto-create-directory`.
See the https://github.com/spring-projects/spring-integration/tree/main/spring-integration-core/src/main/resources/org/springframework/integration/config[schema documentation] for more information.

The message payload resulting from a `put` operation is a `String` that contains the full path of the file on the server after transfer.

==== Using the `mput` Command

`mput` sends multiple files to the server and supports the following option:

* `-R`: Recursive -- send all files (possibly filtered) in the directory and subdirectories

The message payload must be a `java.io.File` (or `String`) that represents a local directory.
A collection of `File` or `String` is also supported.

The same attributes as the <<smb-put-command,`put` command>> are supported.
In addition, you can filter files in the local directory with one of `mput-pattern`, `mput-regex`, `mput-filter`, or `mput-filter-expression`.
The filter works with recursion, as long as the subdirectories themselves pass the filter.
Subdirectories that do not pass the filter are not recursed.

The message payload resulting from an `mput` operation is a `List<String>` object (that is, a `List` of remote file paths resulting from the transfer).

See also <<smb-partial>>.

==== Using the `rm` Command

The `rm` command has no options.

If the remove operation was successful, the resulting message payload is `Boolean.TRUE`.
Otherwise, the message payload is `Boolean.FALSE`.
The `file_remoteDirectory` header holds the remote directory, and the `file_remoteFile` header holds the file name.

==== Using the `mv` Command

The `mv` command has no options.

The `expression` attribute defines the "`from`" path, and the `rename-expression` attribute defines the "`to`" path.
By default, the `rename-expression` is `headers['file_renameTo']`.
This expression must not evaluate to null or an empty `String`.
If necessary, any remote directories needed are created.
The payload of the result message is `Boolean.TRUE`.
The `file_remoteDirectory` header holds the original remote directory, and the `file_remoteFile` header holds the filename.
The `file_renameTo` header holds the new path.

The `remoteDirectoryExpression` can be used in the `mv` command for convenience.
If the "`from`" file is not a full file path, the result of `remoteDirectoryExpression` is used as the remote directory.
The same applies for the "`to`" file, for example, if the task is just to rename a remote file in some directory.

==== Additional Command Information

The `get` and `mget` commands support the `local-filename-generator-expression` attribute.
It defines a SpEL expression to generate the names of local files during the transfer.
The root object of the evaluation context is the request message.
The `remoteFileName` variable is also available.
It is particularly useful for `mget` (for example: `local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"`).

The `get` and `mget` commands support the `local-directory-expression` attribute.
It defines a SpEL expression to generate the names of local directories during the transfer.
The root object of the evaluation context is the request message.
The `remoteDirectory` variable is also available.
It is particularly useful for mget (for example: `local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.myheader"`).
This attribute is mutually exclusive with the `local-directory` attribute.

For all commands, the 'expression' property of the gateway holds the path on which the command acts.
For the `mget` command, the expression might evaluate to `*`, meaning to retrieve all files, `somedirectory/*`, and other values that end with `*`.

The following example shows a gateway configured for an `ls` command:

====
[source,xml]
----
<int-smb:outbound-gateway id="gateway1"
        session-factory="smbSessionFactory"
        request-channel="inbound1"
        command="ls"
        command-options="-1"
        expression="payload"
        reply-channel="toSplitter"/>
----
====

The payload of the message sent to the `toSplitter` channel is a list of `String` objects, each of which contains the name of a file.
If you omitted `command-options="-1"`, the payload would be a list of `FileInfo` objects.
You can provide options as a space-delimited list (for example, `command-options="-1 -dirs -links"`).

The `GET`, `MGET`, `PUT`, and `MPUT` commands support a `FileExistsMode` property (`mode` when using the namespace support).
This affects the behavior when the local file exists (`GET` and `MGET`) or the remote file exists (`PUT` and `MPUT`).
The supported modes are `REPLACE`, `APPEND`, `FAIL`, and `IGNORE`.
For backwards compatibility, the default mode for `PUT` and `MPUT` operations is `REPLACE`.
For `GET` and `MGET` operations, the default is `FAIL`.

==== Configuring with Java Configuration

The following Spring Boot application shows an example of how to configure the outbound gateway with Java configuration:

====
[source, java]
----
@SpringBootApplication
public class SmbJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SmbJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public SmbSessionFactory smbSessionFactory() {
        SmbSessionFactory smbSession = new SmbSessionFactory();
        smbSession.setHost("myHost");
        smbSession.setPort(445);
        smbSession.setDomain("myDomain");
        smbSession.setUsername("myUser");
        smbSession.setPassword("myPassword");
        smbSession.setShareAndDir("myShareAndDir");
        smbSession.setSmbMinVersion(DialectVersion.SMB210);
        smbSession.setSmbMaxVersion(DialectVersion.SMB311);
        return smbSession;
    }

    @Bean
    @ServiceActivator(inputChannel = "smbChannel")
    public MessageHandler handler() {
        SmbOutboundGateway smbOutboundGateway =
            new SmbOutboundGateway(smbSessionFactory(), "'my_remote_dir/'");
        smbOutboundGateway.setOutputChannelName("replyChannel");
        return smbOutboundGateway;
    }

}
----
====

==== Configuring with the Java DSL

The following Spring Boot application shows an example of how to configure the outbound gateway with the Java DSL:

====
[source, java]
----
@SpringBootApplication
public class SmbJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SmbJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public SmbSessionFactory smbSessionFactory() {
        SmbSessionFactory smbSession = new SmbSessionFactory();
        smbSession.setHost("myHost");
        smbSession.setPort(445);
        smbSession.setDomain("myDomain");
        smbSession.setUsername("myUser");
        smbSession.setPassword("myPassword");
        smbSession.setShareAndDir("myShareAndDir");
        smbSession.setSmbMinVersion(DialectVersion.SMB210);
        smbSession.setSmbMaxVersion(DialectVersion.SMB311);
        return smbSession;
    }

    @Bean
    public SmbOutboundGatewaySpec smbOutboundGateway() {
        return Smb.outboundGateway(smbSessionFactory(),
            AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
            .options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
            .regexFileNameFilter("(subSmbSource|.*.txt)")
            .localDirectoryExpression("'localDirectory/' + #remoteDirectory")
            .localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')");
    }

    @Bean
    public IntegrationFlow smbFlow(AbstractRemoteFileOutboundGateway<SmbFile> smbOutboundGateway) {
        return f -> f
            .handle(smbOutboundGateway)
            .channel(c -> c.queue("remoteFileOutputChannel"));
    }

}
----
====

[[smb-partial]]
==== Outbound Gateway Partial Success (`mget` and `mput`)

When performing operations on multiple files (by using `mget` and `mput`) an exception can occur some time after one or more files have been transferred.
In this case a `PartialSuccessException` is thrown.
As well as the usual `MessagingException` properties (`failedMessage` and `cause`), this exception has two additional properties:

* `partialResults`: The successful transfer results.
* `derivedInput`: The list of files generated from the request message (such as local files to transfer for an `mput`).

These attributes let you determine which files were successfully transferred and which were not.

In the case of a recursive `mput`, the `PartialSuccessException` may have nested `PartialSuccessException` instances.

Consider the following directory structure:

====
[source]
----
root/
|- file1.txt
|- subdir/
   | - file2.txt
   | - file3.txt
|- zoo.txt
----
====

If the exception occurs on `file3.txt`, the `PartialSuccessException` thrown by the gateway has `derivedInput` of `file1.txt`, `subdir`, and `zoo.txt` and `partialResults` of `file1.txt`.
Its `cause` is another `PartialSuccessException` with `derivedInput` of `file2.txt` and `file3.txt` and `partialResults` of `file2.txt`.

[[smb-remote-file-info]]
=== Remote File Information

The `SmbStreamingMessageSource` (<<smb-streaming-inbound>>), `SmbInboundFileSynchronizingMessageSource` (<<smb-inbound>>) and "read"-commands of the `SmbOutboundGateway` (<<smb-outbound-gateway>>) provide additional headers in the message to produce with an information about the remote file:

* `FileHeaders.REMOTE_HOST_PORT` - the host:port pair the remote session has been connected to during file transfer operation;
* `FileHeaders.REMOTE_DIRECTORY` - the remote directory the operation has been performed;
* `FileHeaders.REMOTE_FILE` - the remote file name; applicable only for single file operations.

Since the `SmbInboundFileSynchronizingMessageSource` doesn't produce messages against remote files, but using a local copy, the `AbstractInboundFileSynchronizer` stores an information about remote file in the `MetadataStore` (which can be configured externally) in the URI style (`protocol://host:port/remoteDirectory#remoteFileName`) during synchronization operation.
This metadata is retrieved by the `SmbInboundFileSynchronizingMessageSource` when local file is polled.
When local file is deleted, it is recommended to remove its metadata entry.
The `AbstractInboundFileSynchronizer` provides a `removeRemoteFileMetadata()` callback for this purpose.
In addition, there is a `setMetadataStorePrefix()` to be used in the metadata keys.
It is recommended to have this prefix be different from the one used in the `MetadataStore`-based `FileListFilter` implementations, when the same `MetadataStore` instance is shared between these components, to avoid entry overriding because both filter and `AbstractInboundFileSynchronizer` use the same local file name for the metadata entry key.

