DownloadMojo.java
/*
* #%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.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.tooling.commons.packmgr.download.PackageDownloader;
import io.wcm.tooling.commons.packmgr.unpack.ContentUnpacker;
import io.wcm.tooling.commons.packmgr.unpack.ContentUnpackerProperties;
/**
* 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
@SuppressWarnings("PMD.ImmutableField")
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 in the whole content package. 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;
/**
* List of regular patterns matching mixin names inside a <code>.content.xml</code> file. All mixins matching
* theses patterns are removed from the <code>.content.xml</code> when unpacking the content package.
*/
@Parameter
private String[] excludeMixins;
/**
* Set replication status to "activated" for all cq:Page and cq:Template nodes.
*/
@Parameter
private boolean markReplicationActivated;
/**
* List of regular patterns matching node paths in the whole content package. If markReplicationActivated is
* activated it affects only nodes matched by any of these patterns.
*/
@Parameter
private String[] markReplicationActivatedIncludeNodes;
/**
* Sets a fixed date to be used for the "lastReplicated" property when setting replication status to "activated".
* If not set the current date is used.
* <p>
* Use ISO8601 format. Example: <code>2020-01-01T00:00:00.000+02:00</code>.
* </p>
*/
@Parameter
private String dateLastReplicated;
/**
* Whether to upload the local package definition first to CRX package manager before actually downloading the
* package. For this, the local package has to been build locally already.
*/
@Parameter(property = "vault.download.uploadPackageDefinition", defaultValue = "true")
private boolean uploadPackageDefinition;
/**
* Whether to rebuild the package within the CRX package manager before downloading it to include the latest content
* from repository.
*/
@Parameter(property = "vault.download.rebuildPackage", defaultValue = "true")
private boolean rebuildPackage;
/**
* Path of the content package to download. The path is detected automatically when
* <code>uploadPackageDefinition</code> is set to true (which is default). If set to false, the path
* of the content package needs to be specified explicitly.
* <p>
* Example path: <code>/etc/packages/mygroup/mypackage-1.0.0-SNAPSHOT.zip</code>
* </p>
*/
@Parameter(property = "vault.download.contentPackagePath")
private String contentPackagePath;
/**
* Downloads the files
*/
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (isSkip()) {
return;
}
if (this.uploadPackageDefinition && !this.rebuildPackage) {
throw new MojoExecutionException("rebuildPackage=true is required when when uploadPackageDefinition=true.");
}
try (PackageDownloader downloader = new PackageDownloader(getPackageManagerProperties())) {
// uploading package definition
String packagePath;
if (this.uploadPackageDefinition) {
packagePath = downloader.uploadPackageDefinition(getPackageFile());
}
else {
if (StringUtils.isBlank(this.contentPackagePath)) {
throw new MojoExecutionException("Property contentPackagePath needs to be definen when uploadPackageDefinition=false.");
}
packagePath = this.contentPackagePath;
}
// download content package
File outputFileObject = downloader.downloadContentPackage(packagePath, this.outputFile, this.rebuildPackage);
// unpack content package
if (this.unpack) {
unpackFile(outputFileObject);
}
}
catch (IOException ex) {
throw new MojoFailureException("Error during download operation.", ex);
}
}
/**
* Unpack content package
*/
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
private void unpackFile(File file) throws MojoExecutionException {
// initialize unpacker to validate patterns
ContentUnpackerProperties props = new ContentUnpackerProperties();
props.setExcludeFiles(this.excludeFiles);
props.setExcludeNodes(this.excludeNodes);
props.setExcludeProperties(this.excludeProperties);
props.setExcludeMixins(this.excludeMixins);
props.setMarkReplicationActivated(markReplicationActivated);
props.setMarkReplicationActivatedIncludeNodes(markReplicationActivatedIncludeNodes);
props.setDateLastReplicated(this.dateLastReplicated);
ContentUnpacker unpacker = new ContentUnpacker(props);
// 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() && !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).
* @throws MojoExecutionException Mojo execution exception
*/
private boolean deleteDirectoryWithRetries(File directory, int retryCount) throws MojoExecutionException {
if (retryCount > 100) {
return false;
}
if (FileUtils.deleteQuietly(directory)) {
return true;
}
else {
try {
Thread.sleep(10);
}
catch (InterruptedException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
return deleteDirectoryWithRetries(directory, retryCount + 1);
}
}
}