org.springframework.integration.smb.session.SmbSession.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.smb.session.SmbSession.java

Source

/**
 * 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 <a href="http://en.wikipedia.org/wiki/Server_Message_Block">Server Message Block</a>
 * for more details.
 *
 * Inspired by the spring-integration-ftp implementation done by Mark Fisher
 * and Oleg Zhurakousky.
 *
 * @author Markus Spann
 * @author Mark Fisher
 * @author Oleg Zhurakousky
 *
 * @since 1.0
 */
public class SmbSession implements Session<SmbFile> {

    private final Log logger = LogFactory.getLog(SmbSession.class);
    private static final String FILE_SEPARATOR = System.getProperty("file.separator");
    private static final String SMB_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 {

        final String cleanedPath = StringUtils.cleanPath(path);

        if (!StringUtils.hasText(cleanedPath)) {
            return smbShare;
        }

        SmbFile smbFile = new SmbFile(smbShare, cleanedPath);

        boolean appendFileSeparator = !cleanedPath.endsWith(SMB_FILE_SEPARATOR);
        if (appendFileSeparator) {
            try {
                appendFileSeparator = smbFile.isDirectory() || (isDirectory != null && isDirectory);
            } catch (SmbException ex) {
                appendFileSeparator = false;
            }
        }
        if (appendFileSeparator) {
            smbFile = createSmbFileObject(cleanedPath + SMB_FILE_SEPARATOR);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Created new " + SmbFile.class.getName() + "[" + smbFile + "] for path [" + path + "].");
        }
        return smbFile;
    }

    /**
     * Creates an SMB file object pointing to a remote file.
     */
    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 <code>jcifs.util.loglevel</code>
     */
    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");
            }
        }
    }

    @Override
    public String[] listNames(String path) throws IOException {
        throw new UnsupportedOperationException("Not implemented yet");
    }

}