de.elomagic.maven.http.HTTPMojo.java Source code

Java tutorial

Introduction

Here is the source code for de.elomagic.maven.http.HTTPMojo.java

Source

/*
 * Copyright 2015 Carsten Rambow.
 *
 * 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 de.elomagic.maven.http;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;

/**
 *
 * @author Carsten Rambow
 */
@Mojo(name = "http")
public class HTTPMojo extends AbstractMojo {

    @Parameter(defaultValue = "${session}", readonly = true)
    private MavenSession session;

    /**
     * HTTP request URL.
     * <p>
     * Required parameter
     */
    @Parameter(required = true)
    private URL url;
    /**
     * HTTP request method.
     * <p>
     * Required parameter
     */
    @Parameter(required = true)
    private RequestMethod method;
    /**
     * Server id to lookup credential for BASIC authentication .
     */
    @Parameter
    private String serverId;
    /**
     * If set then this file will be uploaded.
     */
    @Parameter
    private File fromFile;
    @Parameter
    private File toFile;
    /**
     * Folder where the downloaded content will be persisted.
     * <p>
     * Must be set when parameter <code>unpack</code> is true
     */
    @Parameter
    private File outputDirectory;
    /**
     * This is MIME type of the body content which will be send with the HTTP request.
     * <p>
     * If unset then it will be tried to get from file extension of property <code>fromFile</code>
     */
    @Parameter
    private String contentType;
    /**
     * HTTP header properties.
     * <p>
     * Optional parameters
     * <p>
     * Parameter "Accept" must be set with a different option parameter.
     */
    @Parameter
    private Map<String, String> httpHeaders;
    /**
     * HTTP form properties.
     * <p>
     * Optional parameters
     */
    @Parameter
    private Map<String, String> formParams;
    /**
     * If true then the response body will be unpacked into the folder of property <code>outputDirectory</code>.
     */
    @Parameter
    private boolean unpack;

    /**
     * If true then response body will be printed on level "info" to the log output.
     */
    @Parameter
    private boolean printResponse;

    /**
     * If true then plug-in will ignore any errors occurred during execution.
     */
    @Parameter
    private boolean failOnError = true;

    /**
     * If true then relaxed SSL check for user generated certificates.
     */
    @Parameter
    private boolean httpsInsecure;

    /**
     * Define accepted media types in the response.
     * <p>
     * Default: *//*;
                   */
    @Parameter
    private String accept = "*/*";

    /**
     * Digest value.
     * <p>
     * If set then parameter <code>digestType<code> must also be set.
     * <p>
     * The response content will be validated against this value.
     */
    @Parameter
    private String digest;

    /**
     * Type of digest (Default "sha1").
     * <p>
     * Supported types: "md5", "sha1", "sha256", "sha384", "sha512"
     */
    @Parameter
    private String digestType;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {

        try {
            Executor executor;

            if (httpsInsecure) {
                getLog().info("Accepting unsecure HTTPS connections.");
                try {
                    SSLContextBuilder builder = new SSLContextBuilder();
                    builder.loadTrustMaterial(null, new TrustAllStrategy());
                    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());

                    final Registry<ConnectionSocketFactory> sfr = RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslsf).build();

                    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
                            sfr);
                    connectionManager.setDefaultMaxPerRoute(100);
                    connectionManager.setMaxTotal(200);
                    connectionManager.setValidateAfterInactivity(1000);

                    HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connectionManager)
                            .build();

                    executor = Executor.newInstance(httpClient);
                } catch (Exception ex) {
                    throw new Exception("Unable to setup HTTP client for unstrusted connections.", ex);
                }
            } else {
                executor = Executor.newInstance();
            }

            Settings settings = session.getSettings();
            if (StringUtils.isNotBlank(serverId)) {
                Server server = settings.getServer(serverId);
                if (server == null) {
                    throw new Exception("Server ID \"" + serverId + "\" not found in your Maven settings.xml");
                }
                getLog().debug("ServerId: " + serverId);
                executor.auth(server.getUsername(), server.getPassword());
            }

            Request request = createRequestMethod();

            request.setHeader("Accept", accept);

            if (httpHeaders != null) {
                for (Entry<String, String> entry : httpHeaders.entrySet()) {
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            }

            if (formParams != null) {
                Form form = Form.form();
                for (Entry<String, String> entry : formParams.entrySet()) {
                    form.add(entry.getKey(), entry.getValue());
                }
            }

            if (fromFile != null) {
                if (!fromFile.exists()) {
                    throw new MojoExecutionException("From file \"" + fromFile + "\" doesn't exist.");
                }

                if (StringUtils.isBlank(contentType)) {
                    contentType = Files.probeContentType(fromFile.toPath());
                }

                getLog().debug("From file: " + fromFile);
                getLog().debug("Upload file size: "
                        + FileUtils.byteCountToDisplaySize(new Long(fromFile.length()).intValue()));
                getLog().debug("Content type: " + contentType);

                if (StringUtils.isBlank(contentType)) {
                    request.body(new FileEntity(fromFile));
                } else {
                    request.body(new FileEntity(fromFile, ContentType.create(contentType)));
                }
            }

            getLog().info(method + " " + url);

            Response response = executor.execute(request);
            handleResponse(response);
        } catch (Exception ex) {
            getLog().error(ex);
            if (failOnError) {
                throw new MojoExecutionException(ex.getMessage(), ex);
            } else {
                getLog().info("Fail on error is disabled. Continue execution.");
            }
        }

    }

    private void handleResponse(final Response response) throws Exception {
        HttpResponse httpResponse = response.returnResponse();
        StatusLine statusLine = httpResponse.getStatusLine();
        int statusCode = statusLine.getStatusCode();

        if (statusCode < 200 || statusCode > 299) {
            if (httpResponse.getEntity() != null) {
                getLog().info(EntityUtils.toString(httpResponse.getEntity()));
            } else {
                getLog().info("Response body is empty.");
            }

            throw new Exception("Response status code " + statusCode + ": " + statusLine.getReasonPhrase());
        }

        getLog().info("Response status code " + statusCode + ": " + statusLine.getReasonPhrase());

        HttpEntity entity = httpResponse.getEntity();
        if (entity == null) {
            getLog().info("Response body is empty.");
        } else if (unpack) {
            File downloadedFile = File.createTempFile("mvn_http_plugin_", ".tmp");
            try (InputStream in = entity.getContent()) {
                Files.copy(in, downloadedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

                if (StringUtils.isNoneBlank(digest)) {
                    checkFile(downloadedFile.toPath());
                }

                getLog().info("Downloaded file size: "
                        + FileUtils.byteCountToDisplaySize(new Long(downloadedFile.length()).intValue()));

                unzipToFile(downloadedFile, outputDirectory);
            } finally {
                downloadedFile.delete();
            }
        } else if (toFile != null) {
            try (InputStream in = entity.getContent()) {
                Files.copy(in, toFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            }

            if (StringUtils.isNoneBlank(digest)) {
                checkFile(toFile.toPath());
            }

            getLog().debug("Downloaded file location: " + toFile);
            getLog().debug("Downloaded file size: "
                    + FileUtils.byteCountToDisplaySize(new Long(toFile.length()).intValue()));
        } else if (printResponse) {
            getLog().info("Response content: " + IOUtil.toString(entity.getContent(), "utf-8"));
        }
    }

    private Request createRequestMethod() throws Exception {
        switch (method) {
        case DELETE:
            return Request.Delete(url.toURI());
        case GET:
            return Request.Get(url.toURI());
        case HEAD:
            return Request.Head(url.toURI());
        case POST:
            return Request.Post(url.toURI());
        case PUT:
            return Request.Put(url.toURI());
        }

        throw new Exception("Unsupported HTTP method \"" + method + "\".");
    }

    /**
     * @todo Must may be refactored. Only a prototype
     * @param file
     * @param destDirectory
     * @throws MojoExecutionException
     */
    private void unzipToFile(final File file, final File destDirectory) throws Exception {
        getLog().info("Extracting downloaded file to folder \"" + destDirectory + "\".");

        int filesCount = 0;

        // create the destination directory structure (if needed)
        destDirectory.mkdirs();

        // open archive for reading
        try (ZipFile zipFile = new ZipFile(file, ZipFile.OPEN_READ)) {
            // For every zip archive entry do
            Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
            while (zipFileEntries.hasMoreElements()) {
                ZipEntry entry = zipFileEntries.nextElement();
                getLog().debug("Extracting entry: " + entry);

                //create destination file
                // TODO Check entry path on path injections
                File destFile = new File(destDirectory, entry.getName());

                //create parent directories if needed
                File parentDestFile = destFile.getParentFile();
                parentDestFile.mkdirs();

                if (!entry.isDirectory()) {
                    BufferedInputStream bufIS = new BufferedInputStream(zipFile.getInputStream(entry));

                    // write the current file to disk
                    Files.copy(bufIS, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    filesCount++;
                }
            }
        }

        getLog().info(filesCount + " files extracted.");
    }

    private void checkFile(final Path path) throws Exception {

        String fileDigest;

        try (InputStream in = Files.newInputStream(path)) {
            switch (digestType.toLowerCase()) {
            case "md5":
                fileDigest = DigestUtils.md5Hex(in);
                break;
            case "sha1":
                fileDigest = DigestUtils.sha1Hex(in);
                break;
            case "sha256":
                fileDigest = DigestUtils.sha256Hex(in);
                break;
            case "sha384":
                fileDigest = DigestUtils.sha384Hex(in);
                break;
            case "sha512":
                fileDigest = DigestUtils.sha512Hex(in);
                break;

            default:
                throw new Exception("Digest type \"" + digestType + "\" not supported.");
            }
        }

        if (!fileDigest.equalsIgnoreCase(digest)) {
            throw new Exception(
                    digestType + " digest check failed. File=\"" + fileDigest + "\", Digest=\"" + digest + "\"");
        }

    }
}