org.ut.biolab.medsavant.shared.util.SeekableFTPStream.java Source code

Java tutorial

Introduction

Here is the source code for org.ut.biolab.medsavant.shared.util.SeekableFTPStream.java

Source

/**
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.ut.biolab.medsavant.shared.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URL;

import net.sf.samtools.util.SeekableStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

/**
 * Random access stream for FTP access to BAM files through Picard
 *
 * @author vwilliams
 */
public class SeekableFTPStream extends SeekableStream {

    private static Log LOG = LogFactory.getLog(SeekableFTPStream.class);
    private static final int SOCKET_TIMEOUT = 10000;

    private final String source;
    private final String username;
    private final String password;
    private final String host;
    private final int port;
    private final String fileName;

    private long length;
    private FTPClient ftpClient = null;
    private long position = 0;

    public SeekableFTPStream(URL url) {
        this(url, "anonymous", "");
    }

    public SeekableFTPStream(URL url, String user, String pwd) {

        if (url == null) {
            throw new IllegalArgumentException("URL may not be null");
        }
        if (!url.getProtocol().toLowerCase().equals("ftp")) {
            throw new IllegalArgumentException("Only ftp:// protocol URLs are valid.");
        }

        source = url.toString();
        username = user;
        password = pwd;
        host = url.getHost();
        int p = url.getPort();
        port = p != -1 ? p : url.getDefaultPort();
        fileName = url.getFile();
        length = 0;
    }

    @Override
    public long length() {
        if (length == 0) {
            FTPFile[] files;
            try {
                files = listFiles(fileName);
            } catch (IOException e) {
                try {
                    disconnect();
                    files = listFiles(fileName);
                } catch (IOException e1) {
                    LOG.warn("Unable to reconnect getting length");
                    return 0;
                }

            }
            for (int i = 0; i < files.length; i++) {
                FTPFile file = files[i];
                if (file != null) {
                    if (file.getName().equals(fileName)) {
                        length = file.getSize();
                        break;
                    }
                }
            }
        }
        return length;

    }

    @Override
    public void seek(long pos) throws IOException {
        position = pos;
        LOG.info("FTP: seek to " + pos);
    }

    @Override
    public int read(byte[] bytes, int offset, int len) throws IOException {
        try {
            return readFromStream(bytes, offset, len);
        } catch (IOException x) {
            LOG.info("Connection closed during read.  Disconnecting and trying again at " + position);
            disconnect();
            return readFromStream(bytes, offset, len);
        }
    }

    private int readFromStream(byte[] bytes, int offset, int len) throws IOException {

        FTPClient client = getFTPClient();
        if (position != 0) {
            client.setRestartOffset(position);
        }
        InputStream is = client.retrieveFileStream(fileName);
        long oldPos = position;
        if (is != null) {
            int n = 0;
            while (n < len) {
                int bytesRead = is.read(bytes, offset + n, len - n);
                if (bytesRead < 0) {
                    if (n == 0)
                        return -1;
                    else
                        break;
                }
                n += bytesRead;
            }
            is.close();
            LOG.info(String.format("FTP read %d bytes at %d: %02x %02x %02x %02x %02x %02x %02x %02x...", len,
                    oldPos, bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3],
                    bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], bytes[offset + 7]));
            try {
                client.completePendingCommand();
            } catch (FTPConnectionClosedException suppressed) {
            } catch (SocketTimeoutException stx) {
                // Accessing 1000 Genomes, we sometimes get a timeout for no apparent reason.
                LOG.info("Timed out during read.  Disconnecting.");
                disconnect();
            }
            position += n;
            return n;
        } else {
            String msg = String.format("Unable to retrieve input stream for file (reply code %d).",
                    client.getReplyCode());
            LOG.error(msg);
            throw new IOException(msg);
        }
    }

    @Override
    public void close() throws IOException {
        if (ftpClient != null) {
            try {
                ftpClient.completePendingCommand();
            } catch (IOException e) {
                LOG.trace("Suppressing IOException from completePendingCommand().");
            }
            try {
                ftpClient.logout();
            } catch (IOException e) {
                LOG.info("Suppressing IOException from logout().");
            }
            disconnect();
        }
    }

    @Override
    public boolean eof() throws IOException {
        return position >= length();
    }

    @Override
    public int read() throws IOException {
        throw new UnsupportedOperationException("read() not supported for SeekableFTPStreams");
    }

    @Override
    public String getSource() {
        return source;
    }

    public FTPFile[] listFiles(String relPath) throws IOException {
        try {
            return getFTPClient().listFiles(relPath);
        } catch (FTPConnectionClosedException e) {
            disconnect();
            return getFTPClient().listFiles(relPath);
        }
    }

    public void disconnect() throws IOException {
        if (ftpClient != null) {
            try {
                ftpClient.disconnect();
            } finally {
                ftpClient = null;
            }
        }
    }

    private FTPClient getFTPClient() throws IOException {
        if (ftpClient == null) {
            FTPClient client = new FTPClient();
            try {
                client.connect(host, port);
                int reply = client.getReplyCode();
                if (!FTPReply.isPositiveCompletion(reply)) {
                    throw new IOException("Unable to connect to " + host);
                }
                if (!client.login(username, password)) {
                    throw new IOException("Unable to login to " + host + " as " + username);
                }
                client.setFileType(FTP.BINARY_FILE_TYPE);
                client.enterLocalPassiveMode();
                client.setSoTimeout(SOCKET_TIMEOUT);
                ftpClient = client;
                client = null;
            } finally {
                if (client != null) {
                    client.disconnect();
                }
            }
        }

        return ftpClient;
    }
}