com.joshlong.esb.springintegration.modules.net.sftp.SFTPInboundSynchronizer.java Source code

Java tutorial

Introduction

Here is the source code for com.joshlong.esb.springintegration.modules.net.sftp.SFTPInboundSynchronizer.java

Source

/*
 * Copyright 2010 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 com.joshlong.esb.springintegration.modules.net.sftp;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;

import org.apache.commons.io.IOUtils;

import org.apache.log4j.Logger;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.core.io.Resource;

import org.springframework.integration.core.MessagingException;

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import java.util.Collection;
import java.util.concurrent.ScheduledFuture;

/**
 * @author <a href="mailto:josh@joshlong.com">Josh Long</a>
 * @author <a href="mailto:mario.gray@gmail.com">Mario Gray</a>
 */
public class SFTPInboundSynchronizer implements InitializingBean /*, Lifecycle*/ {
    private static final Logger logger = Logger.getLogger(SFTPInboundSynchronizer.class);
    private static final long DEFAULT_REFRESH_RATE = 10 * 1000; // 10 seconds 

    // a lot of the approach for this (including the use of a FileReadingMessageSource and the regex / mask approach were lifted from FtpInboundSynchronizer
    static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
    private volatile Resource localDirectory;
    private volatile SFTPSessionPool pool;
    private volatile ScheduledFuture<?> scheduledFuture;
    private volatile String remotePath;
    private volatile TaskScheduler taskScheduler;
    private volatile Trigger trigger = new PeriodicTrigger(DEFAULT_REFRESH_RATE);
    private volatile boolean autoCreatePath;
    private volatile boolean running;
    private volatile boolean shouldDeleteDownloadedRemoteFiles; // obviously this is fale

    public void afterPropertiesSet() throws Exception {
        assert (taskScheduler != null) : "taskScheduler can't be null!";
        assert (localDirectory != null) : "the localDirectory property must not be null!";

        File localDir = localDirectory.getFile();

        if (!localDir.exists()) {
            if (autoCreatePath) {
                if (!localDir.mkdirs()) {
                    throw new RuntimeException(String.format("couldn't create localDirectory %s",
                            this.localDirectory.getFile().getAbsolutePath()));
                }
            }
        }
    }

    public ScheduledFuture<?> getScheduledFuture() {
        return scheduledFuture;
    }

    public TaskScheduler getTaskScheduler() {
        return taskScheduler;
    }

    public Trigger getTrigger() {
        return trigger;
    }

    public boolean isRunning() {
        return running;
    }

    public boolean isShouldDeleteDownloadedRemoteFiles() {
        return shouldDeleteDownloadedRemoteFiles;
    }

    public void setAutoCreatePath(boolean autoCreatePath) {
        this.autoCreatePath = autoCreatePath;
    }

    public void setLocalDirectory(Resource localDirectory) {
        this.localDirectory = localDirectory;
    }

    public void setPool(SFTPSessionPool pool) {
        this.pool = pool;
    }

    public void setRemotePath(String remotePath) {
        this.remotePath = remotePath;
    }

    public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
        this.scheduledFuture = scheduledFuture;
    }

    public void setShouldDeleteDownloadedRemoteFiles(boolean shouldDeleteDownloadedRemoteFiles) {
        this.shouldDeleteDownloadedRemoteFiles = shouldDeleteDownloadedRemoteFiles;
    }

    public void setTaskScheduler(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
    }

    public void setTrigger(Trigger t) {
        this.trigger = t;
    }

    public void start() {
        if (running) {
            return;
        }
        assert checkThatRemotePathExists(remotePath) : "the remotePath had better exist!";
        assert taskScheduler != null : "'taskScheduler' is required";

        scheduledFuture = taskScheduler.schedule(new SynchronizeTask(), trigger);

        this.running = true;

        if (logger.isInfoEnabled()) {
            logger.info("Started " + this);
        }
    }

    public void stop() {
        if (!running) {
            return;
        }
        assert scheduledFuture != null : "scheduledFuture is null!";
        this.scheduledFuture.cancel(true);
        this.running = false;

        if (logger.isInfoEnabled()) {
            logger.info("Stopped " + this);
        }
    }

    @SuppressWarnings("unchecked")
    public void synchronize() throws Exception {
        SFTPSession session = null;

        try {
            session = pool.getSession();
            session.start();

            ChannelSftp channelSftp = session.getChannel();
            Collection<ChannelSftp.LsEntry> files = channelSftp.ls(remotePath);

            for (ChannelSftp.LsEntry lsEntry : files) {
                if ((lsEntry != null) && !lsEntry.getAttrs().isDir() && !lsEntry.getAttrs().isLink()) {
                    copyFromRemoteToLocalDirectory(session, lsEntry, this.localDirectory);
                }
            }
        } catch (IOException e) {
            throw new MessagingException("couldn't synchronize remote to local directory", e);
        } finally {
            if ((session != null) && (pool != null)) {
                pool.release(session);
            }
        }
    }

    /**
     * there be dragons this way ... This method will check to ensure that the remote directory exists. If the directory
     * doesnt exist, and autoCreatePath is configured to be true, then this method makes a few reasonably sane attempts
     * to create it. Otherwise, it fails fast.
     *
     * @param rPath the path on the remote SSH / SFTP server to create.
     *
     * @return whether or not the directory is there (regardless of whether we created it in this method or it already
     *         existed.)
     */
    private boolean checkThatRemotePathExists(String rPath) {
        SFTPSession session = null;
        ChannelSftp channelSftp = null;

        try {
            session = pool.getSession();
            assert session != null : "session's not null";
            session.start();
            channelSftp = session.getChannel();

            SftpATTRS attrs = channelSftp.stat(rPath);
            assert (attrs != null)
                    && attrs.isDir() : "attrs can't be null, and should indicate that it's a directory!";

            return true;
        } catch (Throwable th) {
            logger.debug("exception throwing when trying to verify the presence of the remote rPath '" + rPath
                    + "'. Will try to create the directory.", th);

            if (this.autoCreatePath && (pool != null) && (session != null)) {
                try {
                    if (channelSftp != null) {
                        channelSftp.mkdir(rPath);

                        if (channelSftp.stat(rPath).isDir()) {
                            logger.debug(String.format("created directory successfully: %s", rPath));

                            return true;
                        }
                    }
                } catch (Throwable t) {
                    return false;
                }
            }
        } finally {
            if ((pool != null) && (session != null)) {
                pool.release(session);
            }
        }

        return false;
    }

    @SuppressWarnings("ignored")
    private boolean copyFromRemoteToLocalDirectory(SFTPSession sftpSession, ChannelSftp.LsEntry entry,
            Resource localDir) throws Exception {
        logger.debug(String.format("attempting to sync remote file %s/%s to local file %s", remotePath,
                entry.getFilename(), localDir.getFile().getAbsolutePath()));

        File fileForLocalDir = localDir.getFile();

        File localFile = new File(fileForLocalDir, entry.getFilename());

        if (!localFile.exists()) {
            InputStream in = null;
            FileOutputStream fos = null;

            try {
                File tmpLocalTarget = new File(localFile.getAbsolutePath() + INCOMPLETE_EXTENSION);

                fos = new FileOutputStream(tmpLocalTarget);

                String remoteFqPath = this.remotePath + "/" + entry.getFilename();
                in = sftpSession.getChannel().get(remoteFqPath);
                IOUtils.copy(in, fos);

                if (tmpLocalTarget.renameTo(localFile)) {
                    // last step
                    if (isShouldDeleteDownloadedRemoteFiles()) {
                        logger.debug(String.format("isShouldDeleteDownloadedRemoteFiles == true; "
                                + "attempting to remove remote path '%s'", remoteFqPath));
                        sftpSession.getChannel().rm(remoteFqPath);
                    }
                }

                return true;
            } catch (Throwable th) {
                IOUtils.closeQuietly(in);
                IOUtils.closeQuietly(fos);
            }
        } else {
            logger.debug(String.format("local file %s already exists. Not re-downloading it.",
                    localFile.getAbsolutePath()));

            return true;
        }

        return false;
    }

    private boolean foo() {
        return false;
    }

    class SynchronizeTask implements Runnable {
        public void run() {
            try {
                synchronize();
            } catch (Throwable e) {
                logger.debug("couldn't invoke synchronize()", e);
            }
        }
    }
}