org.apache.jackrabbit.filevault.maven.packaging.GenerateMetadataMojo Maven / Gradle / Ivy
Show all versions of filevault-package-maven-plugin Show documentation
/*
* 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.
*/
package org.apache.jackrabbit.filevault.maven.packaging;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Processor;
import org.apache.jackrabbit.filevault.maven.packaging.impl.DependencyValidator;
import org.apache.jackrabbit.filevault.maven.packaging.impl.PackageDependency;
import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.PackageType;
import org.apache.jackrabbit.vault.util.Text;
import org.apache.maven.archiver.ManifestConfiguration;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
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.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.sonatype.plexus.build.incremental.BuildContext;
/**
* Maven goal which generates the metadata ending up in the package like {@code META-INF/MANIFEST.MF} as well as the
* files ending up in {@code META-INF/vault} like {@code filter.xml}, {@code properties.xml}, {@code config.xml} and
* {@code settings.xml}. Those files will be written to the directory given via parameter {@link #workDirectory}.
* In addition performs some validations.
*/
@Mojo(
name = "generate-metadata",
defaultPhase = LifecyclePhase.PROCESS_CLASSES,
requiresDependencyResolution = ResolutionScope.COMPILE
)
public class GenerateMetadataMojo extends AbstractPackageMojo {
/**
* A date format which is compliant with {@code org.apache.jackrabbit.util.ISO8601.parse(...)}
* @see Restricted profile for ISO8601
* @see JCR-4267
*/
private final DateFormat iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
public static final String MF_KEY_PACKAGE_TYPE = "Content-Package-Type";
public static final String MF_KEY_PACKAGE_ID = "Content-Package-Id";
public static final String MF_KEY_PACKAGE_DEPENDENCIES = "Content-Package-Dependencies";
public static final String MF_KEY_PACKAGE_ROOTS = "Content-Package-Roots";
public static final String MF_KEY_PACKAGE_DESC = "Content-Package-Description";
public static final String MF_KEY_IMPORT_PACKAGE = "Import-Package";
/**
* For m2e incremental build support
*/
@Component
private BuildContext buildContext;
/**
* For correct source of standard embedded path base name.
*/
@Component(hint = "default")
private ArtifactRepositoryLayout embedArtifactLayout;
/**
* The Maven session.
*/
@Parameter(property = "session", readonly = true, required = true)
private MavenSession session;
/**
* The groupId used for the generated content package. This will be part of
* the target installation path of the content package.
*/
@Parameter(
property = "vault.group",
defaultValue="${project.groupId}",
required = true)
private String group;
/**
* The name of the content package
*/
@Parameter(
property = "vault.name",
defaultValue="${project.artifactId}",
required = true)
private String name;
/**
* The version of the content package.
*/
@Parameter(
property = "vault.version",
defaultValue = "${project.version}",
required = true)
private String version;
/**
* Defines the content of the filter.xml file
*/
@Parameter
private final Filters filters = new Filters();
/**
* Optional file that specifies the source of the workspace filter. The filters specified in the configuration
* and injected via emebedds or subpackages are merged into it.
*/
@Parameter
private File filterSource;
/**
* Controls if empty workspace filter fails the build.
*/
@Parameter(
property = "vault.failOnEmptyFilter",
defaultValue="true",
required = true)
private boolean failOnEmptyFilter;
/**
* Specifies additional properties to be set in the properties.xml file.
* These properties cannot overwrite the following predefined properties:
*
*
* group Use group parameter to set
* name Use name parameter to set
* version Use version parameter to set
* groupId groupId of the Maven project descriptor
* artifactId artifactId of the Maven project descriptor
* dependencies Use dependencies parameter to set
* createdBy The value of the user.name system property
* created The current system time
* requiresRoot Use requiresRoot parameter to set
* allowIndexDefinitions Use allowIndexDefinitions parameter to set
* packagePath Automatically generated from the group and package name
* packageType Set via the package type parameter
* acHandling Use accessControlHandling parameter to set
*
*/
@Parameter
private final Properties properties = new Properties();
/**
* Defines the list of dependencies
* A dependency is declared as a {@code } element of a list
* style {@code } element:
*
* <dependency>
* <group>theGroup</group>
* <name>theName</name>
* <version>1.5</version>
* </dependency>
*
*
* The dependency can also reference a maven project dependency, this is preferred
* as it yields to more robust builds.
*
* <dependency>
* <groupId>theGroup</groupId>
* <artifactId>theName</artifactId>
* </dependency>
*
*
* The {@code versionRange} may be indicated as a single version, in which
* case the version range has no upper bound and defines the minimal version
* accepted. Otherwise, the version range defines a lower and upper bound of
* accepted versions, where the bounds are either included using parentheses
* {@code ()} or excluded using brackets {@code []}
*/
@Parameter
private Dependency[] dependencies = new Dependency[0];
/**
* Controls if errors during dependency validation should fail the build.
*/
@Parameter(
property = "vault.failOnDependencyErrors",
defaultValue="true",
required = true)
private boolean failOnDependencyErrors;
/**
* Defines the Access control handling. This will become the
* {@code acHandling} property of the properties.xml file.
* Possible values:
*
* - {@code ignore}: Ignores the packaged access control and leaves the target unchanged.
* - {@code overwrite}: Applies the access control provided with the package to the target. this also removes
* existing access control.
* - {@code merge}: Merge access control provided with the package with the one in the content by replacing the
* access control entries of corresponding principals (i.e. package first). It never alters access control entries of
* principals not present in the package.
* - {@code merge_preserve}: Merge access control in the content with the one provided with the package by
* adding the access control entries of principals not present in the content (i.e. content first). It never alters
* access control entries already existing in the content.
* - {@code clear}: Clears all access control on the target system.
*
*/
@Parameter(
property = "vault.acHandling",
alias = "acHandling",
required = false)
private AccessControlHandling accessControlHandling;
/**
* Defines whether the package requires root. This will become the
* {@code requiresRoot} property of the properties.xml file.
*/
@Parameter(
property = "vault.requiresRoot",
defaultValue="false",
required = true)
private boolean requiresRoot;
/**
* Defines additional bundle dependency via the osgi import-package entry in the manifest.
*/
@Parameter(
property = "vault.importPackage",
defaultValue =
// exclude HTL compiler packages as they are never real dependencies of the content
"-org.apache.sling.scripting.sightly.compiler.expression.nodes," +
"-org.apache.sling.scripting.sightly.java.compiler," +
"-org.apache.sling.scripting.sightly.render"
)
private String importPackage;
/**
* Defines the path under which the embedded bundles are placed. defaults to '/apps/bundles/install'
*/
@Parameter(property = "vault.embeddedTarget")
private String embeddedTarget;
/**
* List of filters for artifacts to embed in the package.
* The {@code Embedded} class represents one or multiple embedded artifact dependencies
* from the project descriptor.
* Each {@code } element may configure any of the following fields
*
*
* groupId String Filter criterion against the group id of a project dependency. A pattern as described below.
* artifactId String Filter criterion against the artifact id of a project dependency. A pattern as described below.
* scope ScopeArtifactFilter Filter criterion against the scope of a project dependency. Possible values are - {@code test}, which allows every scope
- {@code compile+runtime} which allows every scope except {@code test}
- {@code runtime+system} which allows every scope except {@code test} and {@code provided}
- {@code compile} which allows only scope {@code compile}, {@code provided} and {@code system}
- {@code runtime} which only allows scope {@code runtime} and {@code compile}.
* type String Filter criterion against the type of a project dependency. A pattern as described below.
* classifier String Filter criterion against the classifier of a project dependency. A pattern as described below.
* filter Boolean If set to {@code true} adds the embedded artifact location to the package's filter.
* target String The parent folder location in the package where to place the embedded artifact. Falls back to {@link #embeddedTarget} if not set.
*
*
* All fields are optional. All filter criteria is concatenated with AND logic (i.e. every criterion must match for a specific dependency to be embedded).
* * All filter patterns follow the format {@code <filter>{,<filter>}}. * Each {@code filter} is a string which is either an exclude (if it starts with a {@code ~}) or an include otherwise. If the first {@code filter} is an include the pattern acts as whitelist, * otherwise as blacklist. The last matching filter determines the outcome. Only matching dependencies are being considered for being embedded. *
* The difference between {@link #embeddeds} and {@link #subPackages} is that for the former an explicit target is given while for the latter the target is being computed from the artifact's vault property file. */ @Parameter private Embedded[] embeddeds = new Embedded[0]; /** * Defines whether to fail the build when an embedded artifact is not * found in the project's dependencies */ @Parameter(property = "vault.failOnMissingEmbed", defaultValue = "false", required = true) private boolean failOnMissingEmbed; /** * Defines the list of sub packages to be embedded in this package. * The {@code SubPackage} class represents one or multiple subpackage artifact dependencies * from the project descriptor. Each {@code
*
groupId | String | Filter criterion against the group id of a project dependency. A pattern as described below. |
artifactId | String | Filter criterion against the artifact ids of a project dependency. A pattern as described below. |
scope | ScopeArtifactFilter | Filter criterion against the scope of a project dependency. Possible values are
|
type | String | Filter criterion against the type of a project dependency.A pattern as described below. |
classifier | String | Filter criterion against the classifier of a project dependency. A pattern as described below. |
filter | Boolean | If set to {@code true} adds the embedded artifact location to the package's filter |
* All filter patterns follow the format {@code <filter>{,<filter>}}. * Each {@code filter} within a filter pattern is a string which is either an exclude (if it starts with a {@code ~}) or an include otherwise. If the first {@code filter} is an include the pattern acts as whitelist, * otherwise as blacklist. The last matching filter determines the outcome. Only matching dependencies are being considered for being embedded. *
* The difference between {@link #embeddeds} and {@link #subPackages} is that for the former an explicit target is given while for the latter the target is being computed from the artifact's vault property file. */ @Parameter private SubPackage[] subPackages = new SubPackage[0]; /** * Defines the packages that define the repository structure. * For the format description look at {@link #dependencies}. *
* The repository-init feature of sling-start can define initial content that will be available in the
* repository before the first package is installed. Packages that depend on those nodes have no way to reference
* any dependency package that provides these nodes. A "real" package that would creates those nodes cannot be
* installed in the repository, because it would void the repository init structure. On the other hand would filevault
* complain, if the package was listed as dependency but not installed in the repository. So therefor this
* repository-structure packages serve as indicator packages that helps satisfy the structural dependencies, but are
* not added as real dependencies to the package.
*/
@Parameter
private Dependency[] repositoryStructurePackages = new Dependency[0];
/**
* File to store the generated manifest snippet.
*/
@Parameter(property = "vault.generatedImportPackage", defaultValue = "${project.build.directory}/vault-generated-import.txt")
private File generatedImportPackage;
/**
* The archive configuration to use. See the
* documentation for Maven Archiver.
*
* All settings related to manifest are not relevant as this gets overwritten by the manifest in {@link AbstractPackageMojo#workDirectory}
*/
@Parameter
private MavenArchiveConfiguration archive;
/**
* Optional reference to PNG image that should be used as thumbnail for the content package.
*/
@Parameter
private File thumbnailImage;
/**
* Sets the access control handling.
* @param type the string representation of the ac handling
* @throws MojoFailureException if an error occurs
*/
public void setAccessControlHandling(String type) throws MojoFailureException {
try {
accessControlHandling = AccessControlHandling.valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
throw new MojoFailureException("Invalid accessControlHandling specified: " + type +".\n" +
"Must be empty or one of '" + StringUtils.join(AccessControlHandling.values(), "','") + "'.");
}
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (buildContext.isIncremental()) {
getLog().debug("Incremental build");
// only execute in case of changes towards the filter.xml as the generated one contains a merge
if (filterSource != null) {
if (buildContext.hasDelta(filterSource)) {
getLog().debug("Detecting a change on '" + filterSource + "' therefore not cancelling build");
} else {
getLog().debug("'" + filterSource + "' unchanged therefore cancelling build");
return;
}
} else {
getLog().debug("No file change would be relevant therefore cancelling build");
return;
}
}
final File vaultDir = getVaultDir();
vaultDir.mkdirs();
// JCRVLT-331 share work directory to expose vault metadata between process-classes and package phases for
// multi-module builds.
getArtifactWorkDirectoryLookup(getPluginContext())
.put(getModuleArtifactKey(project.getArtifact()), workDirectory);
try {
// calculate the embeddeds and subpackages
Map