From ac6af716e1cce254db15dbf5f92ec0391ff119e7 Mon Sep 17 00:00:00 2001 From: Gregory Bragg Date: Tue, 10 May 2022 14:40:34 -0400 Subject: [PATCH] Add Java DSL for SMB module * Added supporting classes for DSL, fixed checkstyle build errors * Added JUnit tests for DSL package * Updated Java Doc with instructions to setup an external SMB share * Updated AsciiDoc to include instructions on Java DSL configurations * Updated implementation based on PR review feedback * Clean up the code * Add more info to `whats-new.adoc` for these SMB changes --- .../integration/smb/dsl/Smb.java | 147 +++++++++ .../smb/dsl/SmbInboundChannelAdapterSpec.java | 80 +++++ .../smb/dsl/SmbMessageHandlerSpec.java | 48 +++ .../smb/dsl/SmbOutboundGatewaySpec.java | 55 ++++ ...SmbStreamingInboundChannelAdapterSpec.java | 79 +++++ .../integration/smb/dsl/package-info.java | 4 + .../inbound/SmbStreamingMessageSource.java | 94 ++++++ .../smb/outbound/SmbOutboundGateway.java | 137 +++++++++ .../integration/smb/session/SmbFileInfo.java | 118 ++++++++ .../integration/smb/SmbTestSupport.java | 73 +++++ .../integration/smb/dsl/SmbTests.java | 280 ++++++++++++++++++ src/reference/asciidoc/smb.adoc | 104 +++++++ src/reference/asciidoc/whats-new.adoc | 4 +- 13 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbInboundChannelAdapterSpec.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbMessageHandlerSpec.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbOutboundGatewaySpec.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbStreamingInboundChannelAdapterSpec.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/package-info.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbStreamingMessageSource.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbFileInfo.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbTestSupport.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java 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 new file mode 100644 index 0000000000..59c2f170c4 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/Smb.java @@ -0,0 +1,147 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import java.io.File; +import java.util.Comparator; + +import org.springframework.integration.file.remote.MessageSessionCallback; +import org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; +import org.springframework.integration.smb.outbound.SmbOutboundGateway; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; + +import jcifs.smb.SmbFile; + +/** + * The factory for SMB components. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public final class Smb { + + /** + * A {@link SmbInboundChannelAdapterSpec} factory for an inbound channel adapter spec. + * @param sessionFactory the session factory. + * @return the spec. + */ + public static SmbInboundChannelAdapterSpec inboundAdapter(SessionFactory sessionFactory) { + return inboundAdapter(sessionFactory, null); + } + + /** + * A {@link SmbInboundChannelAdapterSpec} factory for an inbound channel adapter spec. + * @param sessionFactory the session factory. + * @param receptionOrderComparator the comparator. + * @return the spec. + */ + public static SmbInboundChannelAdapterSpec inboundAdapter(SessionFactory sessionFactory, + Comparator receptionOrderComparator) { + + return new SmbInboundChannelAdapterSpec(sessionFactory, receptionOrderComparator); + } + + /** + * A {@link SmbStreamingInboundChannelAdapterSpec} factory for an inbound channel + * adapter spec. + * @param remoteFileTemplate the remote file template. + * @return the spec. + */ + public static SmbStreamingInboundChannelAdapterSpec inboundStreamingAdapter( + RemoteFileTemplate remoteFileTemplate) { + + return inboundStreamingAdapter(remoteFileTemplate, null); + } + + /** + * A {@link SmbStreamingInboundChannelAdapterSpec} factory for an inbound channel + * adapter spec. + * @param remoteFileTemplate the remote file template. + * @param receptionOrderComparator the comparator. + * @return the spec. + */ + public static SmbStreamingInboundChannelAdapterSpec inboundStreamingAdapter( + RemoteFileTemplate remoteFileTemplate, + Comparator receptionOrderComparator) { + + return new SmbStreamingInboundChannelAdapterSpec(remoteFileTemplate, receptionOrderComparator); + } + + /** + * A {@link SmbMessageHandlerSpec} factory for an outbound channel adapter spec. + * @param sessionFactory the session factory. + * @return the spec. + */ + public static SmbMessageHandlerSpec outboundAdapter(SessionFactory sessionFactory) { + return new SmbMessageHandlerSpec(sessionFactory); + } + + /** + * A {@link SmbMessageHandlerSpec} factory for an outbound channel adapter spec. + * @param sessionFactory the session factory. + * @param fileExistsMode the file exists mode. + * @return the spec. + */ + public static SmbMessageHandlerSpec outboundAdapter(SessionFactory sessionFactory, + FileExistsMode fileExistsMode) { + + return outboundAdapter(new SmbRemoteFileTemplate(sessionFactory), fileExistsMode); + } + + /** + * A {@link SmbMessageHandlerSpec} factory for an outbound channel adapter spec. + * @param smbRemoteFileTemplate the remote file template. + * @return the spec. + */ + public static SmbMessageHandlerSpec outboundAdapter(SmbRemoteFileTemplate smbRemoteFileTemplate) { + return new SmbMessageHandlerSpec(smbRemoteFileTemplate); + } + + /** + * A {@link SmbMessageHandlerSpec} factory for an outbound channel adapter spec. + * @param smbRemoteFileTemplate the remote file template. + * @param fileExistsMode the file exists mode. + * @return the spec. + */ + public static SmbMessageHandlerSpec outboundAdapter(SmbRemoteFileTemplate smbRemoteFileTemplate, + FileExistsMode fileExistsMode) { + + return new SmbMessageHandlerSpec(smbRemoteFileTemplate, fileExistsMode); + } + + /** + * Produce a {@link SmbOutboundGatewaySpec} based on the + * {@link MessageSessionCallback}. + * @param sessionFactory the {@link SessionFactory} to connect to. + * @param messageSessionCallback the {@link MessageSessionCallback} to perform SMB. + * operation(s) with the {@code Message} context. + * @return the {@link SmbOutboundGatewaySpec} + * @see MessageSessionCallback + */ + public static SmbOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, + MessageSessionCallback messageSessionCallback) { + + return new SmbOutboundGatewaySpec(new SmbOutboundGateway(sessionFactory, messageSessionCallback)); + } + + private Smb() { + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbInboundChannelAdapterSpec.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbInboundChannelAdapterSpec.java new file mode 100644 index 0000000000..ea47a68b03 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbInboundChannelAdapterSpec.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import java.io.File; +import java.util.Comparator; + +import org.springframework.integration.file.dsl.RemoteFileInboundChannelAdapterSpec; +import org.springframework.integration.file.filters.CompositeFileListFilter; +import org.springframework.integration.file.filters.FileListFilter; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.metadata.SimpleMetadataStore; +import org.springframework.integration.smb.filters.SmbPersistentAcceptOnceFileListFilter; +import org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter; +import org.springframework.integration.smb.filters.SmbSimplePatternFileListFilter; +import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizer; +import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizingMessageSource; + +import jcifs.smb.SmbFile; + +/** + * A {@link RemoteFileInboundChannelAdapterSpec} for an {@link SmbInboundFileSynchronizingMessageSource}. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbInboundChannelAdapterSpec + extends RemoteFileInboundChannelAdapterSpec { + + protected SmbInboundChannelAdapterSpec(SessionFactory sessionFactory, Comparator comparator) { + super(new SmbInboundFileSynchronizer(sessionFactory)); + this.target = new SmbInboundFileSynchronizingMessageSource(this.synchronizer, comparator); + } + + /** + * Specify a simple pattern to match remote files. + * @param pattern the pattern. + * @see SmbSimplePatternFileListFilter + * @see #filter(org.springframework.integration.file.filters.FileListFilter) + */ + @Override + public SmbInboundChannelAdapterSpec patternFilter(String pattern) { + return filter(composeFilters(new SmbSimplePatternFileListFilter(pattern))); + } + + /** + * Specify a regular expression to match remote files. + * @param regex the expression. + * @see SmbRegexPatternFileListFilter + * @see #filter(org.springframework.integration.file.filters.FileListFilter) + */ + @Override + public SmbInboundChannelAdapterSpec regexFilter(String regex) { + return filter(composeFilters(new SmbRegexPatternFileListFilter(regex))); + } + + private CompositeFileListFilter composeFilters(FileListFilter fileListFilter) { + CompositeFileListFilter compositeFileListFilter = new CompositeFileListFilter<>(); + compositeFileListFilter.addFilters(fileListFilter, + new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbMessageSource")); + return compositeFileListFilter; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbMessageHandlerSpec.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbMessageHandlerSpec.java new file mode 100644 index 0000000000..fe91985a1a --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbMessageHandlerSpec.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import org.springframework.integration.file.dsl.FileTransferringMessageHandlerSpec; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; +import org.springframework.integration.smb.outbound.SmbMessageHandler; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; + +import jcifs.smb.SmbFile; + +/** + * A {@link FileTransferringMessageHandlerSpec} for SMB. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbMessageHandlerSpec extends FileTransferringMessageHandlerSpec { + + protected SmbMessageHandlerSpec(SessionFactory sessionFactory) { + this.target = new SmbMessageHandler(sessionFactory); + } + + protected SmbMessageHandlerSpec(SmbRemoteFileTemplate smbRemoteFileTemplate) { + this.target = new SmbMessageHandler(smbRemoteFileTemplate); + } + + protected SmbMessageHandlerSpec(SmbRemoteFileTemplate smbRemoteFileTemplate, FileExistsMode fileExistsMode) { + this.target = new SmbMessageHandler(smbRemoteFileTemplate, fileExistsMode); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbOutboundGatewaySpec.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbOutboundGatewaySpec.java new file mode 100644 index 0000000000..97a96ac8a9 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbOutboundGatewaySpec.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import org.springframework.integration.file.dsl.RemoteFileOutboundGatewaySpec; +import org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter; +import org.springframework.integration.smb.filters.SmbSimplePatternFileListFilter; +import org.springframework.integration.smb.outbound.SmbOutboundGateway; + +import jcifs.smb.SmbFile; + +/** + * A {@link RemoteFileOutboundGatewaySpec} for SMB. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbOutboundGatewaySpec extends RemoteFileOutboundGatewaySpec { + + protected SmbOutboundGatewaySpec(SmbOutboundGateway outboundGateway) { + super(outboundGateway); + } + + /** + * @see SmbSimplePatternFileListFilter + */ + @Override + public SmbOutboundGatewaySpec patternFileNameFilter(String pattern) { + return filter(new SmbSimplePatternFileListFilter(pattern)); + } + + /** + * @see SmbRegexPatternFileListFilter + */ + @Override + public SmbOutboundGatewaySpec regexFileNameFilter(String regex) { + return filter(new SmbRegexPatternFileListFilter(regex)); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbStreamingInboundChannelAdapterSpec.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbStreamingInboundChannelAdapterSpec.java new file mode 100644 index 0000000000..07619a8c58 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/SmbStreamingInboundChannelAdapterSpec.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import java.util.Comparator; + +import org.springframework.integration.file.dsl.RemoteFileStreamingInboundChannelAdapterSpec; +import org.springframework.integration.file.filters.CompositeFileListFilter; +import org.springframework.integration.file.filters.FileListFilter; +import org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.metadata.SimpleMetadataStore; +import org.springframework.integration.smb.filters.SmbPersistentAcceptOnceFileListFilter; +import org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter; +import org.springframework.integration.smb.filters.SmbSimplePatternFileListFilter; +import org.springframework.integration.smb.inbound.SmbStreamingMessageSource; + +import jcifs.smb.SmbFile; + +/** + * A {@link RemoteFileStreamingInboundChannelAdapterSpec} for a {@link SmbStreamingMessageSource}. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbStreamingInboundChannelAdapterSpec + extends RemoteFileStreamingInboundChannelAdapterSpec { + + protected SmbStreamingInboundChannelAdapterSpec(RemoteFileTemplate remoteFileTemplate, + Comparator comparator) { + + this.target = new SmbStreamingMessageSource(remoteFileTemplate, comparator); + } + + /** + * Specify a simple pattern to match remote files (e.g. '*.txt'). + * @param pattern the pattern. + * @see SmbSimplePatternFileListFilter + * @see #filter(org.springframework.integration.file.filters.FileListFilter) + */ + @Override + public SmbStreamingInboundChannelAdapterSpec patternFilter(String pattern) { + return filter(composeFilters(new SmbSimplePatternFileListFilter(pattern))); + } + + /** + * Specify a regular expression to match remote files (e.g. '[0-9].*.txt'). + * @param regex the expression. + * @see SmbRegexPatternFileListFilter + * @see #filter(org.springframework.integration.file.filters.FileListFilter) + */ + @Override + public SmbStreamingInboundChannelAdapterSpec regexFilter(String regex) { + return filter(composeFilters(new SmbRegexPatternFileListFilter(regex))); + } + + private CompositeFileListFilter composeFilters(FileListFilter fileListFilter) { + CompositeFileListFilter compositeFileListFilter = new CompositeFileListFilter<>(); + compositeFileListFilter.addFilters(fileListFilter, + new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbStreamingMessageSource")); + return compositeFileListFilter; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/package-info.java new file mode 100644 index 0000000000..58576b42ef --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides SMB Components for the Java DSL. + */ +package org.springframework.integration.smb.dsl; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbStreamingMessageSource.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbStreamingMessageSource.java new file mode 100644 index 0000000000..54e0f808df --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbStreamingMessageSource.java @@ -0,0 +1,94 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.inbound; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.file.remote.AbstractFileInfo; +import org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource; +import org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.metadata.SimpleMetadataStore; +import org.springframework.integration.smb.filters.SmbPersistentAcceptOnceFileListFilter; +import org.springframework.integration.smb.session.SmbFileInfo; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * Message source for streaming SMB remote file contents. + * + * @author Gregory Bragg + * + * @since 6.0 + * + */ +public class SmbStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource { + + private static final Log logger = LogFactory.getLog(SmbStreamingMessageSource.class); + + /** + * Construct an instance with the supplied template. + * @param template the template. + */ + public SmbStreamingMessageSource(RemoteFileTemplate template) { + this(template, null); + } + + /** + * Construct an instance with the supplied template and comparator. + * Note: the comparator is applied each time the remote directory is listed + * which only occurs when the previous list is exhausted. + * @param template the template. + * @param comparator the comparator. + */ + public SmbStreamingMessageSource(RemoteFileTemplate template, Comparator comparator) { + super(template, comparator); + doSetFilter(new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbStreamingMessageSource")); + } + + @Override + public String getComponentType() { + return "smb:inbound-streaming-channel-adapter"; + } + + @Override + protected List> asFileInfoList(Collection files) { + List> canonicalFiles = new ArrayList<>(); + for (SmbFile file : files) { + canonicalFiles.add(new SmbFileInfo(file)); + } + return canonicalFiles; + } + + @Override + protected boolean isDirectory(SmbFile file) { + try { + return file != null && file.isDirectory(); + } + catch (SmbException se) { + logger.error("Unable to determine if this SmbFile represents a directory", se); + return false; + } + } + +} 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 new file mode 100644 index 0000000000..75a99c5d07 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbOutboundGateway.java @@ -0,0 +1,137 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.outbound; + +import java.net.MalformedURLException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.file.remote.AbstractFileInfo; +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.smb.session.SmbFileInfo; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * Outbound Gateway for performing remote file operations via SMB. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbOutboundGateway extends AbstractRemoteFileOutboundGateway { + + private static final Log logger = LogFactory.getLog(SmbOutboundGateway.class); + + /** + * Construct an instance using the provided session factory and callback for + * performing operations on the session. + * @param sessionFactory the session factory. + * @param messageSessionCallback the callback. + */ + public SmbOutboundGateway(SessionFactory sessionFactory, + MessageSessionCallback messageSessionCallback) { + + this(new SmbRemoteFileTemplate(sessionFactory), messageSessionCallback); + remoteFileTemplateExplicitlySet(false); + } + + /** + * Construct an instance with the supplied remote file template and callback + * for performing operations on the session. + * @param remoteFileTemplate the remote file template. + * @param messageSessionCallback the callback. + */ + public SmbOutboundGateway(RemoteFileTemplate remoteFileTemplate, + MessageSessionCallback messageSessionCallback) { + + super(remoteFileTemplate, messageSessionCallback); + } + + @Override + public String getComponentType() { + return "smb:outbound-gateway"; + } + + @Override + protected boolean isDirectory(SmbFile file) { + try { + return file.isDirectory(); + } + catch (SmbException se) { + logger.error("Unable to determine if this SmbFile represents a directory", se); + return false; + } + } + + /** + * Symbolic links are currently not supported in the JCIFS v2.x.x + * dependent library, so this method will always return false. + * @return false + */ + @Override + protected boolean isLink(SmbFile file) { + return false; + } + + @Override + protected String getFilename(SmbFile file) { + return file.getName(); + } + + @Override + protected String getFilename(AbstractFileInfo file) { + return file.getFilename(); + } + + @Override + protected long getModified(SmbFile file) { + return file.getLastModified(); + } + + @Override + protected List> asFileInfoList(Collection files) { + List> canonicalFiles = new ArrayList<>(); + for (SmbFile file : files) { + canonicalFiles.add(new SmbFileInfo(file)); + } + return canonicalFiles; + } + + @Override + protected SmbFile enhanceNameWithSubDirectory(SmbFile file, String directory) { + try { + file.renameTo(new SmbFile(file, directory), true); + return file; + } + catch (SmbException | MalformedURLException | UnknownHostException e) { + logger.error("Unable to enhance file name with a sub directory path", e); + return null; + } + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbFileInfo.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbFileInfo.java new file mode 100644 index 0000000000..34b146df57 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbFileInfo.java @@ -0,0 +1,118 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.session; + +import java.io.IOException; +import java.util.Arrays; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.file.remote.AbstractFileInfo; +import org.springframework.util.Assert; + +import jcifs.internal.dtyp.ACE; +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * A {@link org.springframework.integration.file.remote.FileInfo} implementation for + * SMB. + * + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbFileInfo extends AbstractFileInfo { + + private static final Log logger = LogFactory.getLog(SmbFileInfo.class); + + private final SmbFile smbFile; + + public SmbFileInfo(SmbFile smbFile) { + Assert.notNull(smbFile, "SmbFile must not be null"); + this.smbFile = smbFile; + } + + @Override + public boolean isDirectory() { + try { + return this.smbFile.isDirectory(); + } + catch (SmbException se) { + logger.error("Unable to determine if this SmbFile represents a directory", se); + return false; + } + } + + /** + * Symbolic links are currently not supported in the JCIFS v2.x.x + * dependent library, so this method will always return false. + * @return false + */ + @Override + public boolean isLink() { + return false; + } + + @Override + public long getSize() { + try { + return this.smbFile.length(); + } + catch (SmbException se) { + logger.error("Unable to determine file size", se); + return 0L; + } + } + + @Override + public long getModified() { + return this.smbFile.getLastModified(); + } + + @Override + public String getFilename() { + return this.smbFile.getName(); + } + + /** + * An Access Control Entry (ACE) is an element in a security descriptor + * such as those associated with files and directories. The Windows OS + * determines which users have the necessary permissions to access objects + * based on these entries. + * @return a list of Access Control Entry (ACE) objects representing + * the security descriptor associated with this file or directory. + */ + @Override + public String getPermissions() { + ACE[] aceArray = null; + try { + aceArray = this.smbFile.getSecurity(true); + } + catch (IOException se) { + logger.error("Unable to determine security descriptor information for this SmbFile", se); + } + return (aceArray == null) ? null : Arrays.toString(aceArray); + } + + @Override + public SmbFile getFileInfo() { + return this.smbFile; + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbTestSupport.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbTestSupport.java new file mode 100644 index 0000000000..6c68ab6234 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbTestSupport.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; + +import org.springframework.integration.file.remote.RemoteFileTestSupport; +import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.smb.session.SmbSessionFactory; + +import jcifs.DialectVersion; +import jcifs.smb.SmbFile; + +/** + * Provides a connection to an external SMB Server for test cases. + * + * The constants need to be updated with the 'real' server settings for testing. + * + * @author Gregory Bragg + * + * @since 6.0 + */ + +@Disabled("Actual SMB share must be configured in class [SmbTestSupport].") +public class SmbTestSupport extends RemoteFileTestSupport { + + public static final String HOST = "localhost"; + + public static final String SHARE_AND_DIR = "smb-share/"; + + public static final String USERNAME = "sambaguest"; + + public static final String PASSWORD = "sambaguest"; + + private static SmbSessionFactory smbSessionFactory; + + @BeforeAll + public static void connectToSMBServer() { + smbSessionFactory = new SmbSessionFactory(); + smbSessionFactory.setHost(HOST); + smbSessionFactory.setUsername(USERNAME); + smbSessionFactory.setPassword(PASSWORD); + smbSessionFactory.setShareAndDir(SHARE_AND_DIR); + smbSessionFactory.setSmbMinVersion(DialectVersion.SMB210); + smbSessionFactory.setSmbMaxVersion(DialectVersion.SMB311); + } + + public static SessionFactory sessionFactory() { + return new CachingSessionFactory<>(smbSessionFactory); + } + + @Override + protected String prefix() { + return "smb"; + } + +} 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 new file mode 100644 index 0000000000..2f24a3a7e2 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/dsl/SmbTests.java @@ -0,0 +1,280 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.dsl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.EnableIntegrationManagement; +import org.springframework.integration.config.IntegrationManagementConfigurer; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.Pollers; +import org.springframework.integration.dsl.StandardIntegrationFlow; +import org.springframework.integration.dsl.context.IntegrationFlowContext; +import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration; +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.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.SmbRemoteFileTemplate; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.Message; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * The actual SMB share must be configured in class 'SmbTestSupport' + * with the 'real' server settings for testing. + * + * You must create the following folder structures in your SMB share + * for a successful completion of these unit tests: + * + *
+ *  smbSource/
+ *  |-- smbSource1.txt - contains 'source1'
+ *  |-- smbSource2.txt - contains 'source2'
+ *  |-- SMBSOURCE1.TXT.a
+ *  |-- SMBSOURCE2.TXT.a
+ *  |-- subSmbSource/
+ *      |-- subSmbSource1.txt - contains 'subSource1'
+ *  smbTarget/
+ * 
+ * + * The intent is tests retrieve from smbSource and verify arrival in localTarget or + * send from localSource and verify arrival in remoteTarget. + * + * @author Gregory Bragg + * + * @since 6.0 + */ + +@SpringJUnitConfig +@DirtiesContext +@Disabled("Actual SMB share must be configured in class [SmbTestSupport].") +public class SmbTests extends SmbTestSupport { + + @Autowired + private IntegrationFlowContext flowContext; + + @Autowired + private ApplicationContext context; + + @Autowired + private IntegrationManagementConfigurer integrationManagementConfigurer; + + @Test + public void testSmbInboundFlow() throws IOException { + QueueChannel out = new QueueChannel(); + DirectoryScanner scanner = new DefaultDirectoryScanner(); + IntegrationFlow flow = IntegrationFlows.from(Smb.inboundAdapter(sessionFactory()) + .preserveTimestamp(true) + .remoteDirectory("smbSource") + .maxFetchSize(10) + .scanner(scanner) + .regexFilter(".*\\.txt$") + .localFilename(f -> f.toUpperCase() + ".a") + .localDirectory(getTargetLocalDirectory()), + e -> e.id("smbInboundAdapter").poller(Pollers.fixedDelay(100))) + .channel(out) + .get(); + IntegrationFlowRegistration registration = this.flowContext.registration(flow).register(); + Map components = + TestUtils.getPropertyValue(registration, "integrationFlow.integrationComponents", Map.class); + Iterator iterator = components.keySet().iterator(); + iterator.next(); + Object spcafb = iterator.next(); + assertThat(TestUtils.getPropertyValue(spcafb, "source.fileSource.scanner")).isSameAs(scanner); + Message message = out.receive(10_000); + assertThat(message).isNotNull(); + assertThat(message.getHeaders()) + .containsKeys(FileHeaders.REMOTE_HOST_PORT, FileHeaders.REMOTE_DIRECTORY, FileHeaders.REMOTE_FILE); + Object payload = message.getPayload(); + assertThat(payload).isInstanceOf(File.class); + File file = (File) payload; + assertThat(file.getName()).isIn("SMBSOURCE1.TXT.a", "SMBSOURCE2.TXT.a"); + assertThat(file.getAbsolutePath()).contains("localTarget"); + + message = out.receive(10_000); + assertThat(message).isNotNull(); + file = (File) message.getPayload(); + assertThat(file.getName()).isIn("SMBSOURCE1.TXT.a", "SMBSOURCE2.TXT.a"); + assertThat(file.getAbsolutePath()).contains("localTarget"); + + assertThat(out.receive(10)).isNull(); + + MessageSource source = context.getBean(SmbInboundFileSynchronizingMessageSource.class); + assertThat(TestUtils.getPropertyValue(source, "maxFetchSize")).isEqualTo(10); + + registration.destroy(); + } + + @Test + public void testSmbInboundStreamFlow() throws Exception { + QueueChannel out = new QueueChannel(); + StandardIntegrationFlow flow = IntegrationFlows.from( + Smb.inboundStreamingAdapter(new SmbRemoteFileTemplate(sessionFactory())) + .remoteDirectory("smbSource") + .maxFetchSize(11) + .regexFilter(".*\\.txt$"), + e -> e.id("smbInboundAdapter").poller(Pollers.fixedDelay(100))) + .channel(out) + .get(); + IntegrationFlowRegistration registration = this.flowContext.registration(flow).register(); + Message message = out.receive(10_000); + assertThat(message).isNotNull(); + assertThat(message.getPayload()).isInstanceOf(InputStream.class); + assertThat(message.getHeaders().get(FileHeaders.REMOTE_FILE)).isIn("smbSource1.txt", "smbSource2.txt"); + assertThat(message.getHeaders().get( + FileHeaders.REMOTE_HOST_PORT, String.class)).contains(sessionFactory().getSession().getHostPort()); + new IntegrationMessageHeaderAccessor(message).getCloseableResource().close(); + + message = out.receive(10_000); + assertThat(message).isNotNull(); + assertThat(message.getPayload()).isInstanceOf(InputStream.class); + assertThat(message.getHeaders().get(FileHeaders.REMOTE_FILE)).isIn("smbSource1.txt", "smbSource2.txt"); + new IntegrationMessageHeaderAccessor(message).getCloseableResource().close(); + + MessageSource source = context.getBean(SmbStreamingMessageSource.class); + assertThat(TestUtils.getPropertyValue(source, "maxFetchSize")).isEqualTo(11); + registration.destroy(); + } + + @Test + public void testSmbOutboundFlow() { + IntegrationFlow flow = f -> f + .handle(Smb.outboundAdapter(sessionFactory(), FileExistsMode.REPLACE) + .useTemporaryFileName(false) + .fileNameExpression("headers['" + FileHeaders.FILENAME + "']") + .remoteDirectory("smbTarget")); + IntegrationFlowRegistration registration = this.flowContext.registration(flow).register(); + String fileName = "foo.file"; + Message message = MessageBuilder + .withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8))) + .setHeader(FileHeaders.FILENAME, fileName) + .build(); + registration.getInputChannel().send(message); + RemoteFileTemplate template = new RemoteFileTemplate<>(sessionFactory()); + SmbFile[] files = template.execute(session -> + session.list(getTargetRemoteDirectory().getName())); + assertThat(files.length).isEqualTo(1); + try { + assertThat(files[0].length()).isEqualTo(3); + } + catch (SmbException se) { + se.printStackTrace(); + } + + registration.destroy(); + } + + @Test + public void testSmbOutboundFlowWithSmbRemoteTemplate() { + + SmbRemoteFileTemplate smbTemplate = new SmbRemoteFileTemplate(sessionFactory()); + IntegrationFlow flow = f -> f + .handle(Smb.outboundAdapter(smbTemplate) + .useTemporaryFileName(false) + .fileNameExpression("headers['" + FileHeaders.FILENAME + "']") + .remoteDirectory("smbTarget")); + IntegrationFlowRegistration registration = this.flowContext.registration(flow).register(); + String fileName = "foo.file"; + Message message = MessageBuilder + .withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8))) + .setHeader(FileHeaders.FILENAME, fileName) + .build(); + registration.getInputChannel().send(message); + SmbFile[] files = smbTemplate.execute(session -> + session.list(getTargetRemoteDirectory().getName())); + assertThat(files.length).isEqualTo(1); + try { + assertThat(files[0].length()).isEqualTo(3); + } + catch (SmbException se) { + se.printStackTrace(); + } + + registration.destroy(); + } + + @Test + public void testSmbOutboundFlowWithSmbRemoteTemplateAndMode() { + + SmbRemoteFileTemplate smbTemplate = new SmbRemoteFileTemplate(sessionFactory()); + IntegrationFlow flow = f -> f + .handle(Smb.outboundAdapter(smbTemplate, FileExistsMode.APPEND) + .useTemporaryFileName(false) + .fileNameExpression("headers['" + FileHeaders.FILENAME + "']") + .remoteDirectory("smbTarget")); + IntegrationFlowRegistration registration = this.flowContext.registration(flow).register(); + String fileName = "foo.file"; + Message message1 = MessageBuilder + .withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8))) + .setHeader(FileHeaders.FILENAME, fileName) + .build(); + Message message2 = MessageBuilder + .withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8))) + .setHeader(FileHeaders.FILENAME, fileName) + .build(); + registration.getInputChannel().send(message1); + registration.getInputChannel().send(message2); + SmbFile[] files = smbTemplate.execute(session -> + session.list(getTargetRemoteDirectory().getName())); + assertThat(files.length).isEqualTo(1); + try { + assertThat(files[0].length()).isEqualTo(9); + } + catch (SmbException se) { + se.printStackTrace(); + } + + registration.destroy(); + } + + @Configuration + @EnableIntegration + @EnableIntegrationManagement + public static class ContextConfiguration { + + } + +} diff --git a/src/reference/asciidoc/smb.adoc b/src/reference/asciidoc/smb.adoc index 0f5eb611e3..da3b246b91 100644 --- a/src/reference/asciidoc/smb.adoc +++ b/src/reference/asciidoc/smb.adoc @@ -122,6 +122,55 @@ public MessageSource smbMessageSource() { For XML configuration the `` 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 IntegrationFlows + .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-outbound]] === SMB Outbound Channel Adapter @@ -142,3 +191,58 @@ public MessageHandler smbMessageHandler(SmbSessionFactory smbSessionFactory) { 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 IntegrationFlows.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); + } + +} +---- +==== diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index cc819f93b5..c4ebff4113 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -26,7 +26,9 @@ See <<./graphql.adoc#graphql,GraphQL Support>> for more information. [[x6.0-smb]] === SMB Support -SMB support has been added from the Spring Integration Extensions project. +SMB support has been added from the Spring Integration Extensions project, including support for Java . +The Java DSL (see `org.springframework.integration.smb.dsl.Smb` factory) also has been added to this module. +An `SmbStreamingMessageSource` and `SmbOutboundGateway` implementation are introduced. See <<./smb.adoc#smb,SMB Support>> for more information. [[x6.0-general]]