com.isomorphic.maven.packaging.Downloads.java Source code

Java tutorial

Introduction

Here is the source code for com.isomorphic.maven.packaging.Downloads.java

Source

package com.isomorphic.maven.packaging;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import com.isomorphic.maven.util.LoggingCountingOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.settings.Proxy;
import org.codehaus.plexus.util.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Connects to Isomorphic site, discovers which files exist for a given build, and downloads them 
 * to local file system. 
 */
public class Downloads {

    private static final Logger LOGGER = LoggerFactory.getLogger(Downloads.class);

    private static final String DOMAIN = "www.smartclient.com";
    private static final String LOGIN_URL = "/devlogin/login.jsp";
    private static final String LOGOUT_URL = "/logout.jsp";

    private File toFolder = new File(System.getProperty("java.io.tmpdir"));
    private Boolean overwriteExistingFiles = Boolean.FALSE;
    private Proxy proxyConfiguration;

    private UsernamePasswordCredentials credentials;

    private DefaultHttpClient httpClient = new DefaultHttpClient();
    private HttpHost host = new HttpHost(DOMAIN);

    /**
     * Constructor taking the credentials needed for authentication on smartclient.com. 
     * 
     * @param credentials The credentials needed for authentication on smartclient.com.
     */
    public Downloads(UsernamePasswordCredentials credentials) {
        this.credentials = credentials;
    }

    /**
     * Set the proxy configuration, if any, needed to support network operations from behind
     * a proxy.
     * 
     * @param proxyConfiguration the proxy configuration, if any, needed to support network operations from behind a proxy
     * @see http://maven.apache.org/guides/mini/guide-proxies.html
     */
    public void setProxyConfiguration(Proxy proxyConfiguration) {
        this.proxyConfiguration = proxyConfiguration;
    }

    /**
     * Sets the directory to which the distribution/s should be downloaded.
     * Defaults to the system property <code>java.io.tmpdir</code>.
     * 
     * @param toFolder The directory to which the distribution/s should be downloaded.
     */
    public void setToFolder(File toFolder) {
        this.toFolder = toFolder;
    }

    /**
     * If true, downloads files whether they already exist locally or not.  Skips
     * the download otherwise.  Defaults to false. 
     * 
     * @param overwriteExistingFiles
     */
    public void setOverwriteExistingFiles(Boolean overwriteExistingFiles) {
        this.overwriteExistingFiles = overwriteExistingFiles;
    }

    /**
     * Retrieves a {@link Distribution} instance for each of the given licenses, downloads
     * files if necessary, and {@link Distribution#getFiles() links} the local file to the distribution.
     * 
     * @param product The product built and distributed by Isomorphic Software.  e.g., SmartCLient 
     * @param buildNumber The build number of the desired distribution.  e.g., 4.1d
     * @param buildDate The date on which the distribution was made available
     * @param licenses The licenses, or editions, that the product is released under, and for which the user is registered
     * @return A collection of Distributions, each having its contents resolved to local files, and suitable for use in repacking operations
     * @throws MojoExecutionException on any error
     * 
     * @see http://www.smartclient.com/builds/
     */
    public List<Distribution> fetch(Product product, String buildNumber, String buildDate, License... licenses)
            throws MojoExecutionException {

        try {
            setup();
            login();

            List<Distribution> result = new ArrayList<Distribution>();

            for (License license : licenses) {
                Distribution distribution = Distribution.get(product, license, buildNumber, buildDate);
                download(distribution);
                result.add(distribution);
            }
            logout();

            return result;

        } finally {
            httpClient.getConnectionManager().shutdown();
        }

    }

    /**
     * Obtains a list of hyperlinks, downloads the file/s represented by each, and links it/them to the given distribution.
     * 
     * @param distribution
     * @throws MojoExecutionException
     */
    private void download(Distribution distribution) throws MojoExecutionException {

        String[] links = list(distribution);

        for (String link : links) {

            String filename = FilenameUtils.getName(link);
            File file = new File(toFolder, filename);

            if (file.exists() && !overwriteExistingFiles) {
                LOGGER.info("Existing archive found at '{}'.  Skipping download.", file.getAbsolutePath());
                distribution.getFiles().add(file);
                continue;
            }

            HttpGet httpget = new HttpGet(link);
            HttpResponse response;

            try {
                response = httpClient.execute(host, httpget);
            } catch (Exception e) {
                throw new MojoExecutionException("Error issuing GET request for bundle at '" + httpget + "'", e);
            }

            HttpEntity entity = response.getEntity();

            if (!toFolder.mkdirs() && !toFolder.exists()) {
                throw new MojoExecutionException(
                        "Could not create specified working directory '" + toFolder.getAbsolutePath() + "'");
            }

            FileUtils.deleteQuietly(file);

            OutputStream outputStream = null;

            try {
                LOGGER.info("Downloading file to '{}'", file.getAbsolutePath());
                outputStream = new LoggingCountingOutputStream(new FileOutputStream(file),
                        entity.getContentLength());
                entity.writeTo(outputStream);
                distribution.getFiles().add(file);
            } catch (Exception e) {
                throw new MojoExecutionException("Error writing file to '" + file.getAbsolutePath() + "'", e);
            } finally {
                IOUtils.closeQuietly(outputStream);
            }
        }
    }

    /**
     * Interrogates the remote server for a list of hyperlinks matching the given distribution's {@link Distribution#getRemoteIndexFilter() filter}.
     * 
     * @param dist the build in which some files should exist
     * @return a String array of html href attributes
     * @throws MojoExecutionException
     */
    private String[] list(Distribution dist) throws MojoExecutionException {

        HttpGet request = new HttpGet(dist.getRemoteIndex());
        HttpResponse response;

        try {

            LOGGER.debug("Requesting list of files from {}{}", DOMAIN, dist.getRemoteIndex());
            response = httpClient.execute(host, request);

        } catch (Exception e) {
            throw new MojoExecutionException("Error issuing GET request for bundle at '" + request + "'", e);
        }

        Document doc;

        try {

            String html = EntityUtils.toString(response.getEntity());
            doc = Jsoup.parse(html);
            doc.outputSettings().prettyPrint(true);

        } catch (Exception e) {
            throw new MojoExecutionException("Error processing response from '" + request + "'", e);
        }

        List<String> result = new ArrayList<String>();

        Elements links = doc.select(dist.getRemoteIndexFilter());

        for (Element element : links) {
            String href = element.attr("href");
            result.add(href);
        }

        if (result.isEmpty()) {
            String msg = String.format("No downloads found at '%s%s'.  Response from server: \n\n%s\n", DOMAIN,
                    dist.getRemoteIndex(), doc.html());
            LOGGER.warn(msg);
        }

        return result.toArray(new String[0]);
    }

    /**
     * If {@link #credentials} have been supplied, uses them to autthenticate to the isomorphic web site,
     * allowing download of protected resources.
     * 
     * @throws ClientProtocolException
     * @throws IOException
     */
    private void login() throws MojoExecutionException {

        if (credentials == null) {
            return;
        }

        String username = credentials.getUserName();
        String password = credentials.getPassword();

        LOGGER.info("Authenticating to '{}' with username: '{}'", DOMAIN + LOGIN_URL, username);

        HttpPost login = new HttpPost(LOGIN_URL);

        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("USERNAME", username));
        nvps.add(new BasicNameValuePair("PASSWORD", password));

        try {
            login.setEntity(new UrlEncodedFormEntity(nvps));
            HttpResponse response = httpClient.execute(host, login);
            EntityUtils.consume(response.getEntity());
        } catch (IOException e) {
            throw new MojoExecutionException("Error during POST request for authentication", e);
        }
    }

    /**
     * Logs off at smartclient.com.
     *  
     * @throws ClientProtocolException
     * @throws IOException
     */
    private void logout() {
        HttpPost logout = new HttpPost(LOGOUT_URL);
        LOGGER.info("Logging off at '{}'", DOMAIN + LOGOUT_URL);
        try {
            HttpResponse response = httpClient.execute(host, logout);
            EntityUtils.consume(response.getEntity());
        } catch (Exception e) {
            LOGGER.warn("Error at logout ", e);
        }
    }

    /**
     * Configures the {@link #httpClient}, specifically with the current {@link #proxyConfiguration}. 
     * 
     * @throws MojoExecutionException
     */
    private void setup() throws MojoExecutionException {
        try {
            if (proxyConfiguration != null && isProxied(proxyConfiguration)) {
                if (proxyConfiguration.getUsername() != null) {
                    httpClient.getCredentialsProvider().setCredentials(
                            new AuthScope(proxyConfiguration.getHost(), proxyConfiguration.getPort()),
                            new UsernamePasswordCredentials(proxyConfiguration.getUsername(),
                                    proxyConfiguration.getPassword()));
                }
                HttpHost proxy = new HttpHost(proxyConfiguration.getHost(), proxyConfiguration.getPort());
                httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Error obtaining Maven settings", e);
        }
    }

    /**
     * Adapted from the Site plugin's AbstractDeployMojo to allow http operations through proxy.
     * 
     * @see http://maven.apache.org/guides/mini/guide-proxies.html
     * @see http://maven.apache.org/plugins/maven-site-plugin/xref/org/apache/maven/plugins/site/AbstractDeployMojo.html.
     */
    private boolean isProxied(Proxy proxyConfig) throws MalformedURLException {
        String nonProxyHostsAsString = proxyConfig.getNonProxyHosts();

        for (String nonProxyHost : StringUtils.split(nonProxyHostsAsString, ",;|")) {

            if (StringUtils.contains(nonProxyHost, "*")) {

                // Handle wildcard at the end, beginning or middle of the nonProxyHost
                final int pos = nonProxyHost.indexOf('*');
                String nonProxyHostPrefix = nonProxyHost.substring(0, pos);
                String nonProxyHostSuffix = nonProxyHost.substring(pos + 1);

                // prefix*
                if (StringUtils.isNotEmpty(nonProxyHostPrefix) && DOMAIN.startsWith(nonProxyHostPrefix)
                        && StringUtils.isEmpty(nonProxyHostSuffix)) {

                    return false;
                }

                // *suffix
                if (StringUtils.isEmpty(nonProxyHostPrefix) && StringUtils.isNotEmpty(nonProxyHostSuffix)
                        && DOMAIN.endsWith(nonProxyHostSuffix)) {

                    return false;
                }

                // prefix*suffix
                if (StringUtils.isNotEmpty(nonProxyHostPrefix) && DOMAIN.startsWith(nonProxyHostPrefix)
                        && StringUtils.isNotEmpty(nonProxyHostSuffix) && DOMAIN.endsWith(nonProxyHostSuffix)) {

                    return false;
                }

            } else if (DOMAIN.equals(nonProxyHost)) {
                return false;
            }
        }
        return true;
    }

}