io.wcm.maven.plugins.contentpackage.DownloadMojo.java Source code

Java tutorial

Introduction

Here is the source code for io.wcm.maven.plugins.contentpackage.DownloadMojo.java

Source

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.maven.plugins.contentpackage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.plexus.util.IOUtil;
import org.json.JSONObject;

import io.wcm.maven.plugins.contentpackage.unpacker.ContentUnpacker;

/**
 * Builds and downloads a content package defined on a remote CRX or AEM system.
 */
@Mojo(name = "download", defaultPhase = LifecyclePhase.INSTALL, requiresProject = false, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
public final class DownloadMojo extends AbstractContentPackageMojo {

    /**
     * The output file to save.
     */
    @Parameter(property = "vault.outputFile", required = true, defaultValue = "${project.build.directory}/${project.build.finalName}.zip")
    private String outputFile;

    /**
     * If set to true the package is unpacked to the directory specified by <code>unpackDirectory </code>.
     */
    @Parameter(property = "vault.unpack", defaultValue = "false")
    private boolean unpack;

    /**
     * Directory to unpack the content of the package to.
     */
    @Parameter(property = "vault.unpackDirectory", defaultValue = "${basedir}")
    private File unpackDirectory;

    /**
     * If unpack=true: delete existing content from the named directories (relative to <code>unpackDirectory</code> root)
     * before unpacking the package content, to make sure only the content from the downloaded package remains.
     */
    @Parameter
    private String[] unpackDeleteDirectories = new String[] { "jcr_root", "META-INF" };

    /**
     * List of regular patterns matching relative path of extracted content package. All files matching these patterns
     * are excluded when unpacking the content package.
     */
    @Parameter
    private String[] excludeFiles;

    /**
     * List of regular patterns matching node paths inside a <code>.content.xml</code> file. All nodes matching
     * theses patterns are removed from the <code>.content.xml</code> when unpacking the content package.
     */
    @Parameter
    private String[] excludeNodes;

    /**
     * List of regular patterns matching property names inside a <code>.content.xml</code> file. All properties matching
     * theses patterns are removed from the <code>.content.xml</code> when unpacking the content package.
     */
    @Parameter
    private String[] excludeProperties;

    /**
     * Downloads the files
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (isSkip()) {
            return;
        }

        File outputFileObject = downloadFile(getPackageFile(), this.outputFile);
        if (this.unpack) {
            unpackFile(outputFileObject);
        }
    }

    /**
     * Download content package from CRX instance
     */
    private File downloadFile(File file, String ouputFilePath) throws MojoExecutionException {
        try (CloseableHttpClient httpClient = getHttpClient()) {
            getLog().info("Download " + file.getName() + " from " + getCrxPackageManagerUrl());

            // 1st: try upload to get path of package - or otherwise make sure package def exists (no install!)
            HttpPost post = new HttpPost(getCrxPackageManagerUrl() + "/.json?cmd=upload");
            MultipartEntityBuilder entity = MultipartEntityBuilder.create().addBinaryBody("package", file)
                    .addTextBody("force", "true");
            post.setEntity(entity.build());
            JSONObject jsonResponse = executePackageManagerMethodJson(httpClient, post);
            boolean success = jsonResponse.optBoolean("success", false);
            String msg = jsonResponse.optString("msg", null);
            String path = jsonResponse.optString("path", null);

            // package already exists - get path from error message and continue
            if (!success && StringUtils.startsWith(msg, CRX_PACKAGE_EXISTS_ERROR_MESSAGE_PREFIX)
                    && StringUtils.isEmpty(path)) {
                path = StringUtils.substringAfter(msg, CRX_PACKAGE_EXISTS_ERROR_MESSAGE_PREFIX);
                success = true;
            }
            if (!success) {
                throw new MojoExecutionException("Package path detection failed: " + msg);
            }

            getLog().info("Package path is: " + path + " - now rebuilding package...");

            // 2nd: build package
            HttpPost buildMethod = new HttpPost(getCrxPackageManagerUrl() + "/console.html" + path + "?cmd=build");
            executePackageManagerMethodHtml(httpClient, buildMethod, 0);

            // 3rd: download package
            String crxUrl = StringUtils.removeEnd(getCrxPackageManagerUrl(), "/crx/packmgr/service");
            HttpGet downloadMethod = new HttpGet(crxUrl + path);

            // execute download
            CloseableHttpResponse response = httpClient.execute(downloadMethod);
            try {
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

                    // get response stream
                    InputStream responseStream = response.getEntity().getContent();

                    // delete existing file
                    File outputFileObject = new File(ouputFilePath);
                    if (outputFileObject.exists()) {
                        outputFileObject.delete();
                    }

                    // write response file
                    FileOutputStream fos = new FileOutputStream(outputFileObject);
                    IOUtil.copy(responseStream, fos);
                    fos.flush();
                    responseStream.close();
                    fos.close();

                    getLog().info("Package downloaded to " + outputFileObject.getAbsolutePath());

                    return outputFileObject;
                } else {
                    throw new MojoExecutionException(
                            "Package download failed:\n" + EntityUtils.toString(response.getEntity()));
                }
            } finally {
                if (response != null) {
                    EntityUtils.consumeQuietly(response.getEntity());
                    try {
                        response.close();
                    } catch (IOException ex) {
                        // ignore
                    }
                }
            }
        } catch (FileNotFoundException ex) {
            throw new MojoExecutionException("File not found: " + file.getAbsolutePath(), ex);
        } catch (IOException ex) {
            throw new MojoExecutionException("Download operation failed.", ex);
        }
    }

    /**
     * Unpack content package
     */
    private void unpackFile(File file) throws MojoExecutionException, MojoFailureException {

        // initialize unpacker to validate patterns
        ContentUnpacker unpacker = new ContentUnpacker(this.excludeFiles, this.excludeNodes,
                this.excludeProperties);

        // validate output directory
        if (this.unpackDirectory == null) {
            throw new MojoExecutionException("No unpack directory specified.");
        }
        if (!this.unpackDirectory.exists()) {
            this.unpackDirectory.mkdirs();
        }

        // remove existing content
        if (this.unpackDeleteDirectories != null) {
            for (String directory : unpackDeleteDirectories) {
                File directoryFile = FileUtils.getFile(this.unpackDirectory, directory);
                if (directoryFile.exists()) {
                    if (!deleteDirectoryWithRetries(directoryFile, 0)) {
                        throw new MojoExecutionException(
                                "Unable to delete existing content from " + directoryFile.getAbsolutePath());
                    }
                }
            }
        }

        // unpack file
        unpacker.unpack(file, this.unpackDirectory);

        getLog().info("Package unpacked to " + this.unpackDirectory.getAbsolutePath());
    }

    /**
     * Delete fails sometimes or may be blocked by an editor - give it some time to try again (max. 1 sec).
     */
    private boolean deleteDirectoryWithRetries(File directory, int retryCount) {
        if (retryCount > 100) {
            return false;
        }
        if (FileUtils.deleteQuietly(directory)) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                // ignore
            }
            return deleteDirectoryWithRetries(directory, retryCount + 1);
        }
    }

}