de.thischwa.pmcms.tool.connection.ftp.FtpTransfer.java Source code

Java tutorial

Introduction

Here is the source code for de.thischwa.pmcms.tool.connection.ftp.FtpTransfer.java

Source

/*******************************************************************************
 * Poor Man's CMS (pmcms) - A very basic CMS generating static html pages.
 * http://poormans.sourceforge.net
 * Copyright (C) 2004-2013 by Thilo Schwarz
 * 
 * == BEGIN LICENSE ==
 * 
 * Licensed under the terms of any of the following licenses at your
 * choice:
 * 
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 * 
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * == END LICENSE ==
 ******************************************************************************/
package de.thischwa.pmcms.tool.connection.ftp;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.NumberFormat;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.io.CopyStreamAdapter;
import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.Util;
import org.apache.log4j.Logger;

import de.thischwa.pmcms.tool.connection.AbstractTransfer;
import de.thischwa.pmcms.tool.connection.ConnectionException;
import de.thischwa.pmcms.tool.connection.ConnectionRunningException;
import de.thischwa.pmcms.tool.connection.IConnectionManager;
import de.thischwa.pmcms.tool.connection.PathAnalyzer;
import de.thischwa.pmcms.tool.connection.PathConstructor;
import de.thischwa.pmcms.tool.connection.UploadObject;
import de.thischwa.pmcms.tool.connection.UploadTree;
import de.thischwa.pmcms.tool.connection.UploadTreeNode;

/**
 * {@link AbstractTransfer} implementation for ftp. <br>
 * The underlying class is the <code>FTPClient</code> from <code>apache's commons-net</code>.<br>
 * <br>
 *
 * @author Thilo Schwarz
 */
public class FtpTransfer extends AbstractTransfer {
    private static Logger logger = Logger.getLogger(FtpTransfer.class);

    /** The main object from commons-net to handle the ftp connection. */
    private FTPClient ftpClient;

    public FtpTransfer(final IConnectionManager connectionManager) {
        super(connectionManager);
        this.ftpClient = (FTPClient) connectionManager.getUnderlyingObject();
        if (this.ftpClient == null)
            throw new IllegalArgumentException("FTPClient is null!");
    }

    @Override
    public boolean download(final String sourcePath, OutputStream out) throws ConnectionRunningException {
        super.check();
        boolean ok = false;
        PathAnalyzer pathAnalyzer = new PathAnalyzer(sourcePath);
        if (chDirAbsolute(pathAnalyzer.getDir())) {
            InputStream serverIn = null;
            try {
                // 1. check, if Target exits on the server
                FTPFile serverFile = getByName(ftpClient.listFiles(), pathAnalyzer.getName());
                if (serverFile == null || serverFile.isDirectory()) {
                    logger.debug("[FTP] '" + sourcePath + "' doesn't exists or is a directory, - can't get it!");
                    return false;
                }

                // 2. retrieve the file
                if (!ftpClient.retrieveFile(sourcePath, out))
                    throw new ConnectionRunningException("Error while download [" + sourcePath + "]!");
                ok = true;
                out.flush();
            } catch (Exception e) {
                logger.error("[FTP] While getting/copying streams: " + e.getMessage(), e);
                throw new ConnectionRunningException("[FTP] While getting/copying streams: " + e.getMessage(), e);
            } finally {
                IOUtils.closeQuietly(serverIn);
                IOUtils.closeQuietly(out);
            }
        } else
            throw new ConnectionRunningException("Couldn't change to target dir: " + pathAnalyzer.getDir());

        if (ok)
            logger.debug("[FTP] successfull downloaded: " + sourcePath);
        else
            logger.debug("[FTP] download failed!");
        return ok;
    }

    /* (non-Javadoc)
     * @see de.thischwa.pmcms.tool.connection.AUpload#uploadToStartDir(java.lang.String, java.io.InputStream)
     */
    @Override
    public void uploadToStartDir(final String targetPath, final InputStream in) throws ConnectionRunningException {
        if (StringUtils.isBlank(targetPath) || in == null)
            throw new IllegalArgumentException();
        PathAnalyzer ftt = new PathAnalyzer(targetPath);
        if (chDirsConstruct('/' + ftt.getDir())) {
            check();
            uploadToCurrentDir(ftt.getName(), in);
            logger.debug("[FTP] successfull uploaded: " + targetPath);
        }
    }

    /* (non-Javadoc)
     * @see de.thischwa.pmcms.tool.connection.AbstractTransfer#upload(de.thischwa.pmcms.model.tool.UploadTree)
     */
    @Override
    public void upload(final UploadTree targetTree) throws ConnectionRunningException {
        super.upload(targetTree);
        chDirAbsolute(null);
        check();
        traverseUpload(targetTree.getRootNode());
    }

    @Override
    public void deleteDirectory(String targetPath) throws ConnectionRunningException {
        chDirAbsolute(null);
        check();
        try {
            if (!ftpClient.removeDirectory(targetPath))
                throw new ConnectionException("Couldn't delete dir: " + targetPath);
        } catch (Exception e) {
            throw new ConnectionException(e);
        }
        logger.debug("[FTP] dir successfull deleted: " + targetPath);
    }

    @Override
    public void deleteFile(String targetPath) throws ConnectionRunningException {
        chDirAbsolute(null);
        check();
        try {
            if (!ftpClient.deleteFile(targetPath))
                throw new ConnectionException("Couldn't delete file: " + targetPath);
        } catch (Exception e) {
            throw new ConnectionException(e);
        }
        logger.debug("[FTP] file successfull deleted: " + targetPath);
    }

    /**
     * Uploads a 'file' with the content of the InputStream and the name 'name' to the current dir of FTPClient.
     * If the 'file' exists, it will be deleted before.
     *
     * @throws ConnectionRunningException
     */
    private void uploadToCurrentDir(final String name, final InputStream in) throws ConnectionRunningException {
        // TODO implement the server reconnect here!!
        OutputStream serverOut = null;
        try {
            // 1. check, if target file exists and delete it
            FTPFile serverFile = getByName(ftpClient.listFiles(), name);
            if (serverFile != null) {
                if (!ftpClient.deleteFile(name))
                    throw new ConnectionRunningException("Couldn't delete existent file: " + name);
            }

            // 2. create the empty file 
            if (!ftpClient.storeFile(name, IOUtils.toInputStream("")))
                throw new ConnectionRunningException("Couldn't create and empty file for: " + name);

            // 3. copy stream
            serverOut = new BufferedOutputStream(ftpClient.storeFileStream(name), ftpClient.getBufferSize());
            Util.copyStream(in, serverOut, ftpClient.getBufferSize(), CopyStreamEvent.UNKNOWN_STREAM_SIZE,
                    new CopyStreamAdapter() {
                        @Override
                        public void bytesTransferred(long totalBytesTransferred, int bytesTransferred,
                                long streamSize) {
                            progressAddBytes(bytesTransferred);
                        }
                    });
            // not documented but necessary: flush and close the server stream !!!
            serverOut.flush();
            serverOut.close();
            if (!ftpClient.completePendingCommand()) {
                connectionManager.close();
                throw new ConnectionRunningException(
                        "[FTP] While getting/copying streams: " + ftpClient.getReplyString());
            }

        } catch (Exception e) {
            logger.error("[FTP] While getting/copying streams: " + e.getMessage(), e);
            if (!(e instanceof ConnectionException)) {
                connectionManager.close();
                throw new ConnectionRunningException("[FTP] While getting/copying streams: " + e.getMessage(), e);
            }
        } finally {
            IOUtils.closeQuietly(in);
            progressSetSubTaskMessage("");
        }
    }

    /**
     * Just a wrapper to {@link #uploadToCurrentDir(String, InputStream)}.
     */
    private void uploadToCurrentDir(UploadObject uploadObject) {
        progressSetSubTaskMessage(String.format("Upload file: %s (%sKB)", uploadObject.getName(),
                NumberFormat.getInstance().format(uploadObject.getBytes() / 1024)));
        uploadToCurrentDir(uploadObject.getName(), uploadObject.getInputStream());
    }

    private void traverseUpload(final UploadTreeNode node) {
        if (node.hasChildren()) {
            for (UploadObject uo : node.getChildren()) {
                uploadToCurrentDir(uo);
            }
        }
        if (node.hasSubTrees()) {
            for (String dir : node.getSubTrees().keySet()) {
                chDirsConstruct(dir);
                traverseUpload(node.getSubTrees().get(dir));
                chParentDir();
            }
        }
    }

    private void chParentDir() {
        try {
            if (!ftpClient.changeToParentDirectory())
                throw new ConnectionRunningException("Couldn't change to parent dir!");
            logger.debug("[FTP] successfull chdir to parent.");
        } catch (IOException e) {
            throw new ConnectionRunningException("Error while change to parent dir: " + e.getMessage(), e);
        }
    }

    /**
     * Changes to 'targetDir' absolute to the 'serverRootDir'.
     * 
     * @param targetDir Shouldn't start with '/'! Can have subdirs like a/b/c.
     * @return True, if targetDir exists and successful changed.
     * @throws ConnectionRunningException If any IOException was thrown by the FTPClient.
     */
    private boolean chDirAbsolute(final String targetDir) throws ConnectionException {
        super.check();
        String absoluteDir = serverRootDir + StringUtils.defaultString(targetDir);
        try {
            boolean retval = ftpClient.changeWorkingDirectory(absoluteDir);
            if (retval)
                logger.debug("[FTP] successfull chdir to: " + absoluteDir);
            return retval;
        } catch (IOException e) {
            logger.error("Error while trying to chdir [" + absoluteDir + "]: " + e.getMessage(), e);
            throw new ConnectionException("Error while chDir to [" + absoluteDir + "]: " + e.getMessage(), e);
        }
    }

    /**
     * Changes to 'targetDir' and create the necessary subdirs.
     * 
     * @param targetDirs If starts with '/' it will be interpreted as absolute to 'serverRootDir'!
     * @return True if was created/changed successful, otherwise false.
     * @throws ConnectionRunningException If any IOException was thrown by the FTPClient.
     */
    private boolean chDirsConstruct(final String targetDirs) throws ConnectionException {
        try {
            boolean isAbsolute = false;

            // 1. check, if targetDir begins with '/' -> begin with server root dir
            if (targetDirs.startsWith("/"))
                isAbsolute = true;

            String tempDir;
            if (isAbsolute)
                tempDir = new PathConstructor().add(serverRootDir).add(targetDirs).setAbsolute().toString();
            else
                tempDir = targetDirs;
            // 2. quick try, if requested dir exits, so we doesn't need the time expensive traversing
            if (ftpClient.changeWorkingDirectory(tempDir)) {
                logger.debug("[FTP] successfull chdir to:".concat(tempDir));
                return true;
            } else {
                logger.debug("[FTP] couldn't change dir directly, have to traverse to: ".concat(tempDir));
            }

            // 3. traverse and create if necessary
            String dirs[] = new PathConstructor().add(targetDirs).getDirs();
            for (String dir : dirs) {
                if (StringUtils.isNotBlank(dir)) {
                    FTPFile ftpFile = getByName(ftpClient.listFiles(), dir);
                    if (ftpFile != null && ftpFile.isDirectory()) { // TODO Check if file exists with dir name!
                        if (!ftpClient.changeWorkingDirectory(dir))
                            throw new ConnectionRunningException("Couldn't change to dir: " + dir);
                    } else if (ftpFile == null) {
                        if (!(ftpClient.makeDirectory(dir) && ftpClient.changeWorkingDirectory(dir)))
                            throw new ConnectionRunningException("Couldn't create to dir: " + dir);
                    }
                }
            }
        } catch (IOException e) {
            throw new ConnectionRunningException(e);
        }
        logger.debug("[FTP] Successfull change/create: ".concat(targetDirs));
        return true;
    }

    /**
     * Searches a FTPFile by name from an array of FTPFiles.
     * 
     * @return FTPClient, if exists, or null.
     */
    private FTPFile getByName(final FTPFile[] files, final String name) {
        if (StringUtils.isBlank(name))
            return null;
        for (FTPFile file : files)
            if (file.getName().equals(name))
                return file;
        return null;
    }
}