at.beris.virtualfile.client.ftp.FtpClient.java Source code

Java tutorial

Introduction

Here is the source code for at.beris.virtualfile.client.ftp.FtpClient.java

Source

/*
 * This file is part of VirtualFile.
 *
 * Copyright 2016 by Bernd Riedl <bernd.riedl@gmail.com>
 *
 * Licensed under GNU Lesser General Public License 3.0 or later.
 * Some rights reserved. See COPYING, AUTHORS.
 */

package at.beris.virtualfile.client.ftp;

import at.beris.virtualfile.attribute.FileAttribute;
import at.beris.virtualfile.client.AbstractClient;
import at.beris.virtualfile.config.Configuration;
import at.beris.virtualfile.exception.OperationNotSupportedException;
import at.beris.virtualfile.util.UrlUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.*;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.UserPrincipal;
import java.util.*;
import java.util.concurrent.Callable;

public class FtpClient extends AbstractClient<FTPFile> {
    private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FtpClient.class);
    private final static int MAX_CONNECTION_ATTEMPTS = 3;
    private String physicalRootPath;
    private boolean reconnect;
    private FTPClient ftpClient;

    public FtpClient(URL url, Configuration config) {
        super(url, config);
        init();
    }

    private void init() {
        LOGGER.debug("init");
        ftpClient = new FTPClient();
        ftpClient.setControlEncoding("UTF-8");
    }

    @Override
    public void connect() throws IOException {
        LOGGER.info("Connecting to " + username() + "@" + host() + ":" + String.valueOf(port()));
        reconnect = true;
        ftpClient.connect(host(), port());
        if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            disconnect();
            return;
        }
        login();
        setFileType(FTP.BINARY_FILE_TYPE);
    }

    @Override
    public void disconnect() throws IOException {
        LOGGER.info("Disconnecting from " + username() + "@" + host() + ":" + String.valueOf(port()));
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                reconnect = false;
                if (ftpClient.isConnected()) {
                    ftpClient.logout();
                    ftpClient.disconnect();
                }
                return null;
            }
        });
    }

    @Override
    public void deleteFile(final String path) throws IOException {
        LOGGER.debug("deleteFile (path : {})", path);
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                int replyCode = ftpClient.dele(path);
                String replyText = ftpClient.getReplyString();
                if (!FTPReply.isPositiveCompletion(replyCode))
                    LOGGER.warn("Unexpected Reply (Code: {}, Text: '{}'", replyCode, replyText);
                return null;
            }
        });
    }

    @Override
    public void createFile(final String path) throws IOException {
        LOGGER.debug("createFile (path : {})", path);
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
                    checkConnection();
                    ftpClient.storeFile(path, inputStream);
                } catch (IOException e) {
                    throw e;
                }
                return null;
            }
        });
    }

    @Override
    public boolean exists(final String path) throws IOException {
        LOGGER.debug("exists (path : {})", path);
        Boolean exists = executionHandler(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {

                int replyCode = ftpClient.stat(path);
                String replyText = ftpClient.getReplyString();
                if (!FTPReply.isPositiveCompletion(replyCode)) {
                    // this replyText code is set when file doesn't exist on the server
                    if (FTPReply.FILE_ACTION_NOT_TAKEN == replyCode)
                        return false;
                    else {
                        LOGGER.warn("Unexpected Reply (Code: {}, Text: '{}'", replyCode, replyText);
                    }
                }

                String[] replyTextParts = replyText.split("\n");
                if (replyTextParts.length <= 2) {
                    if (ftpClient.changeWorkingDirectory(path))
                        ftpClient.changeToParentDirectory();
                    else
                        return false;
                }
                return true;
            }
        });
        LOGGER.debug("Returns: {}", exists);
        return exists;
    }

    @Override
    public void createDirectory(final String path) throws IOException {
        LOGGER.debug("createDirectory (path : {})", path);
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                int replyCode = ftpClient.mkd(path);
                String replyText = ftpClient.getReplyString();
                if (!FTPReply.isPositiveCompletion(replyCode)) {
                    // this replyText code is set when file already exists on the server
                    if (FTPReply.FILE_UNAVAILABLE == replyCode)
                        throw new FileAlreadyExistsException(path);
                    else
                        LOGGER.warn("Unexpected Reply (Code: {}, Text: '{}'", replyCode, replyText);
                }
                return null;
            }
        });
    }

    @Override
    public void deleteDirectory(final String path) throws IOException {
        LOGGER.debug("deleteDirectory (path : {})", path);
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                int replyCode = ftpClient.rmd(path);
                String replyText = ftpClient.getReplyString();
                if (!FTPReply.isPositiveCompletion(replyCode)) {
                    LOGGER.warn("Unexpected Reply (Code: {}, Text: '{}'", replyCode, replyText);
                }
                return null;
            }
        });
    }

    @Override
    public InputStream getInputStream(final String path) throws IOException {
        LOGGER.debug("getInputStream (path : {})", path);
        return executionHandler(new Callable<FtpInputStream>() {
            @Override
            public FtpInputStream call() throws Exception {
                return new FtpInputStream(ftpClient.retrieveFileStream(path), ftpClient);
            }
        });
    }

    @Override
    public OutputStream getOutputStream(final String path) throws IOException {
        LOGGER.debug("getOutputStream (path : {})", path);
        return executionHandler(new Callable<FtpOutputStream>() {
            @Override
            public FtpOutputStream call() throws Exception {
                return new FtpOutputStream(ftpClient.storeFileStream(path), ftpClient);
            }
        });
    }

    @Override
    public FTPFile getFileInfo(final String path) throws IOException {
        LOGGER.debug("getFileInfo (path: {})", path);
        return executionHandler(new Callable<FTPFile>() {
            @Override
            public FTPFile call() throws Exception {
                if ("/".equals(path)) {
                    FTPFile rootFile = new FTPFile();
                    rootFile.setName("/");
                    rootFile.setTimestamp(GregorianCalendar.getInstance());
                    return rootFile;
                } else {
                    String lastPathPart = UrlUtils.getLastPathPart(path);
                    String parentPath = UrlUtils.getParentPath(path);
                    ftpClient.changeWorkingDirectory(parentPath);
                    FTPFile[] ftpFiles = ftpClient.listFiles();
                    for (FTPFile ftpFile : ftpFiles) {
                        if (ftpFile.getName().equals(lastPathPart)) {
                            return ftpFile;
                        }
                    }
                    return new FTPFile();
                }
            }
        });
    }

    protected String username() {
        String username = super.username();
        return StringUtils.isBlank(username) ? "anonymous" : username;
    }

    @Override
    protected int defaultPort() {
        return 21;
    }

    public List<FTPFile> list(final String path) throws IOException {
        LOGGER.debug("list (path: {})", path);
        List<FTPFile> fileInfoList = executionHandler(new Callable<List<FTPFile>>() {
            @Override
            public List<FTPFile> call() throws Exception {
                int replyCode = ftpClient.cwd(path);
                String replyText = ftpClient.getReplyString();

                if (FTPReply.isPositiveCompletion(replyCode)) {
                    return Arrays.asList(ftpClient.listFiles());
                } else {
                    LOGGER.warn("Unexpected Reply (Code: {}, Text: '{}'", replyCode, replyText);
                }
                return Collections.emptyList();
            }
        });

        return fileInfoList;
    }

    @Override
    public void setLastModifiedTime(String path, FileTime time) {
        throw new OperationNotSupportedException();
    }

    @Override
    public void setAttributes(String path, Set<FileAttribute> attributes) {
        throw new OperationNotSupportedException();
    }

    @Override
    public void setOwner(String path, UserPrincipal owner) {
        throw new OperationNotSupportedException();
    }

    @Override
    public void setGroup(String path, GroupPrincipal group) {
        throw new OperationNotSupportedException();
    }

    @Override
    public void dispose() throws IOException {
        disconnect();
        ftpClient = null;
    }

    private void checkConnection() throws IOException {
        if (!ftpClient.isConnected())
            connect();
    }

    private void login() throws IOException {
        LOGGER.debug("login");
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                ftpClient.login(username(), String.valueOf(password()));
                return null;
            }
        });
    }

    private void setFileType(final int fileType) throws IOException {
        LOGGER.debug("setFileType (fileType: {})", fileType);
        executionHandler(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                ftpClient.setFileType(fileType);
                return null;
            }
        });
    }

    public String getPhysicalRootPath() throws IOException {
        if (physicalRootPath == null) {
            physicalRootPath = executionHandler(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    ftpClient.changeWorkingDirectory("/");
                    for (FTPFile ftpFile : ftpClient.listFiles()) {
                        if (ftpFile.isSymbolicLink() && ".".equals(ftpFile.getLink())) {
                            return "/" + ftpFile.getName() + "/";
                        }
                    }
                    return "";
                }
            });
        }
        return physicalRootPath;
    }

    private <T> T executionHandler(Callable<T> action) throws IOException {
        int connectionAttempts = MAX_CONNECTION_ATTEMPTS;
        while (connectionAttempts > 0) {
            try {
                checkConnection();
                return action.call();
            } catch (FTPConnectionClosedException e) {
                LOGGER.debug("Exception", e);
                if (reconnect) {
                    LOGGER.warn("Server closed connection. Reconnecting.");
                    connectionAttempts--;
                    connect();
                }
            } catch (IOException e) {
                LOGGER.debug("Exception", e);
                throw e;
            } catch (Exception e) {
                LOGGER.debug("Exception", e);
                throw new RuntimeException(e);
            }
        }
        return null;
    }
}