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

org.kiwiproject.jsch.SftpTransfers Maven / Gradle / Ivy

Go to download

Kiwi is a utility library. We really like Google's Guava, and also use Apache Commons. But if they don't have something we need, and we think it is useful, this is where we put it.

There is a newer version: 4.5.2
Show newest version
package org.kiwiproject.jsch;

import static java.util.stream.Collectors.toList;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.SftpException;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Vector;
import java.util.function.BiFunction;
import java.util.function.Predicate;

/**
 * A simple wrapper around a {@link JSch} instance that handles some basic SFTP operations.
 *
 * @implNote This requires JSch being available at runtime.
 */
@Slf4j
public class SftpTransfers {

    private final SftpConnector connector;

    static {
        JSch.setLogger(new JSchSlf4jLogger(LOG));
    }

    public SftpTransfers(SftpConnector connector) {
        this.connector = connector;
    }

    /**
     * Connects to the remote SFTP server.
     *
     * @apiNote This is a convenience method that delegates to the internal {@link SftpConnector#connect()}
     */
    public void connect() {
        connector.connect();
    }

    /**
     * Disconnects from the remote SFTP server.
     *
     * @apiNote This is a convenience method that delegates to the internal {@link SftpConnector#disconnect()}
     */
    public void disconnect() {
        connector.disconnect();
    }

    /**
     * Pushes a stream of data to a remote SFTP server in the given path and with the given filename.
     *
     * @param remotePath the path on the remote server where the file will be created
     * @param filename   the filename to give the file on the remote server
     * @param data       the stream of data to write to the remote server
     */
    public void putFile(Path remotePath, String filename, InputStream data) {
        connector.runCommand(channel -> {
            changeOrCreateRemoteDirectory(channel, remotePath);
            channel.put(data, filename);
        });
    }

    /**
     * Will change the remote directory or create it if it doesn't exist.
     *
     * @implNote One way to determine if the directory already exists is to attempt to change to it. If an exception
     * is thrown, catch it and attempt to create the directory and then change to it. There doesn't seem to be any
     * (obvious) way to check existence in JSch otherwise.
     */
    private static void changeOrCreateRemoteDirectory(ChannelSftp channel, Path path) throws SftpException {
        try {
            changeToRemoteDirectory(channel, path);
        } catch (SftpException e) {
            LOG.debug("Directory {} did not exist. Will create it", path, e);
            channel.mkdir(path.toString());
            changeToRemoteDirectory(channel, path);
        }
    }

    /**
     * Recursively gets files off of a remote server starting in the given path and stores the files locally in the
     * given path and given filename. The local path will be determined through the given {@code BiFunction} supplier
     * which is provided the current remote path and current remote filename. The local filename will be determined
     * through the given {@code BiFunction} supplier which is provided the remote path and remote filename.
     *
     * @param remotePath            path on the remote server where the file is located
     * @param localPathSupplier     supplier that calculates the path on the local machine where the file will be written
     * @param localFilenameSupplier supplier that calculates the name of the file that will be written locally
     */
    public void getAndStoreAllFiles(Path remotePath,
                                    BiFunction localPathSupplier,
                                    BiFunction localFilenameSupplier) {

        // First store off the files
        listFiles(remotePath).forEach(filename -> getAndStoreFile(remotePath, localPathSupplier, filename, localFilenameSupplier));

        // Recursively go through the directories
        listDirectories(remotePath).forEach(directory ->
                getAndStoreAllFiles(remotePath.resolve(directory), localPathSupplier, localFilenameSupplier));
    }

    /**
     * Gets a file off of a remote server in the given path and with the given filename and stores the file locally in
     * a given path and the original (remote) filename.
     *
     * @param remotePath path on the remote server where the file is located
     * @param localPath  path on the local machine where the file will be written
     * @param filename   name of the file to pull from the remote server (This name is used as the local file name)
     */
    public void getAndStoreFile(Path remotePath, Path localPath, String filename) {
        getAndStoreFile(remotePath, localPath, filename, filename);
    }

    /**
     * Gets a file off of a remote server in the given path and with the given filename and stores the file locally
     * in a given path and the given filename.
     *
     * @param remotePath     path on the remote server where the file is located
     * @param localPath      path on the local machine where the file will be written
     * @param remoteFilename name of the file to pull from the remote server
     * @param localFilename  name of the file that will be written locally
     */
    public void getAndStoreFile(Path remotePath, Path localPath, String remoteFilename, String localFilename) {
        getAndStoreFile(remotePath, (rPath, rFile) -> localPath, remoteFilename, (rPath, rFile) -> localFilename);
    }

    /**
     * Gets a file off of a remote server in the given path and with the given filename and stores the file locally in
     * a given path and the given filename. The local path will be determined through the given {@code BiFunction}
     * supplier which is provided the remote path and remote filename. The local filename will be determined through
     * the given {@code BiFunction} supplier which is provided the remote path and remote filename.
     *
     * @param remotePath            path on the remote server where the file is located
     * @param localPathSupplier     supplier that calculates the path on the local machine where the file will be written
     * @param remoteFilename        name of the file to pull from the remote server
     * @param localFilenameSupplier supplier that calculates the name of the file that will be written locally
     */
    public void getAndStoreFile(Path remotePath,
                                BiFunction localPathSupplier,
                                String remoteFilename,
                                BiFunction localFilenameSupplier) {

        connector.runCommand(channel -> {
            changeToRemoteDirectory(channel, remotePath);

            var localPath = localPathSupplier.apply(remotePath, remoteFilename);
            ensureLocalDirectoryExists(localPath);

            try (var inputStream = channel.get(remoteFilename)) {
                var resolvedLocalPath = localPath.resolve(localFilenameSupplier.apply(remotePath, remoteFilename));
                Files.copy(inputStream, resolvedLocalPath, StandardCopyOption.REPLACE_EXISTING);
            }
        });
    }

    private static void ensureLocalDirectoryExists(Path path) throws IOException {
        if (!Files.exists(path)) {
            LOG.debug("Local storage directory {} doesn't exist. Creating.", path);
            Files.createDirectories(path);
        }
    }

    /**
     * Gets a file off of a remote server with the given path and given filename and returns the contents of the file
     * as a {@code String}.
     *
     * @param remotePath     path on the remote server where the file is located
     * @param remoteFilename name of the file to pull from the remote server
     * @return contents of the retrieved file as a {@code String}
     */
    public String getFileContent(Path remotePath, String remoteFilename) {
        return connector.runCommandWithResponse(channel -> {
            changeToRemoteDirectory(channel, remotePath);

            try (var inputStream = channel.get(remoteFilename)) {
                var streamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
                var stringWriter = new StringWriter();
                streamReader.transferTo(stringWriter);
                return stringWriter.toString();
            }
        });
    }

    /**
     * Gets a file off of a remote server with the given path and given filename and returns an {@link InputStream}. This
     * is useful if the remote file is binary and not a text based file.
     * 

* Note: The caller of this method is responsible for closing the stream. * * @param remotePath path on the remote server where the file is located * @param remoteFilename name of the file to pull from the remote server * @return an {@link InputStream} to read the file */ public InputStream getFileContentAsInputStream(Path remotePath, String remoteFilename) { return connector.runCommandWithResponse(channel -> { changeToRemoteDirectory(channel, remotePath); return channel.get(remoteFilename); }); } /** * Returns a list of files that exist in the given path on the remote server. * * @param remotePath path on the remote server to list files * @return a list of filenames that exist in the given path */ public List listFiles(Path remotePath) { return listRemoteItems(remotePath, file -> !file.getAttrs().isDir()); } /** * Returns a list of files that exist in the given path and matching the given file filter on the remote server. * * @param remotePath path on the remote server to list files * @param fileFilter predicate to filter file names being returned * @return a list of filenames that exist in the given path matching the given file filter */ public List listFiles(Path remotePath, Predicate fileFilter) { return listFiles(remotePath).stream() .filter(fileFilter) .collect(toList()); } /** * Returns a list of directories that exist in the given path on the remote server. * * @param remotePath path on the remote server to list directories * @return a list of directories that exist in the given path */ public List listDirectories(Path remotePath) { return listRemoteItems(remotePath, file -> file.getAttrs().isDir()); } /** * Returns a list of directories that exist in the given path and matching the given directory filter on the * remote server. * * @param remotePath path on the remote server to list files * @param dirFilter predicate to filter directory names being returned * @return a list of directories that exist in the given path matching the given directory filter */ public List listDirectories(Path remotePath, Predicate dirFilter) { return listDirectories(remotePath).stream() .filter(dirFilter) .collect(toList()); } private List listRemoteItems(Path remotePath, Predicate filterFunction) { return connector.runCommandWithResponse(channel -> ls(channel, remotePath) .stream() .filter(filterFunction) .map(ChannelSftp.LsEntry::getFilename) .collect(toList())); } // Suppress Sonar warning about using Vectors. JSch returns a raw Vector which we cannot do anything // about, and this is a private method, so it's not worth worrying about. @SuppressWarnings({"unchecked", "java:S1149"}) private static Vector ls(ChannelSftp channel, Path remotePath) throws SftpException { return channel.ls(remotePath.toString()); } /** * Deletes a given file from the given path on a remote server. * * @param remotePath path on the remote server where the file is located * @param remoteFilename name of the file to delete from the remote server */ public void deleteRemoteFile(Path remotePath, String remoteFilename) { connector.runCommand(channel -> { changeToRemoteDirectory(channel, remotePath); channel.rm(remoteFilename); }); } private static void changeToRemoteDirectory(ChannelSftp channel, Path path) throws SftpException { LOG.debug("Attempting to change to {} on the remote host", path); channel.cd(path.toString()); LOG.debug("Successfully changed directory on the remote host"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy