savant.util.NetworkUtils.java Source code

Java tutorial

Introduction

Here is the source code for savant.util.NetworkUtils.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 savant.util;

import java.io.*;
import java.net.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import net.sf.samtools.util.SeekableBufferedStream;
import net.sf.samtools.util.SeekableFileStream;
import net.sf.samtools.util.SeekableHTTPStream;
import net.sf.samtools.util.SeekableStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPFile;

import savant.api.util.Listener;
import savant.exception.UnknownSchemeException;
import savant.settings.BrowserSettings;

/**
 * Some useful methods for performing network-related functions.
 *
 * @author vwilliams, tarkvara
 */
public class NetworkUtils {
    private static final Log LOG = LogFactory.getLog(NetworkUtils.class);

    private static final int CONNECT_TIMEOUT = 30000; // 30s timeout for making connection
    private static final int READ_TIMEOUT = 30000; // 30s timeout for reading data
    private static final int BUF_SIZE = 8192; // 8kB buffer

    static {
        // Create a trust manager that does not validate certificate chains.
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            LOG.info("Setting default SSL socket factory...");
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception x) {
            LOG.error("Unable to set socket factory.", x);
        }
    }

    /**
     * Given a URI, determine whether it exists or not.
     */
    public static boolean exists(URI uri) {
        try {
            if (uri.getScheme().equals("file")) {
                return new File(uri).exists();
            }
            return getHash(uri.toURL()) != null;
        } catch (Exception x) {
            return false;
        }
    }

    /**
     * Get a unique hash representing the contents of this file.  For an HTTP server,
     * this will be the ETag returned in the header; for FTP servers, we create a
     * hash based on the size and modification time.
     *
     * @param url   URL of the file to be hashed.
     * @return  a hash-value "unique" to this file.
     *
     * @throws IOException
     */
    public static String getHash(URL url) throws IOException {
        String proto = url.getProtocol().toLowerCase();
        if (proto.equals("http") || proto.equals("https")) {
            URLConnection conn = null;
            try {
                conn = url.openConnection();
                return conn.getHeaderField("ETag");
            } finally {
                if ((conn != null) && (conn instanceof HttpURLConnection)) {
                    ((HttpURLConnection) conn).disconnect();
                }
            }
        } else if (proto.equals("ftp")) {
            SeekableFTPStream ftp = new SeekableFTPStream(url, "anonymous", "");

            try {
                // List the files.  We should only get one match.
                FTPFile[] files = ftp.listFiles(url.getFile());
                if (files.length > 0) {
                    return String.format("%016x-%016x", files[0].getTimestamp().getTimeInMillis(),
                            files[0].getSize());
                } else {
                    throw new IOException("URL not found: " + url);
                }
            } finally {
                ftp.close();
            }
        } else if (proto.equals("file")) {
            // Cheesy fake hash-code based on the modification time and size.
            try {
                File f = new File(url.toURI());
                return String.format("%016x-%016x", f.lastModified(), f.length());
            } catch (URISyntaxException x) {
                throw new IllegalArgumentException("Invalid argument; cannot parse " + url + " as a file.");
            }
        } else {
            throw new IllegalArgumentException("Invalid argument; cannot get hash for " + proto + " URLs.");
        }
    }

    /**
     * Given a URI, return a SeekableStream of the appropriate type.
     *
     * @param uri an ftp:, http:, or file: URI
     * @param allowCaching if true, remote streams will be wrapped in a CachedSeekableStream
     * @return a SeekableStream which can be passed to SavantROFile or BAMDataSource
     */
    public static SeekableStream getSeekableStreamForURI(URI uri, boolean allowCaching) throws IOException {
        String proto = uri.getScheme().toLowerCase();
        SeekableStream result;
        if (proto.equals("file")) {
            result = new SeekableBufferedStream(new SeekableFileStream(new File(uri)));
        } else {
            if (proto.equals("http") || proto.equals("https")) {
                result = new SeekableHTTPStream(uri.toURL());
            } else if (proto.equals("ftp")) {
                result = new SeekableFTPStream(uri.toURL());
            } else {
                throw new UnknownSchemeException(uri);
            }
            if (allowCaching) {
                //result = new CacheableSABS(result, CacheableSABS.DEFAULT_BLOCK_SIZE, uri);
                result = new CachedSeekableStream(result, BrowserSettings.getRemoteBufferSize(), uri);
            } else {
                result = new SeekableBufferedStream(result);
            }
        }
        return result;
    }

    /**
     * Given a URI, return a SeekableStream of the appropriate type.  If caching is
     * enabled in the preferences, it will be used as appropriate.
     *
     * @param uri an ftp:, http:, or file: URI
     * @return a SeekableStream which can be passed to SavantROFile or BAMDataSource
     */
    public static SeekableStream getSeekableStreamForURI(URI uri) throws IOException {
        return getSeekableStreamForURI(uri, BrowserSettings.getCachingEnabled());
    }

    /**
     * Goes through the logic of getSeekableStreamForURI, but doesn't actually create a stream.
     * Useful for determining whether an error might be due to cache corruption, in which case
     * a retry is worth taking.
     *
     * @param uri an ftp:, http:, or file: URI
     * @return <code>true</code> if the URI would be opened with a CacheableSABS
     */
    public static boolean isStreamCached(URI uri) {
        if (BrowserSettings.getCachingEnabled()) {
            String proto = uri.getScheme().toLowerCase();
            if (proto.equals("http") || proto.equals("https") || proto.equals("ftp")) {
                return true;
            }
        }
        return false;
    }

    /**
     * Open a stream for the given URL with the CONNECT_TIMEOUT and READ_TIMEOUT.
     * @throws IOException
     */
    public static InputStream openStream(URL url) throws IOException {
        URLConnection conn = url.openConnection();
        conn.setConnectTimeout(CONNECT_TIMEOUT);
        conn.setReadTimeout(READ_TIMEOUT);
        return conn.getInputStream();
    }

    /**
     * Extract the file extension from the given URL.
     *
     * @param url The URL from which to extract the extension
     * @return The extension of the URL
     */
    public static String getExtension(URL url) {
        return MiscUtils.getExtension(url.toString());
    }

    /**
     * Extract the file-name portion of a URI.
     *
     * @param uri the URI to be processed
     * @return the file-name portion of the URI
     */
    public static String getFileName(URI uri) {
        String path = uri.toString();
        int lastSlashIndex = path.lastIndexOf("/");
        return path.substring(lastSlashIndex + 1, path.length());
    }

    /**
     * If u is a file:// URI, return the absolute path.  If it's a network URI, leave
     * it unchanged.
     *
     * @param u the URI to be neatened
     * @return a canonical string representing the URI.
     */
    public static String getNeatPathFromURI(URI u) {
        if (u == null) {
            return "";
        }
        if ("file".equals(u.getScheme())) {
            return (new File(u)).getAbsolutePath();
        }
        return u.toString();
    }

    /**
     * Get a URI from a string.  In most cases, this is just the plain URI, but in the
     * case of file-paths, this may include escaping special characters.  Effectively
     * the inverse of getNeatPathFromURI().
     *
     * @param fileOrURI a string containing either a URI or a file-system path.
     * @return a properly-formed URI
     */
    public static URI getURIFromPath(String fileOrURI) {
        URI uri = null;
        try {
            uri = new URI(fileOrURI);
            if (uri.getScheme() == null) {
                uri = new File(fileOrURI).toURI();
            }
        } catch (URISyntaxException usx) {
            // This can happen if we're passed a file-name containing spaces.
            uri = new File(fileOrURI).toURI();
        }
        return uri;
    }

    /**
     * Create a URL object from a string which we know to be a valid URL.  Avoids having
     * to catch a MalformedURLException which we know will never be thrown.  Intended
     * as the URL equivalent to <code>URL.create()</code>.
     */
    public static URL getKnownGoodURL(String url) {
        try {
            return new URL(url);
        } catch (MalformedURLException ignored) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Create a URL object from an existing URL and a string which we know to be a valid path.  Avoids having
     * to catch a MalformedURLException which we know will never be thrown.  Intended
     * as the URL equivalent to <code>URL.create()</code>.
     */
    public static URL getKnownGoodURL(URL base, String spec) {
        try {
            String baseStr = base.toString();
            if (!baseStr.endsWith("/")) {
                baseStr += "/";
            }
            return new URL(baseStr + spec);
        } catch (MalformedURLException ignored) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Synchronously download the given URL to the given destination directory.
     *
     * @param u the URL to be downloaded
     * @param destDir the destination directory
     * @param fileName the destination file within <code>destDir</code>; use <code>null</code> to infer the name from the URL
     * @return the downloaded file
     */
    public static File downloadFile(URL u, File destDir, String fileName) throws IOException {
        File f = new File(destDir, fileName != null ? fileName : MiscUtils.getFilenameFromPath(u.getPath()));

        InputStream in = NetworkUtils.openStream(u);
        OutputStream out = new FileOutputStream(f);
        byte[] buf = new byte[BUF_SIZE];
        int bytesRead;
        while ((bytesRead = in.read(buf)) != -1) {
            out.write(buf, 0, bytesRead);
        }

        return f;
    }

    /**
     * Synchronously download a (small) file and read its contents to a String.
     *
     * @param u the URL to be downloaded
     * @return a string containing the contents of the URL
     */
    public static String downloadFile(URL u) throws IOException {

        StringBuilder result = new StringBuilder();

        InputStream in = NetworkUtils.openStream(u);
        byte[] buf = new byte[BUF_SIZE];
        int bytesRead;
        while ((bytesRead = in.read(buf)) != -1) {
            char[] r = (new String(buf)).toCharArray();
            result.append(r, 0, bytesRead);
        }

        return result.toString();
    }

    /**
     * Download a file in the background.  Notification events will be sent to the
     * supplied listener.
     *
     * @param u the HTTP URL to be downloaded
     * @param destDir destination directory for the file
     * @param fileName the destination file within <code>destDir</code>; use <code>null</code> to infer the name from the URL
     * @param monitor will receive DownloadEvents
     */
    public static void downloadFile(final URL u, final File destDir, final String fileName,
            final DownloadMonitor monitor) {
        new Thread("NetworkUtils.downloadFile") {
            double totalBytes;

            @Override
            public void run() {
                try {
                    HttpURLConnection httpConn = (HttpURLConnection) u.openConnection();
                    totalBytes = httpConn.getContentLength();

                    File destFile = new File(destDir,
                            fileName != null ? fileName : MiscUtils.getFilenameFromPath(u.getPath()));
                    OutputStream out = new FileOutputStream(destFile);
                    InputStream in = NetworkUtils.openStream(u);
                    fireDownloadEvent(monitor, new DownloadEvent(DownloadEvent.Type.STARTED));
                    byte[] buf = new byte[BUF_SIZE];
                    long bytesSoFar = 0;
                    int bytesRead;
                    while ((bytesRead = in.read(buf)) != -1 && !monitor.isCancelled()) {
                        out.write(buf, 0, bytesRead);
                        if (totalBytes > 0.0) {
                            bytesSoFar += bytesRead;
                            monitor.handleEvent(new DownloadEvent(bytesSoFar / totalBytes));
                        }
                    }
                    fireDownloadEvent(monitor, new DownloadEvent(destFile));
                } catch (IOException x) {
                    fireDownloadEvent(monitor, new DownloadEvent(x));
                }
            }
        }.start();
    }

    private static void fireDownloadEvent(final Listener<DownloadEvent> listener, final DownloadEvent e) {
        MiscUtils.invokeLaterIfNecessary(new Runnable() {
            @Override
            public void run() {
                listener.handleEvent(e);
            }
        });
    }
}