org.qedeq.base.io.UrlUtility.java Source code

Java tutorial

Introduction

Here is the source code for org.qedeq.base.io.UrlUtility.java

Source

/* This file is part of the project "Hilbert II" - http://www.qedeq.org
 *
 * Copyright 2000-2013,  Michael Meyling <mime@qedeq.org>.
 *
 * "Hilbert II" is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */

package org.qedeq.base.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.qedeq.base.trace.Trace;
import org.qedeq.base.utility.YodaUtility;

/**
 * A collection of useful static methods for URL s.
 *
 * @author  Michael Meyling
 */
public final class UrlUtility {

    /** This class, for debugging purpose. */
    private static final Class CLASS = UrlUtility.class;

    /**
     * Constructor, should never be called.
     */
    private UrlUtility() {
        // don't call me
    }

    /**
     * Convert file in URL.
     *
     * @param   file    File.
     * @return  URL.
     */
    public static URL toUrl(final File file) {
        try {
            return file.getAbsoluteFile().toURI().toURL();
        } catch (MalformedURLException e) { // should only happen if there is a bug in the JDK
            throw new RuntimeException(e);
        }
    }

    /**
     * Convert URL path in file path.
     *
     * @param   url    Convert this URL path.
     * @return  File path.
     */
    public static File transformURLPathToFilePath(final URL url) {
        try {
            return new File(URLDecoder.decode(url.getFile(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Create relative address from <code>origin</code> to <code>next</code>.
     * The resulting file path has "/" as directory name separator.
     * If the resulting file path is the same as origin specifies, we return "".
     * Otherwise the result will always have an "/" as last character.
     *
     * @param   origin  This is the original location. Must be a directory.
     * @param   next    This should be the next location. Must also be a directory.
     * @return  Relative (or if necessary absolute) file path.
     */
    public static final String createRelativePath(final File origin, final File next) {
        if (origin.equals(next)) {
            return "";
        }
        final Path org = new Path(origin.getPath().replace(File.separatorChar, '/'), "");
        final Path ne = new Path(next.getPath().replace(File.separatorChar, '/'), "");
        return org.createRelative(ne.toString()).toString();
    }

    /**
     * Simplify file URL by returning a file path.
     *
     * @param   url     URL to simplify.
     * @return  File path (if protocol is "file"). Otherwise just return <code>url</code>.
     */
    public static String easyUrl(final String url) {
        String result = url;
        try {
            final URL u = new URL(url);
            // is this a file URL?
            if (u.getProtocol().equalsIgnoreCase("file")) {
                return transformURLPathToFilePath(u).getCanonicalPath();
            }
        } catch (RuntimeException e) {
            //  ignore
        } catch (IOException e) {
            //  ignore
        }
        return result;
    }

    /**
     * Make local copy of an URL.
     *
     * @param   url             Save this URL.
     * @param   f               Save into this file. An existing file is overwritten.
     * @param   proxyHost       Use this proxy host.
     * @param   proxyPort       Use this port at proxy host.
     * @param   nonProxyHosts   This are hosts not to be proxied.
     * @param   connectTimeout  Connection timeout.
     * @param   readTimeout     Read timeout.
     * @param   listener        Here completion events are fired.
     * @throws  IOException     Saving failed.
     */
    public static void saveUrlToFile(final String url, final File f, final String proxyHost, final String proxyPort,
            final String nonProxyHosts, final int connectTimeout, final int readTimeout,
            final LoadingListener listener) throws IOException {
        final String method = "saveUrlToFile()";
        Trace.begin(CLASS, method);

        // if we are not web started and running under Java 1.4 we use apache commons
        // httpclient library (so we can set timeouts)
        if (!isSetConnectionTimeOutSupported() && !IoUtility.isWebStarted()) {
            saveQedeqFromWebToBufferApache(url, f, proxyHost, proxyPort, nonProxyHosts, connectTimeout, readTimeout,
                    listener);
            Trace.end(CLASS, method);
            return;
        }

        // set proxy properties according to kernel configuration (if not webstarted)
        if (!IoUtility.isWebStarted()) {
            if (proxyHost != null) {
                System.setProperty("http.proxyHost", proxyHost);
            }
            if (proxyPort != null) {
                System.setProperty("http.proxyPort", proxyPort);
            }
            if (nonProxyHosts != null) {
                System.setProperty("http.nonProxyHosts", nonProxyHosts);
            }
        }

        FileOutputStream out = null;
        InputStream in = null;
        try {
            final URLConnection connection = new URL(url).openConnection();

            if (connection instanceof HttpURLConnection) {
                final HttpURLConnection httpConnection = (HttpURLConnection) connection;
                // if we are running at least under Java 1.5 the following code should be executed
                if (isSetConnectionTimeOutSupported()) {
                    try {
                        YodaUtility.executeMethod(httpConnection, "setConnectTimeout", new Class[] { Integer.TYPE },
                                new Object[] { new Integer(connectTimeout) });
                    } catch (NoSuchMethodException e) {
                        Trace.fatal(CLASS, method, "URLConnection.setConnectTimeout was previously found", e);
                    } catch (InvocationTargetException e) {
                        Trace.fatal(CLASS, method, "URLConnection.setConnectTimeout throwed an error", e);
                    }
                }
                // if we are running at least under Java 1.5 the following code should be executed
                if (isSetReadTimeoutSupported()) {
                    try {
                        YodaUtility.executeMethod(httpConnection, "setReadTimeout", new Class[] { Integer.TYPE },
                                new Object[] { new Integer(readTimeout) });
                    } catch (NoSuchMethodException e) {
                        Trace.fatal(CLASS, method, "URLConnection.setReadTimeout was previously found", e);
                    } catch (InvocationTargetException e) {
                        Trace.fatal(CLASS, method, "URLConnection.setReadTimeout throwed an error", e);
                    }
                }
                int responseCode = httpConnection.getResponseCode();
                if (responseCode == 200) {
                    in = httpConnection.getInputStream();
                } else {
                    in = httpConnection.getErrorStream();
                    final String errorText = IoUtility.loadStreamWithoutException(in, 1000);
                    throw new IOException("Response code from HTTP server was " + responseCode
                            + (errorText.length() > 0 ? "\nResponse  text from HTTP server was:\n" + errorText
                                    : ""));
                }
            } else {
                Trace.paramInfo(CLASS, method, "connection.getClass", connection.getClass().toString());
                in = connection.getInputStream();
            }

            if (!url.equals(connection.getURL().toString())) {
                throw new FileNotFoundException(
                        "\"" + url + "\" was substituted by " + "\"" + connection.getURL() + "\" from server");
            }
            final double maximum = connection.getContentLength();
            IoUtility.createNecessaryDirectories(f);
            out = new FileOutputStream(f);
            final byte[] buffer = new byte[4096];
            int bytesRead; // bytes read during one buffer read
            int position = 0; // current reading position within the whole document
            // continue writing
            while ((bytesRead = in.read(buffer)) != -1) {
                position += bytesRead;
                out.write(buffer, 0, bytesRead);
                if (maximum > 0) {
                    double completeness = position / maximum;
                    if (completeness < 0) {
                        completeness = 0;
                    }
                    if (completeness > 100) {
                        completeness = 1;
                    }
                    listener.loadingCompletenessChanged(completeness);
                }
            }
            listener.loadingCompletenessChanged(1);
        } finally {
            IoUtility.close(out);
            out = null;
            IoUtility.close(in);
            in = null;
            Trace.end(CLASS, method);
        }
    }

    /**
     * Make local copy of a http accessable URL. This method uses apaches HttpClient,
     * but it dosn't work under webstart with proxy configuration. If we don't use this
     * method, the apache commons-httpclient library can be removed
     *
     * @param   url             Save this URL.
     * @param   f               Save into this file. An existing file is overwritten.
     * @param   proxyHost       Use this proxy host.
     * @param   proxyPort       Use this port at proxy host.
     * @param   nonProxyHosts   This are hosts not to be proxied.
     * @param   connectTimeout  Connection timeout.
     * @param   readTimeout     Read timeout.
     * @param   listener        Here completion events are fired.
     * @throws  IOException     Saving failed.
     */
    private static void saveQedeqFromWebToBufferApache(final String url, final File f, final String proxyHost,
            final String proxyPort, final String nonProxyHosts, final int connectTimeout, final int readTimeout,
            final LoadingListener listener) throws IOException {
        final String method = "saveQedeqFromWebToBufferApache()";
        Trace.begin(CLASS, method);

        // Create an instance of HttpClient.
        HttpClient client = new HttpClient();

        // set proxy properties according to kernel configuration (if not webstarted)
        if (!IoUtility.isWebStarted() && proxyHost != null && proxyHost.length() > 0) {
            final String pHost = proxyHost;
            int pPort = 80;
            if (proxyPort != null) {
                try {
                    pPort = Integer.parseInt(proxyPort);
                } catch (RuntimeException e) {
                    Trace.fatal(CLASS, method, "proxy port not numeric: " + proxyPort, e);
                }
            }
            if (pHost.length() > 0) {
                client.getHostConfiguration().setProxy(pHost, pPort);
            }
        }

        // Create a method instance.
        GetMethod httpMethod = new GetMethod(url);

        try {
            // Provide custom retry handler is necessary
            httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                    new DefaultHttpMethodRetryHandler(3, false));

            httpMethod.getParams().setSoTimeout(connectTimeout);
            // Throws IOException on TimeOut.

            int statusCode = client.executeMethod(httpMethod);

            if (statusCode != HttpStatus.SC_OK) {
                throw new FileNotFoundException("Problems loading: " + url + "\n" + httpMethod.getStatusLine());
            }

            // Read the response body.
            byte[] responseBody = httpMethod.getResponseBody();
            IoUtility.createNecessaryDirectories(f);
            IoUtility.saveFileBinary(f, responseBody);
            listener.loadingCompletenessChanged(1);
        } finally {
            // Release the connection.
            httpMethod.releaseConnection();
            Trace.end(CLASS, method);
        }
    }

    /**
     * This class ist just for solving the lazy loading problem thread save.
     * see <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom">
     * Initialization_on_demand_holder_idiom</a>.
     */
    private static final class LazyHolderTimeoutMethods {

        /** Lazy initialized constant that knows about the existence of the method
         * <code>URLConnection.setConnectTimeout</code>. This depends on the currently running
         * JVM. */
        private static final boolean IS_SET_CONNECTION_TIMEOUT_SUPPORTED = YodaUtility
                .existsMethod(URLConnection.class, "setConnectTimeout", new Class[] { Integer.TYPE });

        /** Lazy initialized constant that knows about the existence of the method
         * <code>URLConnection.setReadTimeout</code>. This depends on the currently running
         * JVM. */
        private static final boolean IS_SET_READ_TIMEOUT_SUSPPORTED = YodaUtility.existsMethod(URLConnection.class,
                "setReadTimeout", new Class[] { Integer.TYPE });

    }

    /**
     * Is setting of connection timeout supported in current environment?
     *
     * @return  Setting connection timeout supported?
     */
    public static boolean isSetConnectionTimeOutSupported() {
        return LazyHolderTimeoutMethods.IS_SET_CONNECTION_TIMEOUT_SUPPORTED;
    }

    /**
     * Is setting of read timeout supported in current environment?
     *
     * @return  Setting read timeout supported?
     */
    public static boolean isSetReadTimeoutSupported() {
        return LazyHolderTimeoutMethods.IS_SET_READ_TIMEOUT_SUSPPORTED;
    }

}