From 7ad71d38d92a58d92690e3745a18ff5c4150bc3a Mon Sep 17 00:00:00 2001 From: Gregory Bragg Date: Thu, 5 May 2022 15:47:41 -0400 Subject: [PATCH] Migrate SMB extension project to respective module * Removed deprecated replaceFile and useTempFile session configs * Refactored to use AssertJ instead of JUnit asserts as per PR feedback * Code clean up for SMB module --- build.gradle | 11 + .../SmbInboundChannelAdapterParser.java | 65 +++ .../smb/config/SmbNamespaceHandler.java | 36 ++ .../SmbOutboundChannelAdapterParser.java | 37 ++ .../integration/smb/config/package-info.java | 4 + ...SmbPersistentAcceptOnceFileListFilter.java | 48 ++ .../SmbRegexPatternFileListFilter.java | 66 +++ .../SmbSimplePatternFileListFilter.java | 62 +++ ...SystemMarkerFilePresentFileListFilter.java | 59 +++ .../integration/smb/filters/package-info.java | 4 + .../inbound/SmbInboundFileSynchronizer.java | 69 +++ ...InboundFileSynchronizingMessageSource.java | 50 ++ .../integration/smb/inbound/package-info.java | 4 + .../smb/outbound/SmbMessageHandler.java | 47 ++ .../smb/outbound/package-info.java | 4 + .../integration/smb/session/SmbConfig.java | 225 ++++++++ .../smb/session/SmbRemoteFileTemplate.java | 41 ++ .../integration/smb/session/SmbSession.java | 488 ++++++++++++++++++ .../smb/session/SmbSessionFactory.java | 93 ++++ .../integration/smb/session/SmbShare.java | 146 ++++++ .../integration/smb/session/package-info.java | 4 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 4 + .../main/resources/META-INF/spring.tooling | 4 + .../smb/config/spring-integration-smb.gif | Bin 0 -> 539 bytes .../smb/config/spring-integration-smb.xsd | 269 ++++++++++ .../integration/smb/AbstractBaseTests.java | 276 ++++++++++ .../springframework/integration/smb/Main.java | 148 ++++++ .../smb/SmbMessageHistoryTests-context.xml | 35 ++ .../smb/SmbMessageHistoryTests.java | 55 ++ .../smb/SmbParserInboundTests-context.xml | 51 ++ .../SmbParserInboundTests-fail-context.xml | 39 ++ .../smb/SmbParserInboundTests.java | 66 +++ ...boundChannelAdapterParserTests-context.xml | 68 +++ .../SmbInboundChannelAdapterParserTests.java | 140 +++++ ...SmbInboundChannelAdapterSample-context.xml | 38 ++ .../smb/config/SmbInboundOutboundSample.java | 142 +++++ ...boundChannelAdapterParserTests-context.xml | 50 ++ .../SmbOutboundChannelAdapterParserTests.java | 89 ++++ ...mbOutboundChannelAdapterSample-context.xml | 26 + ...oundRemoteFileSystemSynchronizerTests.java | 132 +++++ .../SmbSendingMessageHandlerTests.java | 170 ++++++ ...SmbSessionFactoryWithCIFSContextTests.java | 157 ++++++ .../smb/session/SmbSessionTests.java | 204 ++++++++ .../spring-integration-context.xml | 43 ++ .../src/test/resources/log4j2-test.xml | 15 + 46 files changed, 3785 insertions(+) create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParser.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbNamespaceHandler.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParser.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/config/package-info.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbPersistentAcceptOnceFileListFilter.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbRegexPatternFileListFilter.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSimplePatternFileListFilter.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSystemMarkerFilePresentFileListFilter.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/package-info.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizer.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizingMessageSource.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/package-info.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbMessageHandler.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/package-info.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbConfig.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbRemoteFileTemplate.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSessionFactory.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbShare.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/session/package-info.java create mode 100644 spring-integration-smb/src/main/resources/META-INF/spring.handlers create mode 100644 spring-integration-smb/src/main/resources/META-INF/spring.schemas create mode 100644 spring-integration-smb/src/main/resources/META-INF/spring.tooling create mode 100644 spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.gif create mode 100644 spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.xsd create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/Main.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-fail-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterSample-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundOutboundSample.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterSample-context.xml create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionFactoryWithCIFSContextTests.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionTests.java create mode 100644 spring-integration-smb/src/test/resources/META-INF/spring/integration/spring-integration-context.xml create mode 100644 spring-integration-smb/src/test/resources/log4j2-test.xml diff --git a/build.gradle b/build.gradle index 56e85dbffd..a716882593 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,7 @@ ext { h2Version = '2.1.210' jacksonVersion = '2.13.2' jaxbVersion = '3.0.2' + jcifsVersion = '2.1.29' jeroMqVersion = '0.5.2' jmsApiVersion = '3.0.0' jpaApiVersion = '3.0.2' @@ -874,6 +875,16 @@ project('spring-integration-sftp') { } } +project('spring-integration-smb') { + description = 'Spring Integration SMB Support' + dependencies { + api project(':spring-integration-file') + api "org.codelibs:jcifs:$jcifsVersion" + + testImplementation project(':spring-integration-file').sourceSets.test.output + } +} + project('spring-integration-stomp') { description = 'Spring Integration STOMP Support' dependencies { diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParser.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParser.java new file mode 100644 index 0000000000..f211993f17 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParser.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-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.config; + +import org.springframework.integration.file.config.AbstractRemoteFileInboundChannelAdapterParser; +import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter; +import org.springframework.integration.file.filters.FileListFilter; +import org.springframework.integration.file.remote.synchronizer.InboundFileSynchronizer; +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; + +/** + * Parser for the SMB 'inbound-channel-adapter' element. + * + * @author Markus Spann + * @author Artem Bilan + * @author Prafull Kumar Soni + * + * @since 6.0 + */ +public class SmbInboundChannelAdapterParser extends AbstractRemoteFileInboundChannelAdapterParser { + + @Override + protected String getMessageSourceClassname() { + return SmbInboundFileSynchronizingMessageSource.class.getName(); + } + + @Override + protected Class getInboundFileSynchronizerClass() { + return SmbInboundFileSynchronizer.class; + } + + @Override + protected Class> getSimplePatternFileListFilterClass() { + return SmbSimplePatternFileListFilter.class; + } + + @Override + protected Class> getRegexPatternFileListFilterClass() { + return SmbRegexPatternFileListFilter.class; + } + + @Override + protected Class> getPersistentAcceptOnceFileListFilterClass() { + return SmbPersistentAcceptOnceFileListFilter.class; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbNamespaceHandler.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbNamespaceHandler.java new file mode 100644 index 0000000000..fd28a2e826 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbNamespaceHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-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.config; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * Provides namespace support for using SMB. + * + * @author Markus Spann + * @author Artem Bilan + * + * @since 6.0 + */ +public class SmbNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + public void init() { + registerBeanDefinitionParser("inbound-channel-adapter", new SmbInboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new SmbOutboundChannelAdapterParser()); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParser.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParser.java new file mode 100644 index 0000000000..d0624fabde --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017-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.config; + +import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; +import org.springframework.integration.file.remote.RemoteFileOperations; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; + +/** + * The parser for {@code }. + * + * @author Artem Bilan + * + * @since 6.0 + */ +public class SmbOutboundChannelAdapterParser extends RemoteFileOutboundChannelAdapterParser { + + @Override + protected Class> getTemplateClass() { + return SmbRemoteFileTemplate.class; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/package-info.java new file mode 100644 index 0000000000..89cc17db7e --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/package-info.java @@ -0,0 +1,4 @@ +/** + * SMB-specific file list filter classes. + */ +package org.springframework.integration.smb.config; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbPersistentAcceptOnceFileListFilter.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbPersistentAcceptOnceFileListFilter.java new file mode 100644 index 0000000000..d62727e239 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbPersistentAcceptOnceFileListFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2018-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.filters; + +import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter; +import org.springframework.integration.metadata.ConcurrentMetadataStore; + +import jcifs.smb.SmbFile; + +/** + * Implementation of {@link AbstractPersistentAcceptOnceFileListFilter} for SMB. + * + * @author Prafull Kumar Soni + * + * @since 6.0 + */ +public class SmbPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter { + + + public SmbPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, String prefix) { + super(store, prefix); + } + + @Override + protected long modified(SmbFile file) { + return file.getLastModified(); + } + + @Override + protected String fileName(SmbFile file) { + return file.getName(); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbRegexPatternFileListFilter.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbRegexPatternFileListFilter.java new file mode 100644 index 0000000000..75bd14a693 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbRegexPatternFileListFilter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-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.filters; + +import java.io.UncheckedIOException; +import java.util.regex.Pattern; + +import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * Implementation of {@link AbstractRegexPatternFileListFilter} for SMB. + * + * @author Markus Spann + * @author Prafull Kumar Soni + * + * @since 6.0 + */ +public class SmbRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter { + + public SmbRegexPatternFileListFilter(String pattern) { + this(Pattern.compile(pattern)); + } + + public SmbRegexPatternFileListFilter(Pattern pattern) { + super(pattern); + } + + /** + * Gets the specified SMB file's name. + * @param file SMB file object + * @return file name + * @see AbstractRegexPatternFileListFilter#getFilename(java.lang.Object) + */ + @Override + protected String getFilename(SmbFile file) { + return (file != null ? file.getName() : null); + } + + @Override + protected boolean isDirectory(SmbFile file) { + try { + return file.isDirectory(); + } + catch (SmbException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSimplePatternFileListFilter.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSimplePatternFileListFilter.java new file mode 100644 index 0000000000..2867888995 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSimplePatternFileListFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018-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.filters; + +import java.io.UncheckedIOException; + +import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * Implementation of {@link AbstractSimplePatternFileListFilter} for SMB. + * + * @author Markus Spann + * @author Prafull Kumar Soni + * + * @since 6.0 + */ +public class SmbSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter { + + public SmbSimplePatternFileListFilter(String pathPattern) { + super(pathPattern); + } + + /** + * Gets the specified SMB file's name. + * @param file SMB file object + * @return file name + * @see AbstractSimplePatternFileListFilter#getFilename(java.lang.Object) + */ + @Override + protected String getFilename(SmbFile file) { + return (file != null) ? file.getName() : null; + } + + + @Override + protected boolean isDirectory(SmbFile file) { + try { + return file.isDirectory(); + } + catch (SmbException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSystemMarkerFilePresentFileListFilter.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSystemMarkerFilePresentFileListFilter.java new file mode 100644 index 0000000000..fb8400c42d --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSystemMarkerFilePresentFileListFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018-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.filters; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.integration.file.filters.AbstractMarkerFilePresentFileListFilter; +import org.springframework.integration.file.filters.FileListFilter; + +import jcifs.smb.SmbFile; + +/** + * Implementation of {@link AbstractMarkerFilePresentFileListFilter} for SMB. + * + * @author Prafull Kumar Soni + * + * @since 6.0 + */ +public class SmbSystemMarkerFilePresentFileListFilter extends AbstractMarkerFilePresentFileListFilter { + + public SmbSystemMarkerFilePresentFileListFilter(FileListFilter filter) { + super(filter); + } + + public SmbSystemMarkerFilePresentFileListFilter(FileListFilter filter, String suffix) { + super(filter, suffix); + } + + public SmbSystemMarkerFilePresentFileListFilter(FileListFilter filter, Function function) { + super(filter, function); + } + + public SmbSystemMarkerFilePresentFileListFilter( + Map, Function> filtersAndFunctions) { + + super(filtersAndFunctions); + } + + @Override + protected String getFilename(SmbFile file) { + return file.getName(); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/package-info.java new file mode 100644 index 0000000000..2df5c7374f --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/package-info.java @@ -0,0 +1,4 @@ +/** + * SMB Namespace support classes. + */ +package org.springframework.integration.smb.filters; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizer.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizer.java new file mode 100644 index 0000000000..bdd237427b --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizer.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-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 org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; + +import jcifs.smb.SmbFile; + +/** + * An implementation of {@link AbstractInboundFileSynchronizer} for SMB. + * + * @author Markus Spann + * @author Artem Bilan + * + * @since 6.0 + */ +public class SmbInboundFileSynchronizer extends AbstractInboundFileSynchronizer { + + /** + * Create a synchronizer with the {@link SessionFactory} used to acquire + * {@link org.springframework.integration.file.remote.session.Session} instances. + * @param sessionFactory the {@link SessionFactory} to use. + */ + public SmbInboundFileSynchronizer(SessionFactory sessionFactory) { + super(sessionFactory); + } + + @Override + protected boolean isFile(SmbFile _file) { + try { + return _file != null && _file.isFile(); + } + catch (Exception _ex) { + logger.warn("Unable to get resource status [" + _file + "].", _ex); + } + return false; + } + + @Override + protected String getFilename(SmbFile _file) { + return _file != null ? _file.getName() : null; + } + + @Override + protected long getModified(SmbFile file) { + return file.getLastModified(); + } + + @Override + protected String protocol() { + return "smb"; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizingMessageSource.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizingMessageSource.java new file mode 100644 index 0000000000..7dd5740d73 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizingMessageSource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-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.io.File; +import java.util.Comparator; + +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource; + +import jcifs.smb.SmbFile; + +/** + * A {@link org.springframework.integration.core.MessageSource} implementation for SMB. + * + * @author Markus Spann + * + * @since 6.0 + */ +public class SmbInboundFileSynchronizingMessageSource extends AbstractInboundFileSynchronizingMessageSource { + + public SmbInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer _synchronizer) { + this(_synchronizer, null); + } + + public SmbInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer _synchronizer, + Comparator _comparator) { + super(_synchronizer, _comparator); + } + + @Override + public String getComponentType() { + return "smb:inbound-channel-adapter"; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/package-info.java new file mode 100644 index 0000000000..dde9ee1642 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Inbound Channel Adapters implementations for SMB protocol. + */ +package org.springframework.integration.smb.inbound; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbMessageHandler.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbMessageHandler.java new file mode 100644 index 0000000000..99fe57780a --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/SmbMessageHandler.java @@ -0,0 +1,47 @@ +/* + * 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 org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; + +import jcifs.smb.SmbFile; + +/** +* The SMB specific {@link FileTransferringMessageHandler} extension. Based on the +* {@link SmbRemoteFileTemplate}. +* +* @author Gregory Bragg +* @author Artem Bilan +* +* @since 6.0 +* +* @see SmbRemoteFileTemplate +*/ +public class SmbMessageHandler extends FileTransferringMessageHandler { + + public SmbMessageHandler(SessionFactory sessionFactory) { + this(sessionFactory, FileExistsMode.REPLACE); + } + + public SmbMessageHandler(SessionFactory sessionFactory, FileExistsMode mode) { + super(new SmbRemoteFileTemplate(sessionFactory), mode); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/package-info.java new file mode 100644 index 0000000000..58b10c9a17 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Outbound Channel Adapter implementations for SMB protocol. + */ +package org.springframework.integration.smb.outbound; diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbConfig.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbConfig.java new file mode 100644 index 0000000000..b1a60067df --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbConfig.java @@ -0,0 +1,225 @@ +/* + * Copyright 2012-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.net.URI; +import java.net.URISyntaxException; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import jcifs.DialectVersion; + +/** + * Data holder class for a SMB share configuration. + * + * SmbFile URLs syntax: + * smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]] + * + * @author Markus Spann + * @author Prafull Kumar Soni + * @author Artem Bilan + * @author Gregory Bragg + * + * @since 6.0 + */ +public class SmbConfig { + + private String host; + + private int port; + + private String domain; + + private String username; + + private String password; + + private String shareAndDir; + + /** + * Defaults to and follows the jCIFS library default of 'SMB1'. + */ + private DialectVersion smbMinVersion = DialectVersion.SMB1; + + /** + * Defaults to and follows the jCIFS library default of 'SMB210'. + */ + private DialectVersion smbMaxVersion = DialectVersion.SMB210; + + public SmbConfig() { + } + + public SmbConfig(String _host, int _port, String _domain, String _username, String _password, String _shareAndDir) { + setHost(_host); + setPort(_port); + setDomain(_domain); + setUsername(_username); + setPassword(_password); + setShareAndDir(_shareAndDir); + } + + public void setHost(String _host) { + Assert.hasText(_host, "host must not be empty"); + this.host = _host; + } + + public String getHost() { + return this.host; + } + + public void setPort(int _port) { + Assert.isTrue(_port >= 0, "port must be >= 0"); + this.port = _port; + } + + public int getPort() { + return this.port; + } + + public void setDomain(String _domain) { + Assert.notNull(_domain, "_domain can't be null"); + this.domain = _domain; + } + + public String getDomain() { + return this.domain; + } + + public void setUsername(String _username) { + Assert.hasText(_username, "username should be a non-empty string"); + this.username = _username; + } + + public String getUsername() { + return this.username; + } + + public void setPassword(String _password) { + Assert.notNull(_password, "password should not be null"); + this.password = _password; + } + + public String getPassword() { + return this.password; + } + + public void setShareAndDir(String _shareAndDir) { + Assert.notNull(_shareAndDir, "shareAndDir should not be null"); + this.shareAndDir = _shareAndDir; + } + + public String getShareAndDir() { + return this.shareAndDir; + } + + /** + * Gets the desired minimum SMB version value for what the Windows server will allow + * during protocol transport negotiation. + * @return one of SMB1, SMB202, SMB210, SMB300, SMB302 or SMB311 + */ + public DialectVersion getSmbMinVersion() { + return this.smbMinVersion; + } + + /** + * Sets the desired minimum SMB version value for what the Windows server will allow + * during protocol transport negotiation. + * @param _smbMinVersion one of SMB1, SMB202, SMB210, SMB300, SMB302 or SMB311 + */ + public void setSmbMinVersion(DialectVersion _smbMinVersion) { + this.smbMinVersion = _smbMinVersion; + } + + /** + * Gets the desired maximum SMB version value for what the Windows server will allow + * during protocol transport negotiation. + * @return one of SMB1, SMB202, SMB210, SMB300, SMB302 or SMB311 + */ + public DialectVersion getSmbMaxVersion() { + return this.smbMaxVersion; + } + + /** + * Sets the desired maximum SMB version value for what the Windows server will allow + * during protocol transport negotiation. + * @param _smbMaxVersion one of SMB1, SMB202, SMB210, SMB300, SMB302 or SMB311 + */ + public void setSmbMaxVersion(DialectVersion _smbMaxVersion) { + this.smbMaxVersion = _smbMaxVersion; + } + + String getDomainUserPass(boolean _includePassword) { + String domainUserPass; + if (StringUtils.hasText(this.domain)) { + domainUserPass = String.format("%s;%s", this.domain, this.username); + } + else { + domainUserPass = this.username; + } + if (StringUtils.hasText(this.password)) { + domainUserPass += ":" + (_includePassword ? this.password : "********"); + } + return domainUserPass; + } + + String getHostPort() { + return this.host + (this.port > 0 ? String.format(":%d", this.port) : ""); + } + + /** + * Validates the object. Throws run-time exception if found to be invalid. + * @return the object + */ + public final SmbConfig validate() { + Assert.hasText(getHost(), () -> "host must not be empty in " + this); + Assert.isTrue(getPort() >= 0, () -> "port must be >= 0 in " + this); + Assert.hasText(getShareAndDir(), () -> "share must not be empty in " + this); + return this; + } + + public final String getUrl() { + return getUrl(true); + } + + public final String getUrl(boolean _includePassword) { + String domainUserPass = getDomainUserPass(_includePassword); + + String path = StringUtils.cleanPath(this.shareAndDir); + + if (!path.startsWith("/")) { + path = "/" + path; + } + + try { + return new URI("smb", domainUserPass, this.host, this.port, path, null, null) + .toASCIIString(); + } + catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[url=" + getUrl(false) + + "]"; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbRemoteFileTemplate.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbRemoteFileTemplate.java new file mode 100644 index 0000000000..e35de352fd --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbRemoteFileTemplate.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-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 org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.file.remote.session.SessionFactory; + +import jcifs.smb.SmbFile; + +/** + * The SMB-specific {@link RemoteFileTemplate} implementation. + * + * @author Artem Bilan + * + * @since 6.0 + */ +public class SmbRemoteFileTemplate extends RemoteFileTemplate { + + /** + * Construct a {@link SmbRemoteFileTemplate} with the supplied session factory. + * @param sessionFactory the session factory. + */ + public SmbRemoteFileTemplate(SessionFactory sessionFactory) { + super(sessionFactory); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java new file mode 100644 index 0000000000..e5ee809b4c --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java @@ -0,0 +1,488 @@ +/* + * Copyright 2012-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.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.Arrays; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.file.remote.session.Session; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; +import jcifs.smb.SmbFileOutputStream; + +/** + * Implementation of the {@link Session} interface for Server Message Block (SMB) + * also known as Common Internet File System (CIFS). The Samba project set out to + * create non-Windows implementations of SMB. Often Samba is thus used synonymously to SMB. + * + * SMB is an application-layer network protocol that manages shared access to files, printers + * and other networked resources. + * + * See Server Message Block + * for more details. + * + * @author Markus Spann + * @author Mark Fisher + * @author Oleg Zhurakousky + * @author Artem Bilan + * @author Prafull Kumar Soni + * @author Gregory Bragg + * @author Adam Jones + * + * @since 6.0 + */ +public class SmbSession implements Session { + + private static final Log logger = LogFactory.getLog(SmbSession.class); + + private static final String FILE_SEPARATOR = System.getProperty("file.separator"); + + private static final String SMB_FILE_SEPARATOR = "/"; + + private final SmbShare smbShare; + + /** + * Constructor for an SMB session. + * @param _host server NetBIOS name, DNS name, or IP address, case-insensitive + * @param _port port + * @param _domain case-sensitive domain name + * @param _user case-sensitive username + * @param _password case-sensitive password + * @param _shareAndDir server root SMB directory + * @throws IOException in case of I/O errors + */ + SmbSession(String _host, int _port, String _domain, String _user, String _password, String _shareAndDir) + throws IOException { + + this(new SmbShare(new SmbConfig(_host, _port, _domain, _user, _password, _shareAndDir))); + } + + /** + * Constructor for an SMB session. + * @param _smbShare SMB share resource + */ + public SmbSession(SmbShare _smbShare) { + Assert.notNull(_smbShare, "smbShare must not be null"); + this.smbShare = _smbShare; + if (logger.isDebugEnabled()) { + logger.debug("New " + getClass().getName() + " created."); + } + } + + /** + * Deletes the file or directory at the specified path. + * @param _path path to a remote file or directory + * @return true if delete successful, false if resource is non-existent + * @throws IOException on error conditions returned by a CIFS server + */ + @Override + public boolean remove(String _path) throws IOException { + Assert.hasText(_path, "path must not be empty"); + + boolean removed = false; + SmbFile removeFile = createSmbFileObject(_path); + + if (removeFile.exists()) { + removeFile.delete(); + removed = true; + } + if (!removed && logger.isInfoEnabled()) { + logger.info("Could not remove non-existing resource [" + _path + "]."); + } + else if (logger.isInfoEnabled()) { + logger.info("Successfully removed resource [" + _path + "]."); + } + return removed; + } + + /** + * Returns the contents of the specified SMB resource as an array of SmbFile objects. + * In case the remote resource does not exist, an empty array is returned. + * @param _path path to a remote directory + * @return array of SmbFile objects + * @throws IOException on error conditions returned by a CIFS server or if the remote resource is not a directory. + */ + @Override + public SmbFile[] list(String _path) throws IOException { + SmbFile[] files = new SmbFile[0]; + try { + SmbFile smbDir = createSmbDirectoryObject(_path); + if (!smbDir.exists()) { + if (logger.isWarnEnabled()) { + logger.warn("Remote directory [" + _path + "] does not exist. Cannot list resources."); + } + return files; + } + else if (!smbDir.isDirectory()) { + throw new IOException("Resource [" + _path + "] is not a directory. Cannot list resources."); + } + + files = smbDir.listFiles(); + } + catch (SmbException _ex) { + throw new IOException("Failed to list resources in [" + _path + "].", _ex); + } + + if (logger.isDebugEnabled()) { + logger.debug("Successfully listed " + files.length + " resource(s) in [" + _path + "]" + + ": " + Arrays.toString(files)); + } + else if (logger.isInfoEnabled()) { + logger.info("Successfully listed " + files.length + " resource(s) in [" + _path + "]" + "."); + } + + return files; + } + + /** + * Reads the remote resource specified by path and copies its contents to the specified + * {@link OutputStream}. + * @param _path path to a remote file + * @param _outputStream output stream + * @throws IOException on error conditions returned by a CIFS server or if the remote resource is not a file. + */ + @Override + public void read(String _path, OutputStream _outputStream) throws IOException { + Assert.hasText(_path, "path must not be empty"); + Assert.notNull(_outputStream, "outputStream must not be null"); + + try { + SmbFile remoteFile = createSmbFileObject(_path); + if (!remoteFile.isFile()) { + throw new IOException("Resource [" + _path + "] is not a file."); + } + FileCopyUtils.copy(remoteFile.getInputStream(), _outputStream); + } + catch (SmbException _ex) { + throw new IOException("Failed to read resource [" + _path + "].", _ex); + } + + if (logger.isInfoEnabled()) { + logger.info("Successfully read resource [" + _path + "]."); + } + } + + /** + * Writes contents of the specified {@link InputStream} to the remote resource + * specified by path. Remote directories are created implicitly as required. + * @param _inputStream input stream + * @param _path remote path (of a file) to write to + * @throws IOException on error conditions returned by a CIFS server + */ + @Override + public void write(InputStream _inputStream, String _path) throws IOException { + Assert.notNull(_inputStream, "inputStream must not be empty"); + Assert.hasText(_path, "path must not be null"); + + try { + mkdirs(_path); + SmbFile targetFile = createSmbFileObject(_path); + FileCopyUtils.copy(_inputStream, targetFile.getOutputStream()); + } + catch (SmbException _ex) { + throw new IOException("Failed to write resource [" + _path + "].", _ex); + } + if (logger.isInfoEnabled()) { + logger.info("Successfully wrote remote file [" + _path + "]."); + } + } + + /** + * Convenience method to write a local file object to a remote location. + * @param _file the local file + * @param _path the remote path to write to + * @return the {@link SmbFile} for remote file + * @throws IOException the IO exception + */ + public SmbFile write(File _file, String _path) throws IOException { + return writeAndClose(new FileInputStream(_file), _path); + } + + /** + * Convenience method to write a byte array to a remote location. + * @param _contents the {@code byte[]} to write + * @param _path the remote file to write to + * @return the {@link SmbFile} for remote file + * @throws IOException the IO exception + */ + public SmbFile write(byte[] _contents, String _path) throws IOException { + return writeAndClose(new ByteArrayInputStream(_contents), _path); + } + + /** + * Creates the specified remote path if not yet exists. + * If the specified resource is a file rather than a path, creates all directories leading + * to that file. + * @param _path remote path to create + * @return always true (error states are express by exceptions) + * @throws IOException on error conditions returned by a CIFS server + */ + @Override + public boolean mkdir(String _path) throws IOException { + try { + SmbFile dir = createSmbDirectoryObject(_path); + if (!dir.exists()) { + dir.mkdirs(); + if (logger.isInfoEnabled()) { + logger.info( + "Successfully created remote directory [" + _path + "] in share [" + this.smbShare + "]."); + } + } + else { + if (logger.isInfoEnabled()) { + logger.info("Remote directory [" + _path + "] exists in share [" + this.smbShare + "]."); + } + } + return true; + } + catch (SmbException _ex) { + throw new IOException("Failed to create directory [" + _path + "].", _ex); + } + } + + /** + * Checks whether the remote resource exists. + * @param _path remote path + * @return true if exists, false otherwise + * @throws IOException on error conditions returned by a CIFS server + */ + @Override + public boolean exists(String _path) throws IOException { + return createSmbFileObject(_path).exists(); + } + + /** + * Checks whether the remote resource is a file. + * @param _path remote path + * @return true if resource is a file, false otherwise + * @throws IOException on error conditions returned by a CIFS server + */ + public boolean isFile(String _path) throws IOException { + SmbFile resource = createSmbFileObject(_path); + return resource.exists() && resource.isFile(); + } + + /** + * Checks whether the remote resource is a directory. + * @param _path remote path + * @return true if resource is a directory, false otherwise + * @throws IOException on error conditions returned by a CIFS server + */ + public boolean isDirectory(String _path) throws IOException { + SmbFile resource = createSmbFileObject(_path); + return resource.exists() && resource.isDirectory(); + } + + /** + * Create all directories in the given remote path reference. + * @param _path path remote path, may be a file in which case the file name is ignored + * @return the path created or null + * @throws IOException on error conditions returned by a CIFS server + */ + String mkdirs(String _path) throws IOException { + int idxPath = _path.lastIndexOf(FILE_SEPARATOR); + if (idxPath > -1) { + String path = _path.substring(0, idxPath + 1); + mkdir(path); + return path; + } + return null; + } + + @Override + public void rename(String _pathFrom, String _pathTo) throws IOException { + try { + + SmbFile smbFileFrom = createSmbFileObject(_pathFrom); + SmbFile smbFileTo = createSmbFileObject(_pathTo); + if (smbFileTo.exists()) { + smbFileTo.delete(); + } + smbFileFrom.renameTo(smbFileTo); + + } + catch (SmbException _ex) { + throw new IOException("Failed to rename [" + _pathFrom + "] to [" + _pathTo + "].", _ex); + } + if (logger.isInfoEnabled()) { + logger.info("Successfully renamed remote resource [" + _pathFrom + "] to [" + _pathTo + "]."); + } + + } + + @Override + public void append(InputStream inputStream, String destination) throws IOException { + SmbFile smbFile = createSmbFileObject(destination); + OutputStream fileOutputStream = new SmbFileOutputStream(smbFile, true); + FileCopyUtils.copy(inputStream, fileOutputStream); + } + + @Override + public boolean rmdir(String directory) throws IOException { + SmbFile dir = createSmbDirectoryObject(directory); + try { + dir.delete(); + } + catch (SmbException e) { + if (logger.isWarnEnabled()) { + logger.info("Failed to remove remote directory [" + directory + "]: " + e); + } + return false; + } + if (logger.isInfoEnabled()) { + logger.info("Successfully removed remote directory [" + directory + "]."); + } + return true; + } + + @Override + public InputStream readRaw(String source) throws IOException { + SmbFile remoteFile = createSmbFileObject(source); + if (!remoteFile.isFile()) { + throw new IOException("Resource [" + source + "] is not a file."); + } + return remoteFile.getInputStream(); + } + + @Override + public boolean finalizeRaw() { + return true; + } + + @Override + public Object getClientInstance() { + return this.smbShare; + } + + @Override + public void close() { + this.smbShare.close(); + } + + /** + * Checks with this SMB session is open and ready for work by attempting + * to list remote files and checking for error conditions.. + * @return true if the session is open, false otherwise + */ + @Override + public boolean isOpen() { + if (!this.smbShare.isOpened()) { + return false; + } + try { + this.smbShare.listFiles(); + } + catch (Exception _ex) { + close(); + } + return this.smbShare.isOpened(); + } + + /** + * Convenience method to write the specified input stream to a remote path and return + * the path as an SMB file object. + * @param _inputStream input stream + * @param _path remote path (of a file) to write to + * @return SMB file object + * @throws IOException on error conditions returned by a CIFS server + */ + SmbFile writeAndClose(InputStream _inputStream, String _path) throws IOException { + write(_inputStream, _path); + _inputStream.close(); + return createSmbFileObject(_path); + } + + /** + * Factory method for new SmbFile objects under this session's share for the specified path. + * @param path remote path + * @param isDirectory Boolean object to indicate the path is a directory, may be null + * @return SmbFile object for path + * @throws IOException in case of I/O errors + */ + private SmbFile createSmbFileObject(String path, Boolean isDirectory) throws IOException { + + final String cleanedPath = StringUtils.cleanPath(path); + + if (!StringUtils.hasText(cleanedPath)) { + return this.smbShare; + } + + SmbFile smbFile = new SmbFile(this.smbShare, cleanedPath); + + boolean appendFileSeparator = !cleanedPath.endsWith(SMB_FILE_SEPARATOR); + if (appendFileSeparator) { + try { + appendFileSeparator = smbFile.isDirectory() || (isDirectory != null && isDirectory); + } + catch (SmbException ex) { + appendFileSeparator = false; + } + } + if (appendFileSeparator) { + smbFile = createSmbFileObject(cleanedPath + SMB_FILE_SEPARATOR); + } + if (logger.isDebugEnabled()) { + logger.debug("Created new " + SmbFile.class.getName() + "[" + smbFile + "] for path [" + path + "]."); + } + return smbFile; + } + + /** + * Creates an SMB file object pointing to a remote file. + * @param _path the remote file path + * @return the {@link SmbFile} for remote path + * @throws IOException the IO exception + */ + public SmbFile createSmbFileObject(String _path) throws IOException { + return createSmbFileObject(_path, null); + } + + /** + * Creates an SMB file object pointing to a remote directory. + * @param _path the remote directory path + * @return the {@link SmbFile} for remote path + * @throws IOException the IO exception + */ + public SmbFile createSmbDirectoryObject(String _path) throws IOException { + return createSmbFileObject(_path, true); + } + + @Override + public String getHostPort() { + URL url = this.smbShare.getURL(); + return url.getHost() + ":" + url.getPort(); + } + + @Override + public String[] listNames(String path) { + throw new UnsupportedOperationException("Not implemented yet"); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSessionFactory.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSessionFactory.java new file mode 100644 index 0000000000..1aaa863fa5 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSessionFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-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.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.util.Assert; + +import jcifs.CIFSContext; +import jcifs.smb.SmbFile; + +/** + * The SMB session factory. + * + * @author Markus Spann + * @author Gregory Bragg + * @author Artem Bilan + * + * @since 6.0 + */ +public class SmbSessionFactory extends SmbConfig implements SessionFactory { + + private static final Log logger = LogFactory.getLog(SmbSessionFactory.class); + + private CIFSContext context = null; + + public SmbSessionFactory() { + logger.debug("New " + getClass().getName() + " created."); + } + + /** + * Initializes the SMB session factory with a custom client context configuration. + * @param _context that holds the client configuration, shared services as well as the active credentials + */ + public SmbSessionFactory(CIFSContext _context) { + Assert.notNull(_context, "_context can't be null"); + this.context = _context; + logger.debug("New " + getClass().getName() + " created with CIFSContext."); + } + + @Override + public final SmbSession getSession() { + try { + return createSession(); + } + catch (Exception _ex) { + throw new IllegalStateException("Failed to create session.", _ex); + } + } + + protected SmbSession createSession() throws IOException { + SmbShare smbShare; + if (this.context != null) { + smbShare = new SmbShare(this, this.context); + } + else { + Properties props = new Properties(); + props.setProperty("jcifs.smb.client.minVersion", this.getSmbMinVersion().name()); + props.setProperty("jcifs.smb.client.maxVersion", this.getSmbMaxVersion().name()); + + smbShare = new SmbShare(this, props); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("SMB share init: %s/%s", getHostPort(), getShareAndDir())); + } + + smbShare.init(); + logger.debug("SMB share initialized."); + + return new SmbSession(smbShare); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbShare.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbShare.java new file mode 100644 index 0000000000..1e72edb1c7 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbShare.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-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.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import jcifs.CIFSContext; +import jcifs.CIFSException; +import jcifs.config.PropertyConfiguration; +import jcifs.context.BaseContext; +import jcifs.context.SingletonContext; +import jcifs.smb.NtlmPasswordAuthenticator; +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +/** + * The {@link SmbFile} extension to represent an SMB share directory. + * + * @author Markus Spann + * @author Gregory Bragg + * @author Adam Jones + * + * @since 6.0 + */ +public class SmbShare extends SmbFile { + + private static final Log logger = LogFactory.getLog(SmbShare.class); + + private final AtomicBoolean open = new AtomicBoolean(false); + + private final AtomicBoolean closeContext = new AtomicBoolean(false); + + /** + * Initializes the jCIFS library with default properties. + * @param _smbConfig the SMB share configuration + * @throws IOException if an invalid SMB URL was constructed by jCIFS + */ + public SmbShare(SmbConfig _smbConfig) throws IOException { + super(StringUtils.cleanPath(_smbConfig.validate().getUrl()), + SingletonContext.getInstance().withCredentials( + new NtlmPasswordAuthenticator( + _smbConfig.getDomain(), _smbConfig.getUsername(), _smbConfig.getPassword()))); + } + + /** + * Initializes the jCIFS library with a custom client context configuration. + * @param _smbConfig the SMB share configuration + * @param _context that holds the client configuration, shared services as well as the active credentials + * @throws IOException if an invalid SMB URL was constructed by jCIFS + */ + public SmbShare(SmbConfig _smbConfig, CIFSContext _context) throws IOException { + super(StringUtils.cleanPath(_smbConfig.validate().getUrl()), _context); + } + + /** + * Initializes the jCIFS library with custom properties such as + * 'jcifs.smb.client.minVersion' and 'jcifs.smb.client.maxVersion' + * for setting the minimum/maximum SMB supported versions. + * @param _smbConfig the SMB share configuration + * @param _props the custom property set for jCIFS to initialize + * @throws IOException if an invalid property was set or an invalid SMB URL was constructed by jCIFS + */ + public SmbShare(SmbConfig _smbConfig, Properties _props) throws IOException { + super(StringUtils.cleanPath(_smbConfig.validate().getUrl()), + new BaseContext( + new PropertyConfiguration(_props)).withCredentials( + new NtlmPasswordAuthenticator( + _smbConfig.getDomain(), _smbConfig.getUsername(), _smbConfig.getPassword()))); + + this.closeContext.set(true); + } + + public void init() throws IOException { + boolean canRead; + try { + if (!exists()) { + logger.info("SMB root directory does not exist. Creating it."); + mkdirs(); + } + canRead = canRead(); + } + catch (SmbException _ex) { + if (this.closeContext.get()) { + try { + getContext().close(); + } + catch (CIFSException e) { + logger.error("Unable to close share: " + this); + } + } + throw new IOException("Unable to initialize share: " + this, _ex); + } + Assert.isTrue(canRead, "Share is not accessible " + this); + this.open.set(true); + } + + /** + * Checks whether the share is accessible. + * Note: jcifs.smb.SmbFile defines a package-protected method isOpen(). + * @return true if open + */ + boolean isOpened() { + return this.open.get(); + } + + @Override + public synchronized void close() { + this.open.set(false); + if (this.closeContext.get()) { + try { + getContext().close(); + } + catch (CIFSException e) { + logger.error("Unable to close share: " + this); + } + } + super.close(); + } + + public String newTempFileSuffix() { + return "-" + Long.toHexString(Double.doubleToLongBits(Math.random())) + ".tmp"; + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/package-info.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/package-info.java new file mode 100644 index 0000000000..1f8818c83d --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/package-info.java @@ -0,0 +1,4 @@ +/** + * SMB Remote Session abstraction support classes. + */ +package org.springframework.integration.smb.session; diff --git a/spring-integration-smb/src/main/resources/META-INF/spring.handlers b/spring-integration-smb/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000000..3ccaf0c289 --- /dev/null +++ b/spring-integration-smb/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/smb=org.springframework.integration.smb.config.SmbNamespaceHandler diff --git a/spring-integration-smb/src/main/resources/META-INF/spring.schemas b/spring-integration-smb/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000000..8ddd71f532 --- /dev/null +++ b/spring-integration-smb/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,4 @@ +http\://www.springframework.org/schema/integration/smb/spring-integration-smb-1.0.xsd=org/springframework/integration/smb/config/spring-integration-smb.xsd +http\://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd=org/springframework/integration/smb/config/spring-integration-smb.xsd +https\://www.springframework.org/schema/integration/smb/spring-integration-smb-1.0.xsd=org/springframework/integration/smb/config/spring-integration-smb.xsd +https\://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd=org/springframework/integration/smb/config/spring-integration-smb.xsd diff --git a/spring-integration-smb/src/main/resources/META-INF/spring.tooling b/spring-integration-smb/src/main/resources/META-INF/spring.tooling new file mode 100644 index 0000000000..1b17ebbb90 --- /dev/null +++ b/spring-integration-smb/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration smb namespace +http\://www.springframework.org/schema/integration/smb@name=Integration SMB Namespace +http\://www.springframework.org/schema/integration/smb@prefix=int-smb +http\://www.springframework.org/schema/integration/smb@icon=org/springframework/integration/smb/config/spring-integration-smb.gif diff --git a/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.gif b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.gif new file mode 100644 index 0000000000000000000000000000000000000000..210e0764fa4c1e5baebcdea156bc3f3e1f97a2c9 GIT binary patch literal 539 zcmZ?wbhEHb6krfwc*Xz%|NsB*n7VH6iaomzUp%y<^XRV5le@dm?CrjBr0?SXo|o4a z>{}Ri^GM%=bBkWz+y8vM?x%h3?+zusxj5zR@#^Jm-pe}!R`mow*jzjz+p;&)qBX&& zG1jmr&Ac*H`_zJhTu;?PU-d#?%_oZ!?=4DAaa1VqRO?H!xIQD{=A4w7g+5uX8WR(2 zrX<_tc8Bb5QRHvpiB8JU`8KTWP?thKQZD zFcaE=iuVx<>PO@dH?gn{*FclYXGfDn1BEP literal 0 HcmV?d00001 diff --git a/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.xsd b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.xsd new file mode 100644 index 0000000000..6a85beab96 --- /dev/null +++ b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.xsd @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Identifies directory path (e.g., "/temp/mytransfers/") + where file will be transferred to. + + + + + + + Allows you to provide SpEL expression which will + compute directory path where file will be + transferred to (e.g., "headers.['remote_dir'] + '/myTransfers'"); + + + + + + + Allows you to provide remote file/directory separator + character. DEFAULT: '/' + + + + + + + Extension used when uploading files. We change + it right after we know it's uploaded. + + + + + + + Allows you to specify a reference to a + [org.springframework.integration.file.FileNameGenerator] bean. + + + + + + + + + + + + Allows you to provide SpEL expression which will + compute file name of the remote file (e.g., assuming + payload is java.io.File "payload.getName() + '.transfered'"); + + + + + + + Specifies the order for invocation when this + endpoint is connected as a subscriber to a channel. + This is particularly relevant when that channel + is using a "failover" dispatching strategy, or + when a failure in the delivery to one subscriber + should signal that the message should not be sent + to subscribers with a higher 'order' attribute. + It has no effect when this endpoint itself is a + Polling Consumer for a channel with a queue. + + + + + + + + + + + + + + + + + + + + + + + + Allows you to provide file name pattern to determine + the file names that needs to be scanned and is + based on simple pattern matching algorithm + (e.g., "*.txt, fo*.txt" etc.) + + + + + + + Allows you to provide Regular Expression to determine + the file names that needs to be scanned. (e.g., "f[o]+\.txt" etc.) + + + + + + + + + + + + + + + + + Allows you to specify a reference to + [org.springframework.integration.file.filters.FileListFilter] bean. + + + + + + + Extension used when downloading files. We change + it right after we know it's downloaded. + + + + + + + Identifies directory path (e.g., "/temp/mytransfers") + where file will be transferred FROM. + + + + + + + Allows you to provide remote file/directory separator + character. DEFAULT: '/' + + + + + + + + + + + + Allows you to specify a reference to a + [org.springframework.integration.file.filters.FileListFilter] + bean. This filter is applied to files after they have been + retrieved. The default is an AcceptOnceFileListFilter which means that, + even if a new instance of a file is retrieved from the remote server, + a message won't be generated. The filter provided here is combined + with a filter that prevents the message source from processing + files that are currently being downloaded. + + + + + + + Identifies directory path (e.g., "/local/mytransfers") + where file will be transferred to. + + + + + + + Tells this adapter if local directory must be + auto-created if it doesn't exist. Default is TRUE. + + + + + + + Specify whether to delete the remote source file after copying. + By default, the remote files will NOT be deleted. + + + + + + + + + + + + + + + + + + + + + + + Allows you to specify Charset (e.g., US-ASCII, ISO-8859-1, UTF-8). + [UTF-8] is the default. + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTests.java new file mode 100644 index 0000000000..e2aa25fe75 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTests.java @@ -0,0 +1,276 @@ +/* + * Copyright 2012-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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +/** + * Assorted test utils for the library. + * + * @author Markus Spann + * @author Gregory Bragg + */ +public abstract class AbstractBaseTests { + + /** Instance logger. */ + private final Log logger = LogFactory.getLog(this.getClass()); + + protected final Log getLogger() { + return logger; + } + + @Rule + public final TestName testMethodName = new TestName(); + + private String getTestMethodName() { + return getClass().getSimpleName() + '.' + testMethodName.getMethodName() + "()"; + } + + @Before + public final void logTestBegin() { + getLogger().info("BGN - Test " + getTestMethodName()); + } + + @After + public final void logTestEnd() { + getLogger().info("END - Test " + getTestMethodName()); + } + + /** + * Constructs the Spring application context XML file name from simple class name and suffix '-context.xml'. + * @param _suffix optional suffix + * @return application context XML file name + */ + protected final String getApplicationContextXmlFile(String _suffix) { + String fn = getClass().getSimpleName(); + if (StringUtils.hasText(_suffix)) { + fn += _suffix; + } + fn += "-context.xml"; + getLogger().debug("Returning application context xml file [" + fn + "] for class [" + getClass().getName() + "]."); + return fn; + } + + /** + * Constructs the Spring application context XML file name from simple class name. + * @return application context XML file name + */ + protected final String getApplicationContextXmlFile() { + return getApplicationContextXmlFile(null); + } + + protected final ClassPathXmlApplicationContext getApplicationContext() { + return new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), getClass()); + } + + /** + * Writes the specified input stream to file. + * @param _inputStream input stream + * @param _path output file path + * @throws IOException in case of I/O errors + */ + public static void writeToFile(InputStream _inputStream, String _path) throws IOException { + FileOutputStream fos = new FileOutputStream(_path); + try { + FileCopyUtils.copy(_inputStream, fos); + } + finally { + fos.close(); + } + } + + /** + * Writes the specified byte array to the output stream. + * @param _bytes byte array + * @param _outputStream output stream + * @throws IOException in case of I/O errors + */ + public static void writeToFile(byte[] _bytes, OutputStream _outputStream) throws IOException { + FileCopyUtils.copy(_bytes, _outputStream); + } + + /** + * Writes the specified byte array to the an output file. + * @param _bytes byte array + * @param _fileName output file + * @throws IOException in case of I/O errors + */ + public static void writeToFile(byte[] _bytes, String _fileName) throws IOException { + FileOutputStream fos = new FileOutputStream(_fileName); + writeToFile(_bytes, fos); + fos.close(); + } + + /** + * Creates a new file of the given name. + * If the file exists, it will be deleted. + * The file will be deleted on exit of the JVM. + * @param _fileName file name + * @return file object + */ + protected File createNewFile(String _fileName) { + File file = new File(_fileName); + file.deleteOnExit(); + if (file.exists()) { + file.delete(); + } + + getLogger().debug("File object [" + _fileName + "] created: " + file.getAbsolutePath()); + + assertFileNotExists(file); + return file; + } + + /** + * Deletes one or more files or directories. + * @param _files file or directories + */ + protected void delete(String... _files) { + for (String fileName : _files) { + if (fileName == null) { + continue; + } + File file = new File(fileName); + if (file.exists()) { + getLogger().debug("Deleting file [" + fileName + "]."); + if (!file.delete()) { + file.deleteOnExit(); + } + } + } + } + + /** + * Checks if a directory exists, if not creates it and adds it to the DeleteOnExit hook. + * @param _dir directory + */ + protected void ensureExists(String _dir) { + File dir = new File(_dir); + if (!dir.exists()) { + dir.mkdirs(); + dir.deleteOnExit(); + } + } + + /** + * Retrieves class name and method name at the specified stacktrace index. + * @param _index stacktrace index + * @return fully qualified method name + */ + private static String getStackTraceString(int _index) { + StackTraceElement[] arrStackTraceElems = new Throwable().fillInStackTrace().getStackTrace(); + final int lIndex = Math.min(arrStackTraceElems.length - 1, Math.max(0, _index)); + return arrStackTraceElems[lIndex].getClassName() + "." + arrStackTraceElems[lIndex].getMethodName(); + } + + /** + * Gets the current method name. + * @return method name + */ + public static String getMethodName() { + return getStackTraceString(2); + } + + /** + * Gets the calling method name. + * @return method name + */ + public static String getCallingMethodName() { + return getStackTraceString(3); + } + + /** + * Asserts that the specified file exists. + * @param _file file object + * @return the file object + */ + public static final File assertFileExists(File _file) { + return assertFileExists(_file, true); + } + + public static final File assertFileNotExists(File _file) { + return assertFileExists(_file, false); + } + + /** + * Asserts that the specified file exists or does not exists. + * @param _file file object + * @param _exists true if should exist, false otherwise + * @return the file object + */ + private static File assertFileExists(File _file, boolean _exists) { + assertThat(_file).as("File object is null.").isNotNull(); + if (_exists) { + assertThat(_file.exists()).as("File [" + _file.getAbsolutePath() + "] does not exist.").isTrue(); + } + else { + assertThat(!_file.exists()).as("File [" + _file.getAbsolutePath() + "] exists.").isTrue(); + } + return _file; + } + + public static final File assertFileExists(String _file) { + return assertFileExists(new File(_file)); + } + + /** + * Invokes one or more test methods on the specified test class. + * Catches exceptions during test setup (using reflection) and test invocation. + * @param _testClass test class object + * @param _methodNames String method names to invoke in order, no parameters expected + */ + @SuppressWarnings("deprecation") + protected static void runTests(Class _testClass, String... _methodNames) + throws Exception { + AbstractBaseTests test; + Method[] methods = new Method[_methodNames.length]; + String methodName = null; + + test = _testClass.newInstance(); + for (int i = 0; i < _methodNames.length; i++) { + methodName = _methodNames[i]; + methods[i] = _testClass.getMethod(methodName, (Class[]) null); + } + + Method method = null; + + for (int i = 0; i < methods.length; i++) { + method = methods[i]; + method.invoke(test, (Object[]) null); + } + + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/Main.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/Main.java new file mode 100644 index 0000000000..2017430947 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/Main.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-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 java.util.Scanner; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.smb.outbound.SmbMessageHandler; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.messaging.support.GenericMessage; + +import jcifs.DialectVersion; + +/** + * Starts the Spring Context and will initialize the Spring Integration routes. + * + * @author Gunnar Hillert + * @author Artem Bilan + * @author Gregory Bragg + * + * @since 1.0 + * + */ +public final class Main { + + private static final Log LOGGER = LogFactory.getLog(Main.class); + + private Main() { } + + /** + * Load the Spring Integration Application Context + * + * @param args - command line arguments + */ + public static void main(final String... args) { + + final Scanner scanner = new Scanner(System.in); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("\n=================================================================" + + "\n " + + "\n Welcome to the Spring Integration SMB Test Client " + + "\n " + + "\n For more information please visit: " + + "\n https://github.com/SpringSource/spring-integration-extensions " + + "\n " + + "\n================================================================="); + } + + final GenericXmlApplicationContext context = new GenericXmlApplicationContext(); + + LOGGER.info("Please enter the: "); + LOGGER.info("\t- SMB Host"); + LOGGER.info("\t- SMB Share and Directory"); + LOGGER.info("\t- SMB Username"); + LOGGER.info("\t- SMB Password"); + + LOGGER.info("Host: "); + final String host = scanner.nextLine(); + + LOGGER.info("Share and Directory (e.g. myFile/path/to/): "); + final String shareAndDir = scanner.nextLine(); + + LOGGER.info("Username (e.g. guest): "); + final String username = scanner.nextLine(); + + LOGGER.info("Password (can be empty): "); + final String password = scanner.nextLine(); + + context.getEnvironment().getSystemProperties().put("host", host); + context.getEnvironment().getSystemProperties().put("shareAndDir", shareAndDir); + context.getEnvironment().getSystemProperties().put("username", username); + context.getEnvironment().getSystemProperties().put("password", password); + + context.load("classpath:META-INF/spring/integration/*-context.xml"); + context.registerShutdownHook(); + context.refresh(); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Please press 'q + Enter' to quit the application. " + + "\n " + + "\n========================================================="); + } + + SmbSessionFactory smbSessionFactory = context.getBean("smbSession", SmbSessionFactory.class); + smbSessionFactory.setSmbMinVersion(DialectVersion.SMB210); + smbSessionFactory.setSmbMaxVersion(DialectVersion.SMB311); + + LOGGER.info("Polling from Share: " + smbSessionFactory.getUrl()); + + // Create a test text file on the SMB file share + SmbMessageHandler handlerTxt = new SmbMessageHandler(smbSessionFactory); + handlerTxt.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handlerTxt.setFileNameGenerator(message -> "handlerContent.txt"); + handlerTxt.setAutoCreateDirectory(true); + handlerTxt.setUseTemporaryFileName(false); + handlerTxt.setBeanFactory(context.getBeanFactory()); + handlerTxt.afterPropertiesSet(); + handlerTxt.handleMessage(new GenericMessage("hello, my text")); + + // Create a test binary file on the SMB file share using a temporary filename + SmbMessageHandler handlerBin = new SmbMessageHandler(smbSessionFactory); + handlerBin.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handlerBin.setFileNameGenerator(message -> "handlerContent.bin"); + handlerBin.setAutoCreateDirectory(true); + handlerBin.setUseTemporaryFileName(true); + handlerBin.setBeanFactory(context.getBeanFactory()); + handlerBin.afterPropertiesSet(); + handlerBin.handleMessage(new GenericMessage("hello, my bytes".getBytes())); + + while (true) { + final String input = scanner.nextLine(); + + if ("q".equals(input.trim())) { + scanner.close(); + context.close(); + break; + } + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Exiting application...bye."); + } + + System.exit(0); + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests-context.xml new file mode 100644 index 0000000000..9c9bf00a91 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests-context.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests.java new file mode 100644 index 0000000000..4367e00d20 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-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 static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.smb.session.SmbSessionFactory; + +/** + * @author Markus Spann + * @author Prafull Kumar Soni + * @author Artem Bilan + * @author Gregory Bragg + */ +public class SmbMessageHistoryTests extends AbstractBaseTests { + + @Test + public void testMessageHistory() throws URISyntaxException { + ClassPathXmlApplicationContext applicationContext = getApplicationContext(); + SourcePollingChannelAdapter adapter = applicationContext + .getBean("smbInboundChannelAdapter", SourcePollingChannelAdapter.class); + assertThat("smbInboundChannelAdapter").isEqualTo(adapter.getComponentName()); + assertThat("smb:inbound-channel-adapter").isEqualTo(adapter.getComponentType()); + + SmbSessionFactory smbSessionFactory = applicationContext.getBean(SmbSessionFactory.class); + + String url = smbSessionFactory.getUrl(); + URI uri = new URI(url); + assertThat("sambagu%40est:sambag%25uest").isEqualTo(uri.getRawUserInfo()); + assertThat("sambagu@est:sambag%uest").isEqualTo(uri.getUserInfo()); + + applicationContext.close(); + } +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-context.xml new file mode 100644 index 0000000000..7e39cade36 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-context.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-fail-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-fail-context.xml new file mode 100644 index 0000000000..1f40b5fb17 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-fail-context.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests.java new file mode 100644 index 0000000000..ea3315bddc --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2018 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 java.io.File; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Markus Spann + * @author Prafull Kumar Soni + * + */ +public class SmbParserInboundTests extends AbstractBaseTests { + + @Before + public void prepare() { + ensureExists("test-temp/remote-10"); + cleanUp(); + } + + @Test + public void testLocalFilesAutoCreationTrue() { + assertFileNotExists(new File("test-temp/local-10")); + new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + assertFileExists(new File("test-temp/local-10")); + assertFileNotExists(new File("test-temp/local-6")); + } + + @Test(expected = BeanCreationException.class) + public void testLocalFilesAutoCreationFalse() { + assertFileNotExists(new File("test-temp/local-6")); + new ClassPathXmlApplicationContext(getApplicationContextXmlFile("-fail"), this.getClass()) + .close(); + } + + @After + public void cleanUp() { + delete("test-temp/local-10", "test-temp/local-6"); + } + + public static void main(String[] _args) throws Exception { + new SmbParserInboundTests().cleanUp(); + runTests(SmbParserInboundTests.class, "testLocalFilesAutoCreationTrue", "testLocalFilesAutoCreationFalse"); + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests-context.xml new file mode 100644 index 0000000000..0b4b4af56e --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests-context.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests.java new file mode 100644 index 0000000000..be674c69e7 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012-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.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.PriorityBlockingQueue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.filters.CompositeFileListFilter; +import org.springframework.integration.file.filters.FileListFilter; +import org.springframework.integration.smb.filters.SmbPersistentAcceptOnceFileListFilter; +import org.springframework.integration.smb.filters.SmbSimplePatternFileListFilter; +import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizer; +import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizingMessageSource; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Markus Spann + * @author Gunnar Hillert + * @author Artem Bilan + * @author Prafull Kumar Soni + * @author Gregory Bragg + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class SmbInboundChannelAdapterParserTests { + + @Autowired + ApplicationContext applicationContext; + + @Test(timeout = 100000) + public void testSmbInboundChannelAdapterComplete() { + + final SourcePollingChannelAdapter adapter = this.applicationContext.getBean("smbInbound", SourcePollingChannelAdapter.class); + final PriorityBlockingQueue queue = TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived", PriorityBlockingQueue.class); + assertThat(queue.comparator()).isNotNull(); + assertThat("smbInbound").isEqualTo(adapter.getComponentName()); + assertThat("smb:inbound-channel-adapter").isEqualTo(adapter.getComponentType()); + assertThat(applicationContext.getBean("smbChannel")).isEqualTo(TestUtils.getPropertyValue(adapter, "outputChannel")); + SmbInboundFileSynchronizingMessageSource inbound = + (SmbInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source"); + + SmbInboundFileSynchronizer fisync = + (SmbInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer"); + assertThat(".working.tmp").isEqualTo(TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class)); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); + assertThat(remoteFileSeparator).isNotNull(); + assertThat("").isEqualTo(remoteFileSeparator); + FileListFilter filter = TestUtils.getPropertyValue(fisync, "filter", FileListFilter.class); + assertThat(filter).isNotNull(); + assertThat(filter).isInstanceOf(CompositeFileListFilter.class); + Set fileFilters = TestUtils.getPropertyValue(filter, "fileFilters", Set.class); + + Iterator filtersIterator = fileFilters.iterator(); + assertThat(filtersIterator.next()).isInstanceOf(SmbSimplePatternFileListFilter.class); + assertThat(filtersIterator.next()).isInstanceOf(SmbPersistentAcceptOnceFileListFilter.class); + Object sessionFactory = TestUtils.getPropertyValue(fisync, "remoteFileTemplate.sessionFactory"); + assertThat(SmbSessionFactory.class.isAssignableFrom(sessionFactory.getClass())).isTrue(); + + FileListFilter acceptAllFilter = this.applicationContext.getBean("acceptAllFilter", FileListFilter.class); + assertThat(TestUtils.getPropertyValue(inbound, "fileSource.scanner.filter.fileFilters", Collection.class) + .contains(acceptAllFilter)).isTrue(); + } + + @Test + public void testNoCachingSessionFactoryByDefault() { + SourcePollingChannelAdapter adapter = applicationContext.getBean("simpleAdapter", SourcePollingChannelAdapter.class); + Object sessionFactory = TestUtils.getPropertyValue(adapter, "source.synchronizer.remoteFileTemplate.sessionFactory"); + assertThat(sessionFactory).isInstanceOf(SmbSessionFactory.class); + SmbInboundFileSynchronizer fisync = + TestUtils.getPropertyValue(adapter, "source.synchronizer", SmbInboundFileSynchronizer.class); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); + assertThat(remoteFileSeparator).isNotNull(); + assertThat("/").isEqualTo(remoteFileSeparator); + } + + @Test(timeout = 10000) + public void testSmbInboundChannelAdapterCompleteNoId() { + + Map spcas = applicationContext.getBeansOfType(SourcePollingChannelAdapter.class); + SourcePollingChannelAdapter adapter = null; + for (String key : spcas.keySet()) { + if (!key.equals("smbInbound") && !key.equals("simpleAdapter")) { + adapter = spcas.get(key); + } + } + assertThat(adapter).isNotNull(); + } + + + public static class TestSessionFactoryBean implements FactoryBean { + + public SmbSessionFactory getObject() { + SmbSessionFactory smbFactory = mock(SmbSessionFactory.class); + SmbSession session = mock(SmbSession.class); + when(smbFactory.getSession()).thenReturn(session); + return smbFactory; + } + + public Class getObjectType() { + return SmbSessionFactory.class; + } + + public boolean isSingleton() { + return true; + } + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterSample-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterSample-context.xml new file mode 100644 index 0000000000..ad93ca0e34 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterSample-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundOutboundSample.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundOutboundSample.java new file mode 100644 index 0000000000..1686e1e5e6 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundOutboundSample.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-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.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; + +import org.junit.Ignore; +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.smb.AbstractBaseTests; +import org.springframework.integration.smb.inbound.SmbInboundFileSynchronizingMessageSource; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.GenericMessage; + +/** + * System tests that perform SMB access without any mocking. + * These tests are annotated with '@Ignore', as they requires real SMB share configured + * in the application context/smbClientFactory in order to succeed. + * The test cases create directories and files autonomously and perform clean-up + * on a best effort basis. + * + * @author Markus Spann + * @author Gunnar Hillert + * @author Gregory Bragg + */ +public class SmbInboundOutboundSample extends AbstractBaseTests { + + private static final String INBOUND_APPLICATION_CONTEXT_XML = "SmbInboundChannelAdapterSample-context.xml"; + private static final String OUTBOUND_APPLICATION_CONTEXT_XML = "SmbOutboundChannelAdapterSample-context.xml"; + + @Ignore("Actual SMB share must be configured in file [" + INBOUND_APPLICATION_CONTEXT_XML + "].") + @Test + public void testSmbInboundChannelAdapter() throws Exception { + String testLocalDir = "test-temp/local-4/"; + String testRemoteDir = "test-temp/remote-4/"; + + ApplicationContext ac = new ClassPathXmlApplicationContext(INBOUND_APPLICATION_CONTEXT_XML, this.getClass()); + + Object consumer = ac.getBean("smbInboundChannelAdapter"); + assertThat(consumer).isInstanceOf(SourcePollingChannelAdapter.class); + Object messageSource = TestUtils.getPropertyValue(consumer, "source"); + assertThat(messageSource).isInstanceOf(SmbInboundFileSynchronizingMessageSource.class); + + // retrieve the session factory bean to place a couple of test files remotely using a new session + SmbSessionFactory smbSessionFactory = ac.getBean("smbSessionFactory", SmbSessionFactory.class); + SmbSession smbSession = smbSessionFactory.getSession(); + + // place text files onto the share + smbSession.mkdir(testRemoteDir); + + String[] fileNames = createTestFileNames(5); + for (int i = 0; i < fileNames.length; i++) { + smbSession.write(("File [" + fileNames[i] + "] written by test case [" + getMethodName() + "].").getBytes(), + testRemoteDir + fileNames[i]); + } + + // allow time for the files to arrive locally + Thread.sleep(5000); + + // confirm the local presence of all test files + for (int i = 0; i < fileNames.length; i++) { + assertFileExists(testLocalDir + fileNames[i]).deleteOnExit(); + } + + } + + @Ignore("Actual SMB share must be configured in file [" + OUTBOUND_APPLICATION_CONTEXT_XML + "].") + @Test + public void testSmbOutboundChannelAdapter() throws Exception { + String testRemoteDir = "test-temp/remote-8/"; + String testLocalDir = "test-temp/local-8/"; + new File(testLocalDir).mkdirs(); + + String[] fileNames = createTestFileNames(5); + for (int i = 0; i < fileNames.length; i++) { + writeToFile(("File [" + fileNames[i] + "] written by test case [" + getMethodName() + "].").getBytes(), + testLocalDir + fileNames[i]); + } + + ApplicationContext ac = new ClassPathXmlApplicationContext(OUTBOUND_APPLICATION_CONTEXT_XML, this.getClass()); + + Object consumer = ac.getBean("smbOutboundChannelAdapter"); + assertThat(consumer).isInstanceOf(EventDrivenConsumer.class); + Object messageSource = TestUtils.getPropertyValue(consumer, "handler"); + assertThat(messageSource).isInstanceOf(FileTransferringMessageHandler.class); + + MessageChannel smbChannel = ac.getBean("smbOutboundChannel", MessageChannel.class); + + for (int i = 0; i < fileNames.length; i++) { + smbChannel.send(new GenericMessage(new File(testLocalDir + fileNames[i]))); + } + + Thread.sleep(3000); + + // retrieve the session factory bean to check the test files are present in the remote location + SmbSessionFactory smbSessionFactory = ac.getBean("smbSessionFactory", SmbSessionFactory.class); + SmbSession smbSession = smbSessionFactory.getSession(); + + for (int i = 0; i < fileNames.length; i++) { + String remoteFile = testRemoteDir + fileNames[i]; + assertThat(smbSession.exists(remoteFile)).as("Remote file [" + remoteFile + "] does not exist.").isTrue(); + } + + } + + private String[] createTestFileNames(int _nbTestFiles) { + String[] fileNames = new String[_nbTestFiles]; + for (int i = 0; i < fileNames.length; i++) { + fileNames[i] = "test-file-" + i + ".txt"; + } + return fileNames; + } + + public static void main(String[] _args) throws Exception { + runTests(SmbInboundOutboundSample.class, "testSmbOutboundChannelAdapter", "testSmbInboundChannelAdapter"); + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests-context.xml new file mode 100644 index 0000000000..11b84e6146 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests-context.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests.java new file mode 100644 index 0000000000..a45c6ccc6e --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-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.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.smb.AbstractBaseTests; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.MessageHandler; + +/** + * @author Markus Spann + * @author Gunnar Hillert + * @author Artem Bilan + * @author Prafull Kumar Soni + * @author Gregory Bragg + */ +public class SmbOutboundChannelAdapterParserTests extends AbstractBaseTests { + + @Test + public void testSmbOutboundChannelAdapterComplete() { + ApplicationContext ac = getApplicationContext(); + + Object consumer = ac.getBean("smbOutboundChannelAdapter"); + assertThat(consumer).isInstanceOf(EventDrivenConsumer.class); + + PublishSubscribeChannel channel = ac.getBean("smbPubSubChannel", PublishSubscribeChannel.class); + assertThat(channel).isEqualTo(TestUtils.getPropertyValue(consumer, "inputChannel")); + assertThat("smbOutboundChannelAdapter").isEqualTo(((EventDrivenConsumer) consumer).getComponentName()); + + Object messageHandler = TestUtils.getPropertyValue(consumer, "handler"); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(messageHandler, "remoteFileTemplate.remoteFileSeparator"); + assertThat(remoteFileSeparator).isNotNull(); + assertThat(".working.tmp").isEqualTo(TestUtils.getPropertyValue(messageHandler, "remoteFileTemplate.temporaryFileSuffix", String.class)); + assertThat(".").isEqualTo(remoteFileSeparator); + assertThat(ac.getBean("fileNameGenerator")).isEqualTo(TestUtils.getPropertyValue(messageHandler, "remoteFileTemplate.fileNameGenerator")); + assertThat("UTF-8").isEqualTo(TestUtils.getPropertyValue(messageHandler, "remoteFileTemplate.charset", Charset.class).name()); + + Object sessionFactoryProp = TestUtils.getPropertyValue(messageHandler, "remoteFileTemplate.sessionFactory"); + assertThat(SmbSessionFactory.class).isEqualTo(sessionFactoryProp.getClass()); + + SmbSessionFactory smbSessionFactory = (SmbSessionFactory) sessionFactoryProp; + assertThat("localhost").isEqualTo(TestUtils.getPropertyValue(smbSessionFactory, "host")); + assertThat(0).isEqualTo(TestUtils.getPropertyValue(smbSessionFactory, "port")); + assertThat(23).isEqualTo(TestUtils.getPropertyValue(messageHandler, "order")); + + // verify subscription order + @SuppressWarnings("unchecked") + Set handlers = (Set) TestUtils.getPropertyValue( + TestUtils.getPropertyValue(channel, "dispatcher"), "handlers"); + Iterator iterator = handlers.iterator(); + assertThat(TestUtils.getPropertyValue(ac.getBean("smbOutboundChannelAdapter2"), "handler")).isSameAs(iterator.next()); + assertThat(messageHandler).isSameAs(iterator.next()); + } + + @Test + public void noCachingByDefault() { + ApplicationContext ac = new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + Object adapter = ac.getBean("simpleAdapter"); + Object sfProperty = TestUtils.getPropertyValue(adapter, "handler.remoteFileTemplate.sessionFactory"); + assertThat(SmbSessionFactory.class).isEqualTo(sfProperty.getClass()); + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterSample-context.xml b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterSample-context.xml new file mode 100644 index 0000000000..a1a735fd0a --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterSample-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTests.java new file mode 100644 index 0000000000..a8e9f43ca6 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012-2017 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 static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import org.springframework.integration.smb.AbstractBaseTests; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; + +import jcifs.smb.SmbFile; + +/** + * @author Markus Spann + * @author Gunnar Hillert + * @since 1.0 + */ +public class SmbInboundRemoteFileSystemSynchronizerTests extends AbstractBaseTests { + + private SmbSession smbSession; + + private SmbSessionFactory smbSessionFactory; + + private String testLocalDir = "test-temp/local-9/"; + + private String testRemoteDir = "test-temp/remote-9/"; + + @Before + public void prepare() { + delete(testLocalDir); + ensureExists(testRemoteDir); + + smbSession = mock(SmbSession.class); + smbSessionFactory = new TestSmbSessionFactory(); + smbSessionFactory.setHost("localhost"); + smbSessionFactory.setPort(0); + smbSessionFactory.setDomain(""); + smbSessionFactory.setUsername("sambaguest"); + smbSessionFactory.setPassword("sambaguest"); + smbSessionFactory.setShareAndDir("smb-share/"); + } + +// @Test +// public void testCopyFileToLocalDir() throws Exception { +// File localDirectoy = new File(testLocalDir); +// assertFileNotExists(localDirectoy); +// +// SmbInboundFileSynchronizer synchronizer = spy(new SmbInboundFileSynchronizer(smbSessionFactory)); +// synchronizer.setDeleteRemoteFiles(true); +// synchronizer.setRemoteDirectory(testRemoteDir); +// synchronizer.setFilter(new SmbRegexPatternFileListFilter(".*\\.test$")); +// +// SmbInboundFileSynchronizingMessageSource messageSource = new SmbInboundFileSynchronizingMessageSource(synchronizer); +// messageSource.setAutoCreateLocalDirectory(true); +// +// messageSource.setLocalDirectory(localDirectoy); +// messageSource.afterPropertiesSet(); +// +// String[] testFiles = new String[] {"a.test", "b.test"}; +// +// for (String testFile : testFiles) { +// Message message = messageSource.receive(); +// assertNotNull(message); +// assertEquals(testFile, message.getPayload().getName()); +// assertFileExists(new File(testLocalDir + "/" + testFile)); +// } +// +// Message nothing = messageSource.receive(); +// assertNull(nothing); +// +// // two times because on the third receive (above) the internal queue will be empty +// verify(synchronizer, times(2)).synchronizeToLocalDirectory(localDirectoy); +// } + + class TestSmbSessionFactory extends SmbSessionFactory { + + @Override + protected SmbSession createSession() { + try { + List smbFiles = new ArrayList(); + for (String fileName : new File(testRemoteDir).list()) { + SmbFile file = smbSession.createSmbFileObject(fileName); + smbFiles.add(file); + + doAnswer(new Answer() { + + public Object answer(InvocationOnMock _invocation) throws Throwable { + String path = (String) _invocation.getArguments()[0]; + OutputStream os = (OutputStream) _invocation.getArguments()[1]; + writeToFile((this.getClass().getSimpleName() + " : TEST : " + path).getBytes(), os); + return null; + } + }).when(smbSession).read(Mockito.eq(testRemoteDir + "/" + fileName), Mockito.any(OutputStream.class)); + } + + when(smbSession.list(testRemoteDir)).thenReturn(smbFiles.toArray(new SmbFile[] { })); + when(smbSession.remove(Mockito.anyString())).thenReturn(true); + return smbSession; + + } + catch (Exception _ex) { + throw new RuntimeException("Failed to create mock session.", _ex); + } + } + } +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTests.java new file mode 100644 index 0000000000..41fb8e2c60 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2012-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 static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.smb.AbstractBaseTests; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.util.FileSystemUtils; + +import jcifs.smb.SmbFile; + +/** + * @author Markus Spann + * @author Artem Bilan + * @author Prafull Kumar Soni + * @author Gregory Bragg + */ +public class SmbSendingMessageHandlerTests extends AbstractBaseTests { + + private SmbSession smbSession; + + private SmbSessionFactory smbSessionFactory; + + @Before + public void prepare() { + smbSession = mock(SmbSession.class); + smbSessionFactory = new TestSmbSessionFactory(); + smbSessionFactory.setHost("localhost"); + smbSessionFactory.setPort(0); + smbSessionFactory.setDomain(""); + smbSessionFactory.setUsername("sambaguest"); + smbSessionFactory.setPassword("sambaguest"); + smbSessionFactory.setShareAndDir("smb-share/"); + } + + @After + public void cleanup() { + FileSystemUtils.deleteRecursively(new File("remote-target-dir")); + } + + @Test + public void testHandleFileContentMessage() { + File file = createNewFile("remote-target-dir/handlerContent.test"); + SmbMessageHandler handler = new SmbMessageHandler(smbSessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handler.setFileNameGenerator(message -> "handlerContent.test"); + handler.setAutoCreateDirectory(true); + handler.setBeanFactory(mock(BeanFactory.class)); + handler.afterPropertiesSet(); + handler.handleMessage(new GenericMessage<>("hello")); + assertFileExists(file); + } + + @Test + public void testHandleFileAsByte() { + File file = createNewFile("remote-target-dir/handlerContent.test"); + SmbMessageHandler handler = new SmbMessageHandler(smbSessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handler.setFileNameGenerator(message -> "handlerContent.test"); + handler.setAutoCreateDirectory(true); + handler.setBeanFactory(mock(BeanFactory.class)); + handler.afterPropertiesSet(); + handler.handleMessage(new GenericMessage<>("hello".getBytes())); + assertFileExists(file); + } + +// @Test +// public void testHandleFileMessage() throws Exception { +// File file = createNewFile("remote-target-dir/template.mf.test"); +// SmbMessageHandler handler = new SmbMessageHandler(smbSessionFactory); +// handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); +// handler.setFileNameGenerator(new FileNameGenerator() { +// public String generateFileName(Message message) { +// return ((File) message.getPayload()).getName() + ".test"; +// } +// }); +// handler.afterPropertiesSet(); +// handler.handleMessage(new GenericMessage(new File("template.mf"))); +// assertFileExists(file); +// } + + class TestSmbSessionFactory extends SmbSessionFactory { + + @Override + protected SmbSession createSession() { + try { + when(smbSession.remove(Mockito.anyString())).thenReturn(true); + when(smbSession.list(Mockito.anyString())).thenReturn(new SmbFile[0]); + + doAnswer(new Answer() { + + @Override + public Object answer(InvocationOnMock _invocation) throws Throwable { + String path = (String) _invocation.getArguments()[0]; + OutputStream os = (OutputStream) _invocation.getArguments()[1]; + writeToFile((this.getClass().getSimpleName() + " : TEST : " + path).getBytes(), os); + return null; + } + }).when(smbSession).read(Mockito.anyString(), Mockito.any(OutputStream.class)); + + doAnswer(_invocation -> { + InputStream inputStream = (InputStream) _invocation.getArguments()[0]; + String path = (String) _invocation.getArguments()[1]; + writeToFile(inputStream, path); + return null; + }).when(smbSession) + .write(Mockito.any(InputStream.class), Mockito.anyString()); + + // when(smbSession.write(Mockito.any(byte[].class), Mockito.anyString())).thenReturn(null); + // when(smbSession.write(Mockito.any(File.class), Mockito.anyString())).thenReturn(null); + + doAnswer((Answer) _invocation -> { + String path = (String) _invocation.getArguments()[0]; + return new File(path).mkdirs(); + }).when(smbSession).mkdir(Mockito.anyString()); + + doAnswer(_invocation -> { + String pathFrom = (String) _invocation.getArguments()[0]; + String pathTo = (String) _invocation.getArguments()[1]; + new File(pathFrom).renameTo(new File(pathTo)); + return null; + }).when(smbSession) + .rename(Mockito.anyString(), Mockito.anyString()); + + doNothing().when(smbSession).close(); + when(smbSession.isOpen()).thenReturn(true); + return smbSession; + + } + catch (Exception _ex) { + throw new RuntimeException("Failed to create mock session.", _ex); + } + } + + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionFactoryWithCIFSContextTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionFactoryWithCIFSContextTests.java new file mode 100644 index 0000000000..73f8809613 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionFactoryWithCIFSContextTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2012-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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.smb.AbstractBaseTests; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.util.FileSystemUtils; + +import jcifs.CIFSContext; +import jcifs.context.SingletonContext; +import jcifs.smb.SmbFile; + +/** + * @author Gregory Bragg + * @author Artem Bilan + */ +public class SmbSessionFactoryWithCIFSContextTests extends AbstractBaseTests { + + private SmbSession smbSession; + + private SmbSessionFactory smbSessionFactory; + + @Before + public void prepare() { + smbSession = mock(SmbSession.class); + + smbSessionFactory = new TestSmbSessionFactory(SingletonContext.getInstance()); + assertThat(smbSessionFactory).as("TestSmbSessionFactory object is null.").isNotNull(); + + smbSessionFactory.setHost("localhost"); + smbSessionFactory.setPort(445); + smbSessionFactory.setDomain(""); + smbSessionFactory.setUsername("sambaguest"); + smbSessionFactory.setPassword("sambaguest"); + smbSessionFactory.setShareAndDir("smb-share/"); + } + + @After + public void cleanup() { + FileSystemUtils.deleteRecursively(new File("remote-target-dir")); + } + + @Test + public void testHandleFileContentMessage() { + File file = createNewFile("remote-target-dir/handlerContent.test"); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler<>(smbSessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handler.setFileNameGenerator(message -> "handlerContent.test"); + handler.setAutoCreateDirectory(true); + handler.setBeanFactory(mock(BeanFactory.class)); + handler.afterPropertiesSet(); + handler.handleMessage(new GenericMessage<>("hello")); + assertFileExists(file); + } + + class TestSmbSessionFactory extends SmbSessionFactory { + + private CIFSContext context; + + protected TestSmbSessionFactory(CIFSContext _context) { + assertThat(_context).as("CIFSContext object is null.").isNotNull(); + this.context = _context; + } + + @Override + protected SmbSession createSession() { + try { + // test for a constructor with a CIFSContext + SmbShare smbShare = new SmbShare(this, this.context); + assertThat(smbShare).as("SmbShare object is null.").isNotNull(); + assertThat(smbShare.toString()).isEqualTo("smb://sambaguest:sambaguest@localhost:445/smb-share/"); + + // the rest has been copied from SmbSendingMessageHandlerTests + when(smbSession.remove(Mockito.anyString())).thenReturn(true); + when(smbSession.list(Mockito.anyString())).thenReturn(new SmbFile[0]); + + doAnswer(new Answer() { + + @Override + public Object answer(InvocationOnMock _invocation) throws Throwable { + String path = (String) _invocation.getArguments()[0]; + OutputStream os = (OutputStream) _invocation.getArguments()[1]; + writeToFile((this.getClass().getSimpleName() + " : TEST : " + path).getBytes(), os); + return null; + } + }).when(smbSession).read(Mockito.anyString(), Mockito.any(OutputStream.class)); + + doAnswer(_invocation -> { + InputStream inputStream = (InputStream) _invocation.getArguments()[0]; + String path = (String) _invocation.getArguments()[1]; + writeToFile(inputStream, path); + return null; + }).when(smbSession) + .write(Mockito.any(InputStream.class), Mockito.anyString()); + + // when(smbSession.write(Mockito.any(byte[].class), Mockito.anyString())).thenReturn(null); + // when(smbSession.write(Mockito.any(File.class), Mockito.anyString())).thenReturn(null); + + doAnswer(_invocation -> { + String path = (String) _invocation.getArguments()[0]; + return new File(path).mkdirs(); + }).when(smbSession).mkdir(Mockito.anyString()); + + doAnswer(_invocation -> { + String pathFrom = (String) _invocation.getArguments()[0]; + String pathTo = (String) _invocation.getArguments()[1]; + new File(pathFrom).renameTo(new File(pathTo)); + return null; + }).when(smbSession) + .rename(Mockito.anyString(), Mockito.anyString()); + + doNothing().when(smbSession).close(); + when(smbSession.isOpen()).thenReturn(true); + return smbSession; + } + catch (Exception _ex) { + throw new RuntimeException("Failed to create mock session.", _ex); + } + } + + } + +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionTests.java new file mode 100644 index 0000000000..ede8011b86 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/SmbSessionTests.java @@ -0,0 +1,204 @@ +/* + * Copyright 2012-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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Properties; + +import org.junit.Test; + +import jcifs.DialectVersion; +import jcifs.smb.SmbFile; + +/** + * + * @author Gunnar Hillert + * @author Gregory Bragg + * + */ +public class SmbSessionTests { + + @Test + public void testCreateSmbFileObjectWithBackSlash1() throws IOException { + System.setProperty("file.separator", "\\"); + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba\\"); + assertThat("smb://myshare/blubba/").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithBackSlash2() throws IOException { + System.setProperty("file.separator", "\\"); + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared\\"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba\\"); + assertThat("smb://myshare/blubba/").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithBackSlash3() throws IOException { + System.setProperty("file.separator", "\\"); + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared\\"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("..\\another"); + assertThat("smb://myshare:445/another").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithBackSlash4() throws IOException { + System.setProperty("file.separator", "/"); + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba\\"); + assertThat("smb://myshare/blubba/").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithMissingTrailingSlash1() throws IOException { + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba"); + assertThat("smb://myshare/blubba").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithMissingTrailingSlash2() throws IOException { + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("."); + assertThat("smb://myshare:445/shared/").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithMissingTrailingSlash3() throws IOException { + SmbConfig config = new SmbConfig(); + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + SmbShare smbShare = new SmbShare(config); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("../anotherShare"); + assertThat("smb://myshare:445/anotherShare").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithSmb3Versions1() throws IOException { + Properties props = new Properties(); + SmbConfig config = new SmbConfig(); + + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + config.setSmbMinVersion(DialectVersion.SMB300); + config.setSmbMaxVersion(DialectVersion.SMB311); + + props.setProperty("jcifs.smb.client.minVersion", config.getSmbMinVersion().name()); + props.setProperty("jcifs.smb.client.maxVersion", config.getSmbMaxVersion().name()); + + SmbShare smbShare = new SmbShare(config, props); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba"); + assertThat("smb://myshare/blubba").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithSmb3Versions2() throws IOException { + Properties props = new Properties(); + SmbConfig config = new SmbConfig(); + + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + config.setSmbMinVersion(DialectVersion.SMB302); + config.setSmbMaxVersion(DialectVersion.SMB311); + + props.setProperty("jcifs.smb.client.minVersion", config.getSmbMinVersion().name()); + props.setProperty("jcifs.smb.client.maxVersion", config.getSmbMaxVersion().name()); + + SmbShare smbShare = new SmbShare(config, props); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba"); + assertThat("smb://myshare/blubba").isEqualTo(smbFile.getPath()); + smbSession.close(); + } + + @Test + public void testCreateSmbFileObjectWithSmb3Versions3() throws IOException { + Properties props = new Properties(); + SmbConfig config = new SmbConfig(); + + config.setHost("myshare"); + config.setPort(445); + config.setShareAndDir("shared/"); + config.setSmbMinVersion(DialectVersion.SMB311); + config.setSmbMaxVersion(DialectVersion.SMB311); + + props.setProperty("jcifs.smb.client.minVersion", config.getSmbMinVersion().name()); + props.setProperty("jcifs.smb.client.maxVersion", config.getSmbMaxVersion().name()); + + SmbShare smbShare = new SmbShare(config, props); + SmbSession smbSession = new SmbSession(smbShare); + + SmbFile smbFile = smbSession.createSmbFileObject("smb://myshare\\blubba"); + assertThat("smb://myshare/blubba").isEqualTo(smbFile.getPath()); + smbSession.close(); + } +} diff --git a/spring-integration-smb/src/test/resources/META-INF/spring/integration/spring-integration-context.xml b/spring-integration-smb/src/test/resources/META-INF/spring/integration/spring-integration-context.xml new file mode 100644 index 0000000000..290203098c --- /dev/null +++ b/spring-integration-smb/src/test/resources/META-INF/spring/integration/spring-integration-context.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smb/src/test/resources/log4j2-test.xml b/spring-integration-smb/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..946a83ec13 --- /dev/null +++ b/spring-integration-smb/src/test/resources/log4j2-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +