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
This commit is contained in:
Gregory Bragg
2022-05-10 14:40:34 -04:00
committed by Artem Bilan
parent 1790f235b7
commit ac6af716e1
13 changed files with 1222 additions and 1 deletions

View File

@@ -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<SmbFile> 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<SmbFile> sessionFactory,
Comparator<File> 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<SmbFile> 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<SmbFile> remoteFileTemplate,
Comparator<SmbFile> 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<SmbFile> 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<SmbFile> 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<SmbFile> sessionFactory,
MessageSessionCallback<SmbFile, ?> messageSessionCallback) {
return new SmbOutboundGatewaySpec(new SmbOutboundGateway(sessionFactory, messageSessionCallback));
}
private Smb() {
}
}

View File

@@ -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<SmbFile, SmbInboundChannelAdapterSpec,
SmbInboundFileSynchronizingMessageSource> {
protected SmbInboundChannelAdapterSpec(SessionFactory<SmbFile> sessionFactory, Comparator<File> 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<SmbFile> composeFilters(FileListFilter<SmbFile> fileListFilter) {
CompositeFileListFilter<SmbFile> compositeFileListFilter = new CompositeFileListFilter<>();
compositeFileListFilter.addFilters(fileListFilter,
new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbMessageSource"));
return compositeFileListFilter;
}
}

View File

@@ -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<SmbFile, SmbMessageHandlerSpec> {
protected SmbMessageHandlerSpec(SessionFactory<SmbFile> 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);
}
}

View File

@@ -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<SmbFile, SmbOutboundGatewaySpec> {
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));
}
}

View File

@@ -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<SmbFile, SmbStreamingInboundChannelAdapterSpec,
SmbStreamingMessageSource> {
protected SmbStreamingInboundChannelAdapterSpec(RemoteFileTemplate<SmbFile> remoteFileTemplate,
Comparator<SmbFile> 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<SmbFile> composeFilters(FileListFilter<SmbFile> fileListFilter) {
CompositeFileListFilter<SmbFile> compositeFileListFilter = new CompositeFileListFilter<>();
compositeFileListFilter.addFilters(fileListFilter,
new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbStreamingMessageSource"));
return compositeFileListFilter;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides SMB Components for the Java DSL.
*/
package org.springframework.integration.smb.dsl;

View File

@@ -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<SmbFile> {
private static final Log logger = LogFactory.getLog(SmbStreamingMessageSource.class);
/**
* Construct an instance with the supplied template.
* @param template the template.
*/
public SmbStreamingMessageSource(RemoteFileTemplate<SmbFile> 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<SmbFile> template, Comparator<SmbFile> comparator) {
super(template, comparator);
doSetFilter(new SmbPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "smbStreamingMessageSource"));
}
@Override
public String getComponentType() {
return "smb:inbound-streaming-channel-adapter";
}
@Override
protected List<AbstractFileInfo<SmbFile>> asFileInfoList(Collection<SmbFile> files) {
List<AbstractFileInfo<SmbFile>> 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;
}
}
}

View File

@@ -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<SmbFile> {
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<SmbFile> sessionFactory,
MessageSessionCallback<SmbFile, ?> 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<SmbFile> remoteFileTemplate,
MessageSessionCallback<SmbFile, ?> 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<SmbFile> file) {
return file.getFilename();
}
@Override
protected long getModified(SmbFile file) {
return file.getLastModified();
}
@Override
protected List<AbstractFileInfo<SmbFile>> asFileInfoList(Collection<SmbFile> files) {
List<AbstractFileInfo<SmbFile>> 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;
}
}
}

View File

@@ -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<SmbFile> {
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;
}
}

View File

@@ -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<SmbFile> sessionFactory() {
return new CachingSessionFactory<>(smbSessionFactory);
}
@Override
protected String prefix() {
return "smb";
}
}

View File

@@ -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:
*
* <pre class="code">
* smbSource/
* |-- smbSource1.txt - contains 'source1'
* |-- smbSource2.txt - contains 'source2'
* |-- SMBSOURCE1.TXT.a
* |-- SMBSOURCE2.TXT.a
* |-- subSmbSource/
* |-- subSmbSource1.txt - contains 'subSource1'
* smbTarget/
* </pre>
*
* 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<ByteArrayInputStream> message = MessageBuilder
.withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)))
.setHeader(FileHeaders.FILENAME, fileName)
.build();
registration.getInputChannel().send(message);
RemoteFileTemplate<SmbFile> 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<ByteArrayInputStream> 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<ByteArrayInputStream> message1 = MessageBuilder
.withPayload(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)))
.setHeader(FileHeaders.FILENAME, fileName)
.build();
Message<ByteArrayInputStream> 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 {
}
}

View File

@@ -122,6 +122,55 @@ public MessageSource<File> smbMessageSource() {
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 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);
}
}
----
====

View File

@@ -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]]