All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mule.modules.ftpclient.sftp.SftpClientWrapper Maven / Gradle / Ivy

There is a newer version: 1.0.8
Show newest version
package org.mule.modules.ftpclient.sftp;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.function.Consumer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.mule.api.ConnectionException;
import org.mule.api.ConnectionExceptionCode;
import org.mule.model.streaming.CallbackOutputStream;
import org.mule.modules.ftpclient.AutoCloseOnEOFInputStream;
import org.mule.modules.ftpclient.AutoCloseOnEOFInputStream.ConsumerWithIOException;
import org.mule.modules.ftpclient.ClientWrapper;
import org.mule.modules.ftpclient.FtpFileType;
import org.mule.modules.ftpclient.RemoteFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class SftpClientWrapper extends ClientWrapper {
    public static final String CHANNEL_SFTP = "sftp";
    public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";

    private static final Logger LOGGER = LoggerFactory.getLogger(SftpClientWrapper.class);

    private GenericObjectPool pool;
    private ChannelSftp channel;

    public SftpClientWrapper(GenericObjectPool pool, ChannelSftp channel) {
        this.pool = pool;
        this.channel = channel;
    }

    @Override
    public void destroy() throws JSchException {
        if (channel != null) {
            Session session = channel.getSession();
            channel.disconnect();
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
        }
    }

    @Override
    public boolean validate() {
        return channel.isConnected();
    }

    @Override
    public OutputStream getOutputStream(String directory, String filename) throws IOException, SftpException {
        changeWorkingDirectory(directory, true);
        try {
            OutputStream out = channel.put(filename);
            return new CallbackOutputStream(out, new CallbackOutputStream.Callback() {
                boolean closed = false;

                @Override
                public void onClose() {
                    if (!closed) {
                        closed = true;
                        try {
                            pool.returnObject(SftpClientWrapper.this);
                        } catch (Exception e) {
                            LOGGER.debug("ignore exception in cleanup", e);
                        }
                    }
                }
            });

        } catch (RuntimeException | SftpException e) {
            try {
                pool.returnObject(this);
            } catch (Exception e2) {
                LOGGER.error("Error returning " + this + " to pool " + pool + " while another exception occured", e2);
            }
            throw e;
        }
    }

    @Override
    public InputStream getInputStream(final String directory, final String filename,
            final Consumer onClose) throws SftpException, IOException {
        changeWorkingDirectory(directory, false);
        try {
            final InputStream is = channel.get(filename);
            return new AutoCloseOnEOFInputStream(is, new ConsumerWithIOException() {
                @Override
                public void apply() {
                    try {
                        onClose.accept(SftpClientWrapper.this);
                        pool.returnObject(SftpClientWrapper.this);
                    } catch (Exception e) {
                        LOGGER.debug("ignore exception in cleanup", e);
                    }
                }
            });
        } catch (RuntimeException | SftpException e) {
            try {
                pool.returnObject(this);
            } catch (Exception e2) {
                LOGGER.error("Error returning " + this + " to pool " + pool + " while another exception occured", e2);
            }
            throw e;
        }
    }

    @Override
    public void delete(String directory, String filename) throws IOException {
        changeWorkingDirectory(directory, false);
        try {
            channel.rm(filename);
        } catch (SftpException e) {
            throw new IOException("Can't delete " + directory + "/" + filename, e);
        }
    }

    @Override
    public void move(String fromCompletePath, String toCompletePath) throws SftpException {
        channel.rename(fromCompletePath, toCompletePath);
    }

    @Override
    public List list(String directory) throws IOException {
        changeWorkingDirectory(directory, false);
        List fileList = new ArrayList<>();
        try {
            @SuppressWarnings("unchecked")
            Collection files = channel.ls(".");
            for (LsEntry e : files) {
                String name = e.getFilename();
                if (".".equals(name) || "..".equals(name)) {
                    continue;
                }
                SftpATTRS attrs = e.getAttrs();
                FtpFileType type;
                if (attrs.isDir()) {
                    type = FtpFileType.DIRECTORY;
                } else if (attrs.isLink()) {
                    type = FtpFileType.SYMBOLIC_LINK;
                } else if (attrs.isReg()) {
                    type = FtpFileType.FILE;
                } else {
                    type = FtpFileType.UNKNOWN;
                }
                fileList.add(new RemoteFile(type, name, attrs.getSize(), new Date(attrs.getMTime() * 1000L)));
            }
        } catch (SftpException e) {
            throw invalidate(new IOException("Can't list " + directory, e));
        }
        return fileList;
    }

    @Override
    protected void changeToAbsoluteDirectory(String directory, boolean create) throws IOException {
        try {
            channel.cd(directory);
        } catch (SftpException e) {
            // We may fail because the directory does not exist: Just create and
            // try again
            if (create) {
                try {
                    channel.cd("/");
                } catch (SftpException e1) {
                    throw invalidate(new IOException(e));
                }
                for (String d : split(directory)) {
                    changeToChildDirectory(d, true);
                }
            } else {
                throw invalidate(new IOException(e));
            }
        }
    }

    @Override
    protected void changeToParentDirectory() throws IOException {
        try {
            channel.cd("..");
        } catch (SftpException e) {
            throw invalidate(new IOException(e));
        }
    }

    @Override
    protected void changeToChildDirectory(String name, boolean create) throws IOException {
        try {
            channel.cd(name);
        } catch (SftpException e) {
            // We may fail because the directory does not exist: Just create and
            // try again
            if (create) {
                createDirectory(name);
                changeToChildDirectory(name, false);
            } else {
                throw invalidate(new IOException(e));
            }
        }
    }

    @Override
    protected void createDirectory(String name) throws IOException {
        try {
            channel.mkdir(name);
        } catch (SftpException e) {
            throw invalidate(new IOException(e));
        }
    }

    public static ChannelSftp createChannel(JSch jsch, String host, int port, String knownHostsFile, String user,
            String password) throws ConnectionException {
        try {
            Properties props = new Properties();
            configureHostChecking(jsch, knownHostsFile, props);

            Session session = jsch.getSession(user, host);
            session.setConfig(props);
            session.setPort(port);
            session.setPassword(password);
            session.setTimeout(0);
            session.connect();
            return (ChannelSftp) session.openChannel(CHANNEL_SFTP);
        } catch (JSchException e) {
            translateException(e, host, port, user);
            return null; // not reached, but compiler doesn't know
        }
    }

    public static ChannelSftp createChannel(JSch jsch, String host, int port, String knownHostsFile, String user,
            String identityFile, String identityResource, String passphrase) throws ConnectionException {
        try {
            if (StringUtils.isNotBlank(identityResource)) {
                addIdentityFromResource(jsch, identityResource, passphrase);
            } else {
                addIdentity(jsch, new File(identityFile), passphrase);
            }
            Properties props = new Properties();
            configureHostChecking(jsch, knownHostsFile, props);

            Session session = jsch.getSession(user, host);
            session.setConfig(props);
            session.setPort(port);
            session.setTimeout(0);
            session.connect();
            return (ChannelSftp) session.openChannel(CHANNEL_SFTP);
        } catch (JSchException e) {
            translateException(e, host, port, user);
            return null; // not reached, but compiler doesn't know
        }
    }

    private static void addIdentityFromResource(JSch jsch, String identityResource, String passphrase)
            throws JSchException, ConnectionException {
        File file = null;
        try {
            file = File.createTempFile(identityResource, "tmp");
            try (InputStream is = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream(identityResource.trim()); OutputStream os = new FileOutputStream(file)) {
                if (is == null) {
                    throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, "",
                            "Can't load classpath resource " + identityResource);
                }
                IOUtils.copy(is, os);
                addIdentity(jsch, file, passphrase);
            } finally {
                if (file != null) {
                    file.delete();
                }
            }
        } catch (IOException e) {
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, e.getMessage(),
                    "Can't copy " + identityResource + " to temp file");
        } finally {
            if (file != null) {
                file.delete();
            }
        }
    }

    private static void addIdentity(JSch jsch, File file, String passphrase) throws JSchException {
        if (StringUtils.isEmpty(passphrase)) {
            jsch.addIdentity(file.getAbsolutePath());
        } else {
            jsch.addIdentity(file.getAbsolutePath(), passphrase);
        }
    }

    private static void configureHostChecking(JSch jsch, String knownHostsFile, Properties props) throws JSchException {
        if (StringUtils.isNotBlank(knownHostsFile)) {
            props.put(STRICT_HOST_KEY_CHECKING, "ask");
            jsch.setKnownHosts(knownHostsFile);
        } else {
            props.put(STRICT_HOST_KEY_CHECKING, "no");
        }
    }

    private IOException invalidate(IOException ioe) {
        try {
            destroy();
            pool.invalidateObject(this);
        } catch (Exception e) {
            LOGGER.error("ignore on cleanup", e);
        }
        return ioe;
    }

    public static void translateException(JSchException e, String host, int port, String user)
            throws ConnectionException {
        if (e.getCause() instanceof ConnectException) {
            throw new ConnectionException(ConnectionExceptionCode.CANNOT_REACH, "",
                    "Could not connect to ftp server: " + port + "@" + host, e);
        } else if (e.getCause() instanceof UnknownHostException) {
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN_HOST, "", "Unknown host: " + host, e);
        } else {
            throw new ConnectionException(ConnectionExceptionCode.INCORRECT_CREDENTIALS, "",
                    "Could not login to ftp server with user " + user, e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy