diff --git a/.gitignore b/.gitignore index 0f182a0..5339548 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,20 @@ -*.class - -# Package Files # -*.jar -*.war -*.ear +*.iml +*.ipr +*.iws +*.msg +*/src/main/java/META-INF +.classpath +.DS_Store +.gradle +.idea +.project +.settings +bin +build +build.log +derby.log +lib +logs +nohup.out +out +target \ No newline at end of file diff --git a/spring-integration-smb/log4j.properties b/spring-integration-smb/log4j.properties new file mode 100644 index 0000000..c4672eb --- /dev/null +++ b/spring-integration-smb/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n + +log4j.category.org.springframework=WARN +log4j.category.org.springframework.integration=DEBUG diff --git a/spring-integration-smb/pom.xml b/spring-integration-smb/pom.xml new file mode 100644 index 0000000..6523c28 --- /dev/null +++ b/spring-integration-smb/pom.xml @@ -0,0 +1,253 @@ + + + 4.0.0 + + org.springframework.integration + spring-integration-smb + 2.1.1.BUILD-SNAPSHOT + Spring Integration SMB Support + + + + + baag + baag-maven-root + 1.4-SNAPSHOT + + ../../Skripte/Maven/Wurzel/ + + + + 3.0.5.RELEASE + 2.1.1.BUILD-SNAPSHOT + + + + + + scm:svn:https://eiche.baag/svn/3rdparty/spring-integration-smb/trunk/ + scm:svn:https://eiche.baag/svn/3rdparty/spring-integration-smb/trunk/ + https://eiche.baag/svn/3rdparty/spring-integration-smb/trunk/ + + + + + msp + Markus Spann + markus.spann@baaderbank.de + +1 + + + + + + + + src/main/java + + **/* + + + **/*.java + + + + + src/main/resources + + **/* + + + + + + + src/test/java + + **/* + + + **/*.java + + + + + src/test/resources + + **/* + + + + + + + + maven-surefire-plugin + + false + never + + **/*Tests.java + + + **/*Abstract*.java + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + + org.springframework.ide.eclipse.core.springbuilder + + + org.springframework.ide.eclipse.core.springnature + + + + + + + + com.mycila.maven-license-plugin + maven-license-plugin + 1.10.b1 + + ${basedir} +
${basedir}/src/main/resources/license_header.txt
+ true + + + src/**/*.java + + true + + ${project.inceptionYear} + + UTF-8 + true + +
+
+ +
+ +
+ + + + + org.samba.jcifs + jcifs + 1.3.17 + compile + + + + org.springframework + spring-core + ${spring.core.version} + compile + + + org.springframework + spring-context + ${spring.core.version} + compile + + + org.springframework + spring-context-support + ${spring.core.version} + compile + + + org.springframework + spring-beans + ${spring.core.version} + compile + + + org.springframework + spring-expression + ${spring.core.version} + compile + + + org.springframework.integration + spring-integration-core + ${spring.integration.version} + compile + + + org.springframework.integration + spring-integration-file + ${spring.integration.version} + compile + + + org.springframework.integration + spring-integration-test + ${spring.integration.version} + test + + + cglib + cglib-nodep + 2.2 + test + + + log4j + log4j + 1.2.12 + test + + + javax.activation + activation + 1.1.1 + compile + true + + + org.springframework + spring-test + ${spring.core.version} + test + + + + org.hamcrest + hamcrest-all + 1.1 + test + + + org.mockito + mockito-all + 1.8.4 + test + + + junit + junit-dep + 4.8.2 + test + + + +
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 0000000..29e0695 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParser.java @@ -0,0 +1,51 @@ +/** + * Copyright 2002-2012 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 + * + * http://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; + +/** + * Parser for the SMB 'inbound-channel-adapter' element. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbInboundChannelAdapterParser extends AbstractRemoteFileInboundChannelAdapterParser { + + private static final String BASE_PACKAGE = "org.springframework.integration.smb"; + + + @Override + protected String getMessageSourceClassname() { + return BASE_PACKAGE + ".inbound.SmbInboundFileSynchronizingMessageSource"; + } + + @Override + protected String getInboundFileSynchronizerClassname() { + return BASE_PACKAGE + ".inbound.SmbInboundFileSynchronizer"; + } + + @Override + protected String getSimplePatternFileListFilterClassname() { + return BASE_PACKAGE + ".filters.SmbSimplePatternFileListFilter"; + } + + @Override + protected String getRegexPatternFileListFilterClassname() { + return BASE_PACKAGE + ".filters.SmbRegexPatternFileListFilter"; + } + +} 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 0000000..cfef762 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/config/SmbNamespaceHandler.java @@ -0,0 +1,34 @@ +/** + * Copyright 2002-2012 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 + * + * http://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; +import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; + +/** + * Provides namespace support for using SMB. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + public void init() { + registerBeanDefinitionParser("inbound-channel-adapter", new SmbInboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new RemoteFileOutboundChannelAdapterParser()); // TODO need implementation for SMB? + } + +} 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 0000000..aa4a730 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbRegexPatternFileListFilter.java @@ -0,0 +1,59 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.regex.Pattern; + +import jcifs.smb.SmbFile; + +import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter; + +/** + * Implementation of {@link AbstractRegexPatternFileListFilter} for SMB. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter { + + private final String toString; + + public SmbRegexPatternFileListFilter(String _pattern) { + this(Pattern.compile(_pattern)); + } + + public SmbRegexPatternFileListFilter(Pattern _pattern) { + super(_pattern); + toString = getClass().getName() + "[pattern='" + _pattern + "']"; + } + + /** + * Gets the specified SMB file's name. + * @param _file SMB file object + * @return file name + * @see org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter#getFilename(java.lang.Object) + */ + @Override + protected String getFilename(SmbFile _file) { + return (_file != null) ? _file.getName() : null; + } + + @Override + public String toString() { + return toString; + } + +} 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 0000000..4d5bf3c --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/SmbSimplePatternFileListFilter.java @@ -0,0 +1,52 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 jcifs.smb.SmbFile; + +import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter; + +/** + * Implementation of {@link AbstractSimplePatternFileListFilter} for SMB. + * + * @author Markus Spann + */ +public class SmbSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter { + + private final String toString; + + public SmbSimplePatternFileListFilter(String _pathPattern) { + super(_pathPattern); + toString = getClass().getName() + "[pattern='" + _pathPattern + "']"; + } + + /** + * Gets the specified SMB file's name. + * @param _file SMB file object + * @return file name + * @see org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter#getFilename(java.lang.Object) + */ + @Override + protected String getFilename(SmbFile _file) { + return (_file != null) ? _file.getName() : null; + } + + @Override + public String toString() { + return toString; + } + +} 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 0000000..b5f0bb9 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizer.java @@ -0,0 +1,66 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 jcifs.smb.SmbFile; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; + +/** + * An implementation of {@link AbstractInboundFileSynchronizer} for SMB. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbInboundFileSynchronizer extends AbstractInboundFileSynchronizer { + + private final Log logger = LogFactory.getLog(SmbInboundFileSynchronizer.class); + + private final String toString; + + /** + * Create a synchronizer with the {@link SessionFactory} used to acquire + * {@link org.springframework.integration.file.remote.session.Session} instances. + */ + public SmbInboundFileSynchronizer(SessionFactory _sessionFactory) { + super(_sessionFactory); + toString = getClass().getName() + "[sessionFactory=" + _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 + public String toString() { + return toString; + } + +} 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 0000000..8407b99 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/inbound/SmbInboundFileSynchronizingMessageSource.java @@ -0,0 +1,58 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 jcifs.smb.SmbFile; + +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; +import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource; + +/** + * A {@link org.springframework.integration.core.MessageSource} implementation for SMB. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbInboundFileSynchronizingMessageSource extends AbstractInboundFileSynchronizingMessageSource { + + // CHECKSTYLE:OFF + private final static String componentType = "smb:inbound-channel-adapter"; + // CHECKSTYLE:ON + private final String toString; + + public SmbInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer _synchronizer) { + this(_synchronizer, null); + } + + public SmbInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer _synchronizer, Comparator _comparator) { + super(_synchronizer, _comparator); + toString = getClass().getName() + "[componentType=" + componentType + ", synchronizer=" + _synchronizer + ", comparator=" + _comparator + "]"; + } + + @Override + public String getComponentType() { + return componentType; + } + + @Override + public String toString() { + return toString; + } + +} 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 0000000..26a5856 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbConfig.java @@ -0,0 +1,183 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * 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 + * @since 2.1.1 + */ +public class SmbConfig { + + private String host; + private int port; + private String domain; + private String username; + private String password; + private String shareAndDir; + + private boolean replaceFile = false; + private boolean useTempFile = false; + + public SmbConfig() { + } + + public SmbConfig(String _host, int _port, String _domain, String _username, String _password, String _shareAndDir) throws UnsupportedEncodingException { + this(); + + 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 host; + } + + public void setPort(int _port) { + Assert.isTrue(_port >= 0, "port must be >= 0"); + this.port = _port; + } + + public int getPort() { + return port; + } + + public void setDomain(String _domain) { + Assert.notNull(_domain); + this.domain = _domain; + } + + public String getDomain() { + return domain; + } + + public void setUsername(String _username) { + Assert.hasText(_username, "username should be a non-empty string"); + this.username = _username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String _password) { + Assert.notNull(_password, "password should not be null"); + this.password = _password; + } + + public String getPassword() { + return password; + } + + public void setShareAndDir(String _shareAndDir) { + Assert.notNull(_shareAndDir, "shareAndDir should not be null"); + this.shareAndDir = _shareAndDir; + } + + public String getShareAndDir() { + return shareAndDir; + } + + public void setReplaceFile(boolean _replaceFile) { + this.replaceFile = _replaceFile; + } + + public boolean isReplaceFile() { + return replaceFile; + } + + void setUseTempFile(boolean _useTempFile) { + this.useTempFile = _useTempFile; + } + + public boolean isUseTempFile() { + return useTempFile; + } + + String getDomainUserPass(boolean _includePassword) { + String domainUserPass; + String user = _includePassword ? this.username : "********"; + if (StringUtils.hasText(this.domain)) { + domainUserPass = String.format("%s;%s", this.domain, user); + } else { + domainUserPass = user; + } + if (StringUtils.hasText(this.password)) { + domainUserPass += ":" + 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); + if (domainUserPass != null) { + try { + domainUserPass = URLEncoder.encode(domainUserPass, "UTF8"); + // CHECKSTYLE:OFF + } catch (UnsupportedEncodingException _ex) { + // CHECKSTYLE:ON + } + } + return String.format("smb://%s@%s/%s", domainUserPass, getHostPort(), StringUtils.cleanPath(this.shareAndDir)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[url=" + getUrl(false) + + ", replaceFile=" + replaceFile + + "]"; + } + +} 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 0000000..372a344 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSession.java @@ -0,0 +1,448 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.util.Arrays; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.NestedIOException; +import org.springframework.integration.file.remote.session.Session; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +/** + * 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. + * + * Inspired by the sprint-integration-ftp implementation done by Mark Fisher and Oleg Zhurakousky. + * + * @author Markus Spann + * @author Mark Fisher + * @author Oleg Zhurakousky + * + * @since 2.1.1 + */ +public class SmbSession implements Session { + + private final Log logger = LogFactory.getLog(SmbSession.class); + private static final String FILE_SEPARATOR = System.getProperty("file.separator"); + + static { + configureJcifs(); + } + + 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 user name + * @param _password case-sensitive password + * @param _shareAndDir server root SMB directory + * @param _replaceFile replace existing files if true + * @param _useTempFile make use temporary files when writing + * @throws IOException in case of I/O errors + */ + SmbSession(String _host, int _port, String _domain, String _user, String _password, String _shareAndDir, boolean _replaceFile, boolean _useTempFile) throws IOException { + this(new SmbShare(new SmbConfig(_host, _port, _domain, _user, _password, _shareAndDir))); + + smbShare.setReplaceFile(_replaceFile); + smbShare.setUseTempFile(_useTempFile); + } + + /** + * Constructor for an SMB session. + * @param _smbShare SMB share resource + */ + public SmbSession(SmbShare _smbShare) { + Assert.notNull(_smbShare, "smbShare must not be null"); + smbShare = _smbShare; + 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 + * @see org.springframework.integration.file.remote.session.Session#remove(java.lang.String) + */ + 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.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. + * @see org.springframework.integration.file.remote.session.Session#list(java.lang.String) + */ + public SmbFile[] list(String _path) throws IOException { + SmbFile[] files = new SmbFile[0]; + try { + SmbFile smbDir = createSmbDirectoryObject(_path); + if (!smbDir.exists()) { + logger.warn("Remote directory [" + _path + "] does not exist. Cannot list resources."); + return files; + } else if (!smbDir.isDirectory()) { + throw new NestedIOException("Resource [" + _path + "] is not a directory. Cannot list resources."); + } + + files = smbDir.listFiles(); + + } catch (SmbException _ex) { + throw new NestedIOException("Failed to list resources in [" + _path + "].", _ex); + } + String msg = "Successfully listed " + files.length + " resource(s) in [" + _path + "]"; + if (logger.isDebugEnabled()) { + logger.debug(msg + ": " + Arrays.toString(files)); + } else { + logger.info(msg + "."); + } + + 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. + * @see org.springframework.integration.file.remote.session.Session#read(java.lang.String, java.io.OutputStream) + */ + 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 NestedIOException("Resource [" + _path + "] is not a file."); + } + FileCopyUtils.copy(remoteFile.getInputStream(), _outputStream); + + } catch (SmbException _ex) { + throw new NestedIOException("Failed to read resource [" + _path + "].", _ex); + } + logger.info("Successfully read resource [" + _path + "]."); + } + + /** + * Writes contents of the specified {@link InputStream} to the remote resource + * specified by path. Remote directories are created implicitely 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 + * @see org.springframework.integration.file.remote.session.Session#write(java.io.InputStream, java.lang.String) + */ + 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); + + if (smbShare.isUseTempFile()) { + + String tempFileName = _path + smbShare.newTempFileSuffix(); + SmbFile tempFile = createSmbFileObject(tempFileName); + tempFile.createNewFile(); + Assert.isTrue(tempFile.canWrite(), "Temporary file [" + tempFileName + "] is not writable."); + + FileCopyUtils.copy(_inputStream, tempFile.getOutputStream()); + + if (targetFile.exists() && smbShare.isReplaceFile()) { + targetFile.delete(); + } + tempFile.renameTo(targetFile); + + } else { + + FileCopyUtils.copy(_inputStream, targetFile.getOutputStream()); + + } + + } catch (SmbException _ex) { + throw new NestedIOException("Failed to write resource [" + _path + "].", _ex); + } + logger.info("Successfully wrote remote file [" + _path + "]."); + } + + /** + * Convenience method to write a local file object to a remote location. + * @see org.springframework.integration.smb.session.SmbSession.write(InputStream, String) + */ + 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. + * @see org.springframework.integration.smb.session.SmbSession.write(InputStream, String) + */ + 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 + * @see org.springframework.integration.file.remote.session.Session#mkdir(java.lang.String) + */ + public boolean mkdir(String _path) throws IOException { + try { + SmbFile dir = createSmbDirectoryObject(_path); + if (!dir.exists()) { + dir.mkdirs(); + logger.info("Successfully created remote directory [" + _path + "] in share [" + smbShare + "]."); + } else { + logger.info("Remote directory [" + _path + "] exists in share [" + smbShare + "]."); + } + return true; + } catch (SmbException _ex) { + throw new NestedIOException("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 + * @see org.springframework.integration.file.remote.session.Session#exists(java.lang.String) + */ + 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; + } + + /** + * Renames a remote resource. + * @param _pathFrom remote source path + * @param _pathTo remote target path + * @throws IOException on error conditions returned by a CIFS server + * @see org.springframework.integration.file.remote.session.Session#rename(java.lang.String, java.lang.String) + */ + public void rename(String _pathFrom, String _pathTo) throws IOException { + try { + + SmbFile smbFileFrom = createSmbFileObject(_pathFrom); + SmbFile smbFileTo = createSmbFileObject(_pathTo); + if (smbShare.isReplaceFile() && smbFileTo.exists()) { + smbFileTo.delete(); + } + smbFileFrom.renameTo(smbFileTo); + + } catch (SmbException _ex) { + throw new NestedIOException("Failed to rename [" + _pathFrom + "] to [" + _pathTo + "].", _ex); + } + logger.info("Successfully renamed remote resource [" + _pathFrom + "] to [" + _pathTo + "]."); + + } + + /** + * Closes this SMB session. + * @see org.springframework.integration.file.remote.session.Session#close() + */ + public void close() { + smbShare.doClose(); + } + + /** + * 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 + * @see org.springframework.integration.file.remote.session.Session#isOpen() + */ + public boolean isOpen() { + if (!smbShare.isOpened()) { + return false; + } + try { + smbShare.listFiles(); + } catch (Exception _ex) { + close(); + } + return 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 { + String path = StringUtils.cleanPath(_path); + if (!StringUtils.hasText(path)) { + return smbShare; + } + + SmbFile smbFile = new SmbFile(smbShare, path); + + boolean appendFileSeparator = !path.endsWith(FILE_SEPARATOR); + if (appendFileSeparator) { + try { + appendFileSeparator = smbFile.isDirectory() || (_isDirectory != null && _isDirectory); + } catch (Exception _ex) { + appendFileSeparator = false; + } + } + if (appendFileSeparator) { + smbFile = createSmbFileObject(path + 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. + */ + public SmbFile createSmbFileObject(String _path) throws IOException { + return createSmbFileObject(_path, null); + } + + /** + * Creates an SMB file object pointing to a remote directory. + */ + public SmbFile createSmbDirectoryObject(String _path) throws IOException { + return createSmbFileObject(_path, true); + } + + /** + * Static configuration of the JCIFS library. + * The log level of this class is mapped to a suitable jcifs.util.loglevel + */ + static void configureJcifs() { + // TODO jcifs.Config.setProperty("jcifs.smb.client.useExtendedSecurity", "false"); + // TODO jcifs.Config.setProperty("jcifs.smb.client.disablePlainTextPasswords", "false"); + + // set JCIFS SMB client library' log level unless already configured by system property + final String sysPropLogLevel = "jcifs.util.loglevel"; + + if (jcifs.Config.getProperty(sysPropLogLevel) == null) { + // set log level according to this class' logger's log level. + Log log = LogFactory.getLog(SmbSession.class); + if (log.isTraceEnabled()) { + jcifs.Config.setProperty(sysPropLogLevel, "N"); + } else if (log.isDebugEnabled()) { + jcifs.Config.setProperty(sysPropLogLevel, "3"); + } else { + jcifs.Config.setProperty(sysPropLogLevel, "1"); + } + } + } + +} 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 0000000..cf89a49 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbSessionFactory.java @@ -0,0 +1,66 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 jcifs.smb.SmbFile; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.file.remote.session.SessionFactory; + +/** + * The SMB session factory. + * + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbSessionFactory extends SmbConfig implements SessionFactory { + + private final Log logger = LogFactory.getLog(this.getClass()); + + public SmbSessionFactory() { + logger.debug("New " + getClass().getName() + " created."); + } + + 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 = new SmbShare((SmbConfig) this); + smbShare.setReplaceFile(this.isReplaceFile()); + smbShare.setUseTempFile(this.isUseTempFile()); + + logger.info(String.format("SMB share init: %s/%s", getHostPort(), getShareAndDir())); + + smbShare.init(); + logger.debug("SMB share initialized."); + + return new SmbSession(smbShare); + } + + @Override + public String toString() { + return super.toString(); + } + +} 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 0000000..4021901 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbShare.java @@ -0,0 +1,104 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.concurrent.atomic.AtomicBoolean; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.NestedIOException; +import org.springframework.util.Assert; + +public class SmbShare extends SmbFile { + + private final Log logger = LogFactory.getLog(SmbShare.class); + + private final AtomicBoolean open = new AtomicBoolean(false); + + private final AtomicBoolean replaceFile = new AtomicBoolean(false); + + private final AtomicBoolean useTempFile = new AtomicBoolean(false); + + public SmbShare(String _url) throws IOException { + super(_url); + } + + public SmbShare(SmbConfig _smbConfig) throws IOException { + this(_smbConfig.validate().getUrl()); + } + + public void init() throws NestedIOException { + boolean canRead = false; + try { + if (!exists()) { + logger.info("SMB root directory does not exist. Creating it."); + mkdirs(); + } + canRead = canRead(); + } catch (SmbException _ex) { + throw new NestedIOException("Unable to initialize share: " + this, _ex); + } + Assert.isTrue(canRead, "Share is not accessible " + this); + open.set(true); + } + + public boolean isReplaceFile() { + return replaceFile.get(); + } + + public void setReplaceFile(boolean _replace) { + this.replaceFile.set(_replace); + } + + public boolean isUseTempFile() { + return useTempFile.get(); + } + + public void setUseTempFile(boolean _useTempFile) { + this.useTempFile.set(_useTempFile); + } + + /** + * Checks whether the share is accessible. + * Note: jcifs.smb.SmbFile defines a package-protected method isOpen(). + * @return true if open + */ + boolean isOpened() { + return open.get(); + } + + /** + * Set the open state to closed. + * Note: jcifs.smb.SmbFile defines a package-protected method close(). + */ + void doClose() { + open.set(false); + } + + public String newTempFileSuffix() { + return "-" + Long.toHexString(Double.doubleToLongBits(Math.random())) + ".tmp"; + } + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbUtils.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbUtils.java new file mode 100644 index 0000000..cb94da6 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/session/SmbUtils.java @@ -0,0 +1,50 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; + +public abstract class SmbUtils { + + private SmbUtils() { + } + + /** + * Read the specified file into a byte array. + * @param _file file + * @return byte array of file contents + * @throws IOException + */ + public static byte[] readFile(File _file) throws IOException { + FileInputStream stream = new FileInputStream(_file); + try { + FileChannel fc = stream.getChannel(); + MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + return bb.array(); + + /* Instead of using default, pass in a decoder. */ + // return Charset.defaultCharset().decode(bb).toString(); + + } finally { + stream.close(); + } + } + +} 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 0000000..3ccaf0c --- /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 0000000..1f406d7 --- /dev/null +++ b/spring-integration-smb/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/smb/spring-integration-smb-2.0.xsd=org/springframework/integration/smb/config/spring-integration-smb-2.0.xsd +http\://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd=org/springframework/integration/smb/config/spring-integration-smb-2.0.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 0000000..b448ab4 --- /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/license_header.txt b/spring-integration-smb/src/main/resources/license_header.txt new file mode 100644 index 0000000..49a6b55 --- /dev/null +++ b/spring-integration-smb/src/main/resources/license_header.txt @@ -0,0 +1,13 @@ +Copyright 2002-2012 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 + + http://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. diff --git a/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb-2.0.xsd b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb-2.0.xsd new file mode 100644 index 0000000..627fcc0 --- /dev/null +++ b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb-2.0.xsd @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + 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 + [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: '/' + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Identifies channel attached to this adapter. Depending on the type of the adapter + this channel could be the receiving channel (e.g., outbound-channel-adapter) or channel where + messages will be sent to by this adapter (e.g., inbound-channel-adapter). + + + + + + + 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/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 0000000..66a798e Binary files /dev/null and b/spring-integration-smb/src/main/resources/org/springframework/integration/smb/config/spring-integration-smb.gif differ diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTest.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTest.java new file mode 100644 index 0000000..aac00a8 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/AbstractBaseTest.java @@ -0,0 +1,283 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +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 + */ +public abstract class AbstractBaseTest { + + /** Instance logger. */ + private final Log logger = LogFactory.getLog(this.getClass()); + + protected final Log getLogger() { + return logger; + } + + // CHECKSTYLE:OFF + @Rule // requires JUnit 4.7 or later + public final TestName testMethodName = new TestName(); + // CHECKSTYLE:ON + + 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) { + assertNotNull("File object is null.", _file); + if (_exists) { + assertTrue("File [" + _file.getAbsolutePath() + "] does not exist.", _file.exists()); + } else { + assertTrue("File [" + _file.getAbsolutePath() + "] exists.", !_file.exists()); + } + 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 + */ + protected static void runTests(Class _testClass, String... _methodNames) { + AbstractBaseTest test; + Method[] methods = new Method[_methodNames.length]; + String methodName = null; + + try { + test = _testClass.newInstance(); + for (int i = 0; i < _methodNames.length; i++) { + methodName = _methodNames[i]; + methods[i] = _testClass.getMethod(methodName, (Class[]) null); + } + } catch (Exception _ex) { + System.err.println("Test setup failed for " + _testClass + "." + methodName + "()."); + _ex.printStackTrace(); + return; + } + + Method method = null; + try { + + for (int i = 0; i < methods.length; i++) { + method = methods[i]; + method.invoke(test, (Object[]) null); + } + + } catch (Exception _ex) { + System.err.println("Test execution failed for " + _testClass + "." + method.getName() + "."); + _ex.printStackTrace(); + } + + } + +} 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 0000000..3e4fcb9 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests-context.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..1da73c1 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbMessageHistoryTests.java @@ -0,0 +1,34 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; + +/** + * @author Markus Spann + * + */ +public class SmbMessageHistoryTests extends AbstractBaseTest { + @Test + public void testMessageHistory() throws Exception { + SourcePollingChannelAdapter adapter = getApplicationContext().getBean("smbInboundChannelAdapter", SourcePollingChannelAdapter.class); + assertEquals("smbInboundChannelAdapter", adapter.getComponentName()); + assertEquals("smb:inbound-channel-adapter", adapter.getComponentType()); + } +} 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 0000000..bab68cd --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-context.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..47ed218 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests-fail-context.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..04181c7 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/SmbParserInboundTests.java @@ -0,0 +1,62 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 + * + */ +public class SmbParserInboundTests extends AbstractBaseTest { + + @Before + public void prepare() { + ensureExists("test-temp/remote-10"); + cleanUp(); + } + + @Test + public void testLocalFilesAutoCreationTrue() throws Exception { + 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() throws Exception { + assertFileNotExists(new File("test-temp/local-6")); + new ClassPathXmlApplicationContext(getApplicationContextXmlFile("-fail"), this.getClass()); + } + + @After + public void cleanUp() { + delete("test-temp/local-10", "test-temp/local-6"); + } + + public static void main(String[] _args) { + 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 0000000..ad996a5 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests-context.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..1d603eb --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterParserTests.java @@ -0,0 +1,120 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Comparator; +import java.util.Map; + +import org.junit.Test; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.smb.AbstractBaseTest; +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; + +/** + * @author Markus Spann + */ +public class SmbInboundChannelAdapterParserTests extends AbstractBaseTest { + + @SuppressWarnings("unchecked") + @Test(timeout = 10000) + public void testSmbInboundChannelAdapterComplete() throws Exception{ + ApplicationContext ac = + new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + SourcePollingChannelAdapter adapter = ac.getBean("smbInbound", SourcePollingChannelAdapter.class); + Comparator comparator = TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived.q.comparator", Comparator.class); + assertNotNull(comparator); + assertEquals("smbInbound", adapter.getComponentName()); + assertEquals("smb:inbound-channel-adapter", adapter.getComponentType()); + assertNotNull(TestUtils.getPropertyValue(adapter, "poller")); + assertEquals(ac.getBean("smbChannel"), TestUtils.getPropertyValue(adapter, "outputChannel")); + SmbInboundFileSynchronizingMessageSource inbound = + (SmbInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source"); + + SmbInboundFileSynchronizer fisync = + (SmbInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer"); + assertEquals(".working.tmp", TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class)); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); + assertNotNull(remoteFileSeparator); + assertEquals("", remoteFileSeparator); + SmbSimplePatternFileListFilter filter = (SmbSimplePatternFileListFilter) TestUtils.getPropertyValue(fisync, "filter"); + assertNotNull(filter); + Object sessionFactory = TestUtils.getPropertyValue(fisync, "sessionFactory"); + assertTrue(SmbSessionFactory.class.isAssignableFrom(sessionFactory.getClass())); + } + + @Test(timeout = 10000) + public void cachingSessionFactoryByDefault() throws Exception{ + ApplicationContext ac = new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + SourcePollingChannelAdapter adapter = ac.getBean("simpleAdapter", SourcePollingChannelAdapter.class); + Object sessionFactory = TestUtils.getPropertyValue(adapter, "source.synchronizer.sessionFactory"); + assertEquals(CachingSessionFactory.class, sessionFactory.getClass()); + SmbInboundFileSynchronizer fisync = + TestUtils.getPropertyValue(adapter, "source.synchronizer", SmbInboundFileSynchronizer.class); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); + assertNotNull(remoteFileSeparator); + assertEquals("/", remoteFileSeparator); + } + + @Test(timeout = 10000) + public void testSmbInboundChannelAdapterCompleteNoId() throws Exception{ + ApplicationContext ac = + new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + Map spcas = ac.getBeansOfType(SourcePollingChannelAdapter.class); + SourcePollingChannelAdapter adapter = null; + for (String key : spcas.keySet()) { + if (!key.equals("smbInbound") && !key.equals("simpleAdapter")){ + adapter = spcas.get(key); + } + } + assertNotNull(adapter); + } + + + public static class TestSessionFactoryBean implements FactoryBean { + + public SmbSessionFactory getObject() throws Exception { + 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 0000000..e9efed5 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundChannelAdapterSample-context.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + 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 0000000..ae5e88f --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbInboundOutboundSample.java @@ -0,0 +1,137 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertTrue; + +import java.io.File; + +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.smb.AbstractBaseTest; +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; + +/** + * 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 + */ +public class SmbInboundOutboundSample extends AbstractBaseTest { + + private static final String INBOUND_APPLICATION_CONTEXT_XML = "SmbInboundChannelAdapterSample-context.xml"; + private static final String OUTBOUND_APPLICATION_CONTEXT_XML = "SmbOutboundChannelAdapterSample-context.xml"; + + @org.junit.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"); + assertTrue(consumer instanceof SourcePollingChannelAdapter); + Object messageSource = TestUtils.getPropertyValue(consumer, "source"); + assertTrue(messageSource instanceof SmbInboundFileSynchronizingMessageSource); + + // retrieve the session factory bean to place a couple of test files remotely using a new session + SmbSessionFactory smbSessionFactory = ac.getBean("smbSessionFactory", SmbSessionFactory.class); + smbSessionFactory.setReplaceFile(true); + 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(); + } + + } + + @org.junit.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"); + assertTrue(consumer instanceof EventDrivenConsumer); + Object messageSource = TestUtils.getPropertyValue(consumer, "handler"); + assertTrue(messageSource instanceof FileTransferringMessageHandler); + + 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); + smbSessionFactory.setReplaceFile(false); + SmbSession smbSession = smbSessionFactory.getSession(); + + for (int i = 0; i < fileNames.length; i++) { + String remoteFile = testRemoteDir + fileNames[i]; + assertTrue("Remote file [" + remoteFile + "] does not exist.", smbSession.exists(remoteFile)); + } + + } + + 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) { + 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 0000000..fdea976 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests-context.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..99b0204 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterParserTests.java @@ -0,0 +1,89 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertSame; + +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.core.MessageHandler; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.smb.AbstractBaseTest; +import org.springframework.integration.smb.session.SmbSessionFactory; +import org.springframework.integration.test.util.TestUtils; + +/** + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbOutboundChannelAdapterParserTests extends AbstractBaseTest { + + @Test + public void testSmbOutboundChannelAdapterComplete() throws Exception { + ApplicationContext ac = getApplicationContext(); + + Object consumer = ac.getBean("smbOutboundChannelAdapter"); + assertTrue(consumer instanceof EventDrivenConsumer); + + PublishSubscribeChannel channel = ac.getBean("smbPubSubChannel", PublishSubscribeChannel.class); + assertEquals(channel, TestUtils.getPropertyValue(consumer, "inputChannel")); + assertEquals("smbOutboundChannelAdapter", ((EventDrivenConsumer) consumer).getComponentName()); + + Object messageHandler = TestUtils.getPropertyValue(consumer, "handler"); + String remoteFileSeparator = (String) TestUtils.getPropertyValue(messageHandler, "remoteFileSeparator"); + assertNotNull(remoteFileSeparator); + assertEquals(".working.tmp", TestUtils.getPropertyValue(messageHandler, "temporaryFileSuffix", String.class)); + assertEquals(".", remoteFileSeparator); + assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(messageHandler, "fileNameGenerator")); + assertEquals("UTF-8", TestUtils.getPropertyValue(messageHandler, "charset")); + assertNotNull(TestUtils.getPropertyValue(messageHandler, "temporaryDirectory")); + + Object sessionFactoryProp = TestUtils.getPropertyValue(messageHandler, "sessionFactory"); + assertEquals(SmbSessionFactory.class, sessionFactoryProp.getClass()); + + SmbSessionFactory smbSessionFactory = (SmbSessionFactory) sessionFactoryProp; + assertEquals("localhost", TestUtils.getPropertyValue(smbSessionFactory, "host")); + assertEquals(0, TestUtils.getPropertyValue(smbSessionFactory, "port")); + assertEquals(23, TestUtils.getPropertyValue(messageHandler, "order")); + + // verify subscription order + @SuppressWarnings("unchecked") + Set handlers = (Set) TestUtils.getPropertyValue(TestUtils.getPropertyValue(channel, "dispatcher"), "handlers"); + Iterator iterator = handlers.iterator(); + assertSame(TestUtils.getPropertyValue(ac.getBean("smbOutboundChannelAdapter2"), "handler"), iterator.next()); + assertSame(messageHandler, iterator.next()); + } + + @Test + public void cachingByDefault() { + ApplicationContext ac = new ClassPathXmlApplicationContext(getApplicationContextXmlFile(), this.getClass()); + Object adapter = ac.getBean("simpleAdapter"); + Object sfProperty = TestUtils.getPropertyValue(adapter, "handler.sessionFactory"); + assertEquals(CachingSessionFactory.class, sfProperty.getClass()); + Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory"); + assertEquals(SmbSessionFactory.class, innerSfProperty.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 0000000..b0bff06 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/config/SmbOutboundChannelAdapterSample-context.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTest.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTest.java new file mode 100644 index 0000000..436dd48 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/inbound/SmbInboundRemoteFileSystemSynchronizerTest.java @@ -0,0 +1,134 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import jcifs.smb.SmbFile; + +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.integration.Message; +import org.springframework.integration.smb.AbstractBaseTest; +import org.springframework.integration.smb.filters.SmbRegexPatternFileListFilter; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; + +/** + * @author Markus Spann + * @since 2.1.1 + */ +public class SmbInboundRemoteFileSystemSynchronizerTest extends AbstractBaseTest { + + 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/"); + smbSessionFactory.setReplaceFile(true); + } + + @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/SmbSendingMessageHandlerTest.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTest.java new file mode 100644 index 0000000..546c7c5 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/outbound/SmbSendingMessageHandlerTest.java @@ -0,0 +1,167 @@ +/** + * Copyright 2002-2012 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 + * + * http://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 jcifs.smb.SmbFile; + +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.expression.common.LiteralExpression; +import org.springframework.integration.Message; +import org.springframework.integration.file.FileNameGenerator; +import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.smb.AbstractBaseTest; +import org.springframework.integration.smb.session.SmbSession; +import org.springframework.integration.smb.session.SmbSessionFactory; + +/** + * @author Markus Spann + */ +public class SmbSendingMessageHandlerTest extends AbstractBaseTest { + + 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/"); + smbSessionFactory.setReplaceFile(true); + } + + @Test + public void testHandleFileContentMessage() throws Exception { + File file = createNewFile("remote-target-dir/handlerContent.test"); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler(smbSessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handler.setFileNameGenerator(new FileNameGenerator() { + public String generateFileName(Message message) { + return "handlerContent.test"; + } + }); + handler.afterPropertiesSet(); + handler.handleMessage(new GenericMessage("hello")); + assertFileExists(file); + } + + @Test + public void testHandleFileAsByte() throws Exception { + File file = createNewFile("remote-target-dir/handlerContent.test"); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler(smbSessionFactory); + handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir")); + handler.setFileNameGenerator(new FileNameGenerator() { + public String generateFileName(Message message) { + return "handlerContent.test"; + } + }); + 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"); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler(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() { + 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(new Answer() { + public Object answer(InvocationOnMock _invocation) throws Throwable { + 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(new Answer() { + public Object answer(InvocationOnMock _invocation) throws Throwable { + String path = (String) _invocation.getArguments()[0]; + new File(path).mkdirs(); + return null; + } + }).when(smbSession).mkdir(Mockito.anyString()); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock _invocation) throws Throwable { + 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/MySmbSessionTest.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/MySmbSessionTest.java new file mode 100644 index 0000000..5479e3d --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/session/MySmbSessionTest.java @@ -0,0 +1,59 @@ +/** + * Copyright 2002-2012 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 + * + * http://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.junit.Assert.fail; + +import org.junit.Test; + +public class MySmbSessionTest { + + @Test + public void testRemove() { + fail("Not yet implemented"); + } + + @Test + public void testRead() { + fail("Not yet implemented"); + } + + @Test + public void testWrite() { + fail("Not yet implemented"); + } + + @Test + public void testMkdir() { + fail("Not yet implemented"); + } + + @Test + public void testRename() { + fail("Not yet implemented"); + } + + @Test + public void testClose() { + fail("Not yet implemented"); + } + + @Test + public void testIsOpen() { + fail("Not yet implemented"); + } + +} diff --git a/spring-integration-smb/src/test/resources/log4j.properties b/spring-integration-smb/src/test/resources/log4j.properties new file mode 100644 index 0000000..f336d51 --- /dev/null +++ b/spring-integration-smb/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n + +log4j.category.org.springframework=WARN +log4j.category.org.springframework.integration=DEBUG +log4j.category.org.springframework.integration.file=DEBUG diff --git a/spring-integration-smb/template.mf b/spring-integration-smb/template.mf new file mode 100644 index 0000000..2072d27 --- /dev/null +++ b/spring-integration-smb/template.mf @@ -0,0 +1,16 @@ +Bundle-SymbolicName: org.springframework.integration.smb +Bundle-Name: Spring Integration SMB Support +Bundle-Vendor: SpringSource +Bundle-Version: ${version} +Bundle-ManifestVersion: 2 +Import-Template: + org.apache.commons.logging;version="[1.1.1, 2.0.0)", + org.apache.commons.net.*;version="[2.0.0, 3.0.0)", + org.springframework.integration.*;version="[2.0.5, 2.0.6)", + org.springframework.beans.*;version="[3.0.5, 4.0.0)", + org.springframework.context;version="[3.0.5, 4.0.0)", + org.springframework.core.*;version="[3.0.5, 4.0.0)", + org.springframework.scheduling.*;version="[3.0.5, 4.0.0)", + org.springframework.util;version="[3.0.5, 4.0.0)", + javax.*;version="0", + org.w3c.dom.*;version="0"