| /* |
| * 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.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URISyntaxException; |
| 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.LinkedList; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TimeZone; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.jar.Manifest; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| import org.apache.commons.io.FilenameUtils; |
| import org.apache.jackrabbit.vault.fs.api.PathFilterSet; |
| import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.ConfigurationException; |
| import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter; |
| import org.apache.jackrabbit.vault.fs.io.AccessControlHandling; |
| import org.apache.jackrabbit.vault.packaging.Dependency; |
| import org.apache.jackrabbit.vault.packaging.PackageId; |
| import org.apache.jackrabbit.vault.packaging.PackageProperties; |
| import org.apache.jackrabbit.vault.packaging.PackageType; |
| import org.apache.jackrabbit.vault.util.Constants; |
| 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; |
| |
| import aQute.bnd.header.Attrs; |
| import aQute.bnd.header.Parameters; |
| import aQute.bnd.osgi.Processor; |
| |
| /** |
| * 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, |
| threadSafe = true |
| ) |
| public class GenerateMetadataMojo extends AbstractMetadataPackageMojo { |
| |
| /** |
| * A date format which is compliant with {@code org.apache.jackrabbit.util.ISO8601.parse(...)} |
| * @see <a href="https://www.w3.org/TR/NOTE-datetime">Restricted profile for ISO8601</a> |
| * @see <a href="https://issues.apache.org/jira/browse/JCR-4267">JCR-4267</a> |
| */ |
| private final DateFormat iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); |
| |
| /** |
| * 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) |
| String group; |
| |
| /** |
| * The name of the content package |
| */ |
| @Parameter( |
| property = "vault.name", |
| defaultValue="${project.artifactId}", |
| required = true) |
| String name; |
| |
| /** |
| * The version of the content package. |
| */ |
| @Parameter( |
| property = "vault.version", |
| defaultValue = "${project.version}", |
| required = true) |
| String version; |
| |
| /** |
| * Defines the content of the filter.xml file. |
| * Each filter consists of the mandatory element {@code root} and the optional {@code mode} and {@code type} elements. All those elements are simple strings. |
| * In addition optionally a number of {@code include} and {@code exclude} elements are supported below {@code includes}/{@code excludes} respectively. |
| */ |
| @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. |
| * @deprecated This is no longer evaluated as every package is supposed to come with a non-empty filter |
| */ |
| @Deprecated |
| @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: |
| * <p> |
| * <table> |
| * <tr><td>group</td><td>Use <i>group</i> parameter to set</td></tr> |
| * <tr><td>name</td><td>Use <i>name</i> parameter to set</td></tr> |
| * <tr><td>version</td><td>Use <i>version</i> parameter to set</td></tr> |
| * <tr><td>groupId</td><td><i>groupId</i> of the Maven project descriptor</td></tr> |
| * <tr><td>artifactId</td><td><i>artifactId</i> of the Maven project descriptor</td></tr> |
| * <tr><td>dependencies</td><td>Use <i>dependencies</i> parameter to set</td></tr> |
| * <tr><td>createdBy</td><td>The value of the <i>user.name</i> system property</td></tr> |
| * <tr><td>created</td><td>The current system time</td></tr> |
| * <tr><td>requiresRoot</td><td>Use <i>requiresRoot</i> parameter to set</td></tr> |
| * <tr><td>allowIndexDefinitions</td><td>Use <i>allowIndexDefinitions</i> parameter to set</td></tr> |
| * <tr><td>packagePath</td><td>Automatically generated from the group and package name</td></tr> |
| * <tr><td>packageType</td><td>Set via the package type parameter</td></tr> |
| * <tr><td>acHandling</td><td>Use <i>accessControlHandling</i> parameter to set</td></tr> |
| * </table> |
| */ |
| @Parameter |
| private final Properties properties = new Properties(); |
| |
| /** |
| * Defines the list of dependencies |
| * A dependency is declared as a {@code <dependency>} element of a list |
| * style {@code <dependencies>} element: |
| * <pre> |
| * <dependency> |
| * <group>theGroup</group> |
| * <name>theName</name> |
| * <version>1.5</version> |
| * </dependency> |
| * </pre> |
| * <p> |
| * The dependency can also reference a maven project dependency, this is preferred |
| * as it yields to more robust builds. |
| * <pre> |
| * <dependency> |
| * <groupId>theGroup</groupId> |
| * <artifactId>theName</artifactId> |
| * </dependency> |
| * </pre> |
| * <p> |
| * 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(property = "vault.dependencies") |
| private Collection<MavenBasedPackageDependency> dependencies = new LinkedList<>(); |
| |
| |
| /** |
| * Defines the Access control handling. This will become the |
| * {@code acHandling} property of the properties.xml file.<br/> |
| * Possible values: |
| * <ul> |
| * <li>{@code ignore}: Ignores the packaged access control and leaves the target unchanged.</li> |
| * <li>{@code overwrite}: Applies the access control provided with the package to the target. this also removes |
| * existing access control.</li> |
| * <li>{@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.</li> |
| * <li>{@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.</li> |
| * <li>{@code clear}: Clears all access control on the target system.</li> |
| * </ul> |
| */ |
| @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 <embedded>} element may configure any of the following fields |
| * <p> |
| * <table> |
| * <tr><td>groupId</td><td>String</td><td>Filter criterion against the group id of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>artifactId</td><td>String</td><td>Filter criterion against the artifact id of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>scope</td><td>ScopeArtifactFilter</td><td>Filter criterion against the <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">scope of a project dependency</a>. Possible values are <ul><li>{@code test}, which allows every scope</li><li>{@code compile+runtime} which allows every scope except {@code test}</li><li>{@code runtime+system} which allows every scope except {@code test} and {@code provided}</li><li>{@code compile} which allows only scope {@code compile}, {@code provided} and {@code system}</li><li>{@code runtime} which only allows scope {@code runtime} and {@code compile}.</td></tr> |
| * <tr><td>type</td><td>String</td><td>Filter criterion against the type of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>classifier</td><td>String</td><td>Filter criterion against the classifier of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>filter</td><td>Boolean</td><td>If set to {@code true} adds the embedded artifact location to the package's filter.</td></tr> |
| * <tr><td>isAllVersionsFilter</td><td>Boolean</td><td>If {@code filter} is {@code true} and this is {@code true} as well, the filter entry will contain all versions of the same artifact (by creating an according filter pattern).</td></tr> |
| * <tr><td>excludeTransitive</td><td>Boolean</td><td>If {@code true} only filters on direct dependencies (not on transitive ones). Default = {@code false}.</td><tr> |
| * <tr><td>target</td><td>String</td><td>The parent folder location in the package where to place the embedded artifact. Falls back to {@link #embeddedTarget} if not set.</td></tr> |
| * </table> |
| * </pre> |
| * 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). |
| * <br> |
| * 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.</td></tr> |
| * <br> |
| * <i>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.</i> |
| */ |
| @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 <subPackage>} element may configure any of the following fields |
| * <p> |
| * <table> |
| * <tr><td>groupId</td><td>String</td><td>Filter criterion against the group id of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>artifactId</td><td>String</td><td>Filter criterion against the artifact ids of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>scope</td><td>ScopeArtifactFilter</td><td>Filter criterion against the <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">scope of a project dependency</a>. Possible values are <ul><li>{@code test}, which allows every scope</li><li>{@code compile+runtime} which allows every scope except {@code test}</li><li>{@code runtime+system} which allows every scope except {@code test} and {@code provided}</li><li>{@code compile} which allows only scope {@code compile}, {@code provided} and {@code system}</li><li>{@code runtime} which only allows scope {@code runtime} and {@code compile}.</td></tr> |
| * <tr><td>type</td><td>String</td><td>Filter criterion against the type of a project dependency.A pattern as described below.</td></tr> |
| * <tr><td>classifier</td><td>String</td><td>Filter criterion against the classifier of a project dependency. A pattern as described below.</td></tr> |
| * <tr><td>isAllVersionsFilter</td><td>Boolean</td><td>If {@code filter} is {@code true} and this is {@code true} as well, the filter entry will contain all versions of the same artifact (by creating an according filter pattern).</td></tr> |
| * <tr><td>excludeTransitive</td><td>Boolean</td><td>If {@code true} only filters on direct dependencies (not on transitive ones). Default = {@code false}.</td><tr> |
| * <tr><td>filter</td><td>Boolean</td><td>If set to {@code true} adds the embedded artifact location to the package's filter</td></tr> |
| * </table> |
| * </pre> |
| * 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 as a sub package). |
| * <br> |
| * 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. |
| * <br> |
| * <i>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.</i> |
| */ |
| @Parameter |
| private SubPackage[] subPackages = new SubPackage[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 <a |
| * href="http://maven.apache.org/shared/maven-archiver/index.html">the |
| * documentation for Maven Archiver</a>. |
| * |
| * All settings related to manifest are not relevant as this gets overwritten by the manifest in {@link AbstractMetadataPackageMojo#workDirectory} |
| */ |
| @Parameter |
| private MavenArchiveConfiguration archive; |
| |
| /** |
| * Optional reference to PNG image that should be used as thumbnail for the content package. |
| */ |
| @Parameter |
| private File thumbnailImage; |
| |
| |
| /** |
| * Defines the content package type. This is either 'application', 'content', 'container' or 'mixed'. |
| * If omitted, it is calculated automatically based on filter definitions. Certain package types imply restrictions, |
| * for example, 'application' and 'content' packages are not allowed to contain sub packages or embedded bundles.<br> |
| * Possible values: |
| * <ul> |
| * <li>{@code application}: An application package consists purely of application content. It serializes |
| * entire subtrees with no inclusion or exclusion filters. it does not contain any subpackages nor OSGi |
| * configuration or bundles.</li> |
| * <li>{@code content}: A content package consists only of content and user defined configuration. |
| * It usually serializes entire subtrees but can contain inclusion or exclusion filters. it does not contain |
| * any subpackages nor OSGi configuration or bundles.</li> |
| * <li>{@code container}: A container package only contains sub packages and OSGi configuration and bundles. |
| * The container package is only used as container for deployment.</li> |
| * <li>{@code mixed}: Catch all type for a combination of the above.</li> |
| * </ul> |
| */ |
| @Parameter(property = "vault.packageType") |
| protected PackageType packageType; |
| |
| |
| /** |
| * Defines whether the package is allowed to contain index definitions. This will become the |
| * {@code allowIndexDefinitions} property of the properties.xml file. |
| */ |
| @Parameter( |
| property = "vault.allowIndexDefinitions", |
| defaultValue="false", |
| required = true) |
| boolean allowIndexDefinitions; |
| |
| // take the first "-" followed by a digit as separator between version suffix and rest |
| private static final Pattern FILENAME_PATTERN_WITHOUT_VERSION_IN_GROUP1 = Pattern.compile("((?!-\\d).*-)\\d.*"); |
| |
| |
| public GenerateMetadataMojo() { |
| super(); |
| // always emit dates in UTC timezone |
| iso8601DateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); |
| } |
| |
| /** |
| * Sets the package type. |
| * @param type the string representation of the package type |
| * @throws MojoFailureException if an error occurs |
| */ |
| public void setPackageType(String type) throws MojoFailureException { |
| try { |
| packageType = PackageType.valueOf(type.toUpperCase()); |
| } catch (IllegalArgumentException e) { |
| throw new MojoFailureException("Invalid package type specified: " + type +".\n" + |
| "Must be empty or one of 'application', 'content', 'container', 'mixed'"); |
| } |
| } |
| |
| /** |
| * 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) { |
| // TODO: emit in lower case |
| 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; |
| } |
| } |
| |
| if (!failOnEmptyFilter) { |
| getLog().warn("The parameter 'failOnEmptyFilter' is no longer supported and ignored. Every package must have a non-empty filter!"); |
| } |
| final File vaultDir = getGeneratedVaultDir(true); |
| 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 { |
| // find the meta-inf source directory |
| File metaInfDirectory = getMetaInfVaultSourceDirectory(); |
| |
| // generate the filter.xml |
| String sourceFilters = computeFilters(metaInfDirectory); |
| computeImportPackage(); |
| |
| // this must happen before the filter rules are extended |
| // but after filters have been consolidated |
| if (packageType == null) { |
| packageType = computePackageType(); |
| } |
| |
| // calculate the embeddeds and subpackages |
| Map<String, File> embeddedFiles = getEmbeddeds(); |
| embeddedFiles.putAll(getSubPackages()); |
| setEmbeddedFilesMap(embeddedFiles); |
| |
| String dependenciesString = computeDependencies(); |
| String dependenciesLocations = computeDependenciesLocations(); |
| |
| // generate properties.xml |
| final Properties vaultProperties = computeProperties(dependenciesString, dependenciesLocations); |
| try (FileOutputStream fos = new FileOutputStream(new File(vaultDir, Constants.PROPERTIES_XML))) { |
| vaultProperties.storeToXML(fos, project.getName()); |
| } |
| writeFilters(sourceFilters); |
| copyFile("/vault/config.xml", new File(vaultDir, Constants.CONFIG_XML)); |
| copyFile("/vault/settings.xml", new File(vaultDir, Constants.SETTINGS_XML)); |
| |
| // add package thumbnail |
| if (thumbnailImage != null && thumbnailImage.exists()) { |
| File vaultDefinitionFolder = new File(vaultDir, "definition"); |
| if (!vaultDefinitionFolder.exists()) { |
| vaultDefinitionFolder.mkdir(); |
| } |
| copyFile("/vault/definition/.content.xml", new File(vaultDefinitionFolder, ".content.xml")); |
| FileUtils.copyFile(thumbnailImage, new File(vaultDefinitionFolder, "thumbnail.png")); |
| } |
| |
| writeManifest(getGeneratedManifestFile(true), dependenciesString, dependenciesLocations, vaultProperties); |
| } catch (IOException | ManifestException | DependencyResolutionRequiredException | ConfigurationException e) { |
| throw new MojoExecutionException(e.toString(), e); |
| } |
| buildContext.refresh(vaultDir); |
| } |
| |
| void writeManifest(File file, String dependenciesString, String dependenciesLocations, final Properties vaultProperties) |
| throws ManifestException, DependencyResolutionRequiredException, IOException, FileNotFoundException { |
| // generate manifest file |
| MavenArchiver mavenArchiver = new MavenArchiver(); |
| mavenArchiver.setCreatedBy("Apache Jackrabbit FileVault - Package Maven Plugin", "org.apache.jackrabbit", "filevault-package-maven-plugin"); |
| Manifest manifest = mavenArchiver.getManifest(session, project, getMavenArchiveConfiguration(vaultProperties, dependenciesString, dependenciesLocations)); |
| try (OutputStream out = new FileOutputStream(file)) { |
| manifest.write(out); |
| } |
| } |
| |
| /** |
| * Computes the package filters. |
| * |
| * Requirements: |
| * - backward compatibility: if a filter.xml is copied to vault-work with the resource plugin, then it should still "work" correctly. |
| * - if there are any comments in the original filter source, they should not get lost, if possible |
| * - if there are filters specified in the pom and in a filter source, they should get merged. |
| * - if the prefix property is set, it should be used if no filter is set. |
| * - if both, a inline filter and a implicit filter is present, the build fails. |
| * - re-run the package goal w/o cleaning the target first must work |
| * |
| *@return the source filter string (if there have been a filter given manually), otherwise {@code null} |
| * @throws IOException if an I/O error occurs |
| * @throws MojoExecutionException if the build fails |
| */ |
| private String computeFilters(File vaultMetaDir) throws IOException, MojoExecutionException { |
| // backward compatibility: if implicit filter exists, use it. but check for conflicts |
| File filterFile = getGeneratedFilterFile(true); |
| if (filterFile.exists() && filterFile.lastModified() != 0) { |
| // if both, a inline filter and a implicit filter is present, the build fails. |
| if (!filters.getFilterSets().isEmpty()) { |
| getLog().error("Refuse to merge inline filters and non-sourced filter.xml. If this is intended, specify the filter.xml via the 'filterSource' property."); |
| throw new MojoExecutionException("Conflicting filters, look at above log for details."); |
| } |
| // load filters for further processing |
| try { |
| filters.load(filterFile); |
| } catch (ConfigurationException e) { |
| throw new IOException("Error loading filter file '" + filterFile + "'", e); |
| } |
| |
| getLog().warn("The project is using a filter.xml provided via the resource plugin."); |
| getLog().warn("This is deprecated and might no longer be supported in future versions."); |
| getLog().warn("Use the 'filterSource' property to specify the filter or use inline filters."); |
| return null; |
| } |
| |
| // if last modified of vault-work/META-INF/vault/filter.xml == 0 -> delete it |
| if (filterFile.exists() && filterFile.lastModified() == 0) { |
| try { |
| Files.delete(filterFile.toPath()); |
| } catch (IOException e) { |
| getLog().error("Unable to delete previously generated filter.xml. re-run the goals with a clean setup."); |
| throw new MojoExecutionException("Unable to delete file.", e); |
| } |
| } |
| |
| // check for filters file in vaultDir |
| if (vaultMetaDir != null) { |
| File metaFilterFile = new File(vaultMetaDir, "filter.xml"); |
| if (metaFilterFile.exists()) { |
| if (filterSource != null && !filterSource.equals(metaFilterFile)) { |
| getLog().error("Project contains filter.xml in META-INF/vault but also specifies a filter source."); |
| throw new MojoExecutionException("Conflicting filters, look at above log for details."); |
| } |
| filterSource = metaFilterFile; |
| } |
| } |
| |
| // if filterSource exists, read the filters into sourceFilters |
| DefaultWorkspaceFilter sourceFilters = new DefaultWorkspaceFilter(); |
| if (filterSource != null && filterSource.exists()) { |
| getLog().info("Loading filter from " + filterSource.getPath()); |
| try { |
| sourceFilters.load(filterSource); |
| } catch (ConfigurationException e) { |
| throw new IOException(e); |
| } |
| if (!filters.getFilterSets().isEmpty()) { |
| getLog().info("Merging inline filters."); |
| mergeFilters(sourceFilters, filters); |
| } |
| |
| // now copy everything from sourceFilter to filters (as the latter is supposed to contain the final filter rules)! |
| // sourceFilters.resetSource(); |
| // there is no suitable clone nor constructor, therefore use a serialization/deserialization approach |
| try (InputStream serializedFilters = sourceFilters.getSource()) { |
| filters.load(serializedFilters); |
| } catch (ConfigurationException e) { |
| throw new IllegalStateException("cloning filters failed.", e); |
| } |
| |
| // reset source filters for later. this looks a bit complicated but is needed to keep the same |
| // filter order as in previous versions |
| sourceFilters = new DefaultWorkspaceFilter(); |
| try { |
| sourceFilters.load(filterSource); |
| } catch (ConfigurationException e) { |
| throw new IOException("Error loading filter file '" + filterSource + "'", e); |
| } |
| } |
| |
| // if the prefix property is set, it should be used if no filter is set |
| if (filters.getFilterSets().isEmpty() && prefix.length() > 0) { |
| filters.add(new PathFilterSet(prefix)); |
| } |
| |
| return sourceFilters.getSourceAsString(); |
| } |
| |
| private void mergeFilters(DefaultWorkspaceFilter dst, WorkspaceFilter src) { |
| for (PathFilterSet fs: src.getFilterSets()) { |
| // check for collision |
| for (PathFilterSet mfs: dst.getFilterSets()) { |
| if (mfs.getRoot().equals(fs.getRoot())) { |
| throw new IllegalArgumentException("Merging of equal filter roots not allowed for: " + fs.getRoot()); |
| } |
| } |
| dst.add(fs); |
| } |
| } |
| |
| private void writeFilters(String sourceFilters) throws IOException, MojoExecutionException { |
| // if no filter is defined at all, fail |
| if (filters.getFilterSets().isEmpty()) { |
| throw new MojoExecutionException("No workspace filter defined!"); |
| } |
| |
| File filterFile = getGeneratedFilterFile(true); |
| // if the source filters and the generated filters are the same, copy the source file to retain the comments |
| if (filterSource != null && filters.getSourceAsString().equals(sourceFilters)) { |
| FileUtils.copyFile(filterSource, filterFile); |
| } else { |
| // generate xml and write to filter.xml |
| getLog().info("Generating filter.xml from plugin configuration"); |
| FileUtils.fileWrite(filterFile.getAbsolutePath(), filters.getSourceAsString()); |
| } |
| |
| // update the last modified time of filter.xml to for generated filters |
| if (!filterFile.setLastModified(0)) { |
| getLog().warn("Unable to set last modified of filters file. make sure to clean the project before next run."); |
| } |
| } |
| |
| /** |
| * Computes the import-package definition from the given bundles if not provided by the project. |
| */ |
| private void computeImportPackage() throws IOException { |
| TreeMap<String, Attrs> importParams = new TreeMap<>(); |
| if (generatedImportPackage.exists()) { |
| String importPackageStr = FileUtils.fileRead(generatedImportPackage); |
| if (importPackageStr.length() > 0) { |
| importParams.putAll(new Parameters(importPackageStr)); |
| } |
| } |
| |
| // override computed patterns |
| if (importPackage != null) { |
| getLog().debug("merging analyzer-packages with:\n" + importPackage + "\n"); |
| for (Map.Entry<String, Attrs> entry : new Parameters(importPackage).entrySet()) { |
| boolean delete = false; |
| String pkg = entry.getKey(); |
| if ("-*".equals(pkg)) { |
| importParams.clear(); |
| continue; |
| } |
| if (pkg.charAt(0) == '-') { |
| pkg = pkg.substring(1); |
| delete = true; |
| } |
| if (pkg.endsWith("*")) { |
| String pkgDot = pkg.substring(0, pkg.length() - 1); |
| if (!pkgDot.endsWith(".")) { |
| // matches both, the packages and sub packages |
| pkg = pkgDot; |
| pkgDot = pkg + "."; |
| } |
| Iterator<Map.Entry<String, Attrs>> iter = importParams.entrySet().iterator(); |
| while (iter.hasNext()) { |
| Map.Entry<String, Attrs> e = iter.next(); |
| String pkgName = e.getKey(); |
| if (pkgName.equals(pkg) || pkgName.startsWith(pkgDot)) { |
| if (delete) { |
| iter.remove(); |
| } else { |
| e.setValue(entry.getValue()); |
| } |
| } |
| } |
| } else { |
| if (delete) { |
| importParams.remove(pkg); |
| } else { |
| importParams.put(pkg, entry.getValue()); |
| } |
| } |
| } |
| } |
| importPackage = Processor.printClauses(importParams); |
| |
| if (!importPackage.isEmpty()) { |
| getLog().info("Merged detected packages from analyzer with 'importPackage':"); |
| for (Map.Entry<String, Attrs> e: importParams.entrySet()) { |
| StringBuilder report = new StringBuilder(); |
| report.append(" ").append(e.getKey()); |
| try { |
| Processor.printClause(e.getValue(), report); |
| } catch (IOException e1) { |
| throw new IllegalStateException("Internal error while generating report", e1); |
| } |
| getLog().info(report); |
| } |
| getLog().info(""); |
| } |
| } |
| |
| /** |
| * Computes the dependency string. |
| * @return the dependency string |
| * @throws URISyntaxException |
| */ |
| private String computeDependencies() throws IOException { |
| String dependenciesString = null; |
| if (!dependencies.isEmpty()) { |
| MavenBasedPackageDependency.resolve(project, getLog(), dependencies); |
| Dependency[] vaultDependencies = dependencies.stream().map(MavenBasedPackageDependency::getPackageDependency).toArray(Dependency[]::new); |
| dependenciesString = Dependency.toString(vaultDependencies); |
| } |
| return dependenciesString; |
| } |
| |
| private String computeDependenciesLocations() throws IOException { |
| String dependenciesLocations = null; |
| if (!dependencies.isEmpty()) { |
| MavenBasedPackageDependency.resolve(project, getLog(), dependencies); |
| // pid = uri |
| dependenciesLocations = dependencies.stream().filter(a -> a.getInfo() != null).map(a -> a.getInfo().getId().toString() + "=" + a.getLocation()).collect(Collectors.joining(",")); |
| } |
| return dependenciesLocations; |
| } |
| |
| /** |
| * Escapes multiline manifest values to work around bug <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8222547">JDK-8222547</a> |
| * Java itself only adds leading SPACE in case a line is longer than 72 chars. |
| * |
| * If the value contains a newline, suffix it with an additional space (continuation character)! |
| * |
| * Unfortunately although the generated manifests for such escaped values are perfectly valid according to the spec, |
| * when reading those via {@link java.util.jar.Manifest} the new lines are stripped. |
| */ |
| static final String escapeManifestValue(String value) { |
| return value.replaceAll("\n", "\n ") // this covers CRLF and LF |
| .replaceAll("\r(?!\n)", "\r "); // only CR (not followed by LF) |
| } |
| |
| private MavenArchiveConfiguration getMavenArchiveConfiguration(Properties vaultProperties, String dependenciesString, String dependenciesLocations) throws IOException { |
| if (archive == null) { |
| archive = new MavenArchiveConfiguration(); |
| archive.setManifest(new ManifestConfiguration()); |
| |
| archive.setAddMavenDescriptor(true); |
| archive.setCompress(true); |
| archive.setIndex(false); |
| |
| archive.getManifest().setAddDefaultSpecificationEntries(false); |
| archive.getManifest().setAddDefaultImplementationEntries(true); |
| |
| // TODO: split up manifest generation |
| PackageId id = new PackageId(group, name, version); |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_TYPE, escapeManifestValue(packageType.name().toLowerCase())); |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_ID, escapeManifestValue(id.toString())); |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_DESC, escapeManifestValue(vaultProperties.getProperty("description", ""))); |
| if (dependenciesString != null && dependenciesString.length() > 0) { |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_DEPENDENCIES, escapeManifestValue(dependenciesString)); |
| if (dependenciesLocations != null && dependenciesLocations.length() > 0) { |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_DEPENDENCIES_LOCATIONS, escapeManifestValue(dependenciesLocations)); |
| } |
| } |
| |
| // be sure to avoid duplicates |
| Set<String> rts = new TreeSet<>(); |
| for (PathFilterSet p: filters.getFilterSets()) { |
| rts.add(p.getRoot()); |
| } |
| String[] roots = rts.toArray(new String[rts.size()]); |
| Arrays.sort(roots); |
| archive.addManifestEntry(PackageProperties.MF_KEY_PACKAGE_ROOTS, escapeManifestValue(StringUtils.join(roots, ","))); |
| |
| // import package is not yet there! |
| if (StringUtils.isNotEmpty(importPackage)) { |
| archive.addManifestEntry(PackageProperties.MF_KEY_IMPORT_PACKAGE, escapeManifestValue(StringUtils.deleteWhitespace(importPackage))); |
| } |
| } |
| |
| return archive; |
| } |
| |
| private Properties computeProperties(String dependenciesString, String dependenciesLocations) { |
| final Properties props = new Properties(); |
| |
| // find the description of the content package (bug #30546) |
| // this is allowed to be overwritten by the properties map (GRANITE-1527) |
| String description = project.getDescription(); |
| if (description == null) { |
| description = project.getName(); |
| if (description == null) { |
| description = project.getArtifactId(); |
| } |
| } |
| props.put(PackageProperties.NAME_DESCRIPTION, description); |
| |
| // add all user defined properties |
| // before the rest of the properties to prevent user |
| // overwriting of predefined properties |
| // (see JavaDoc of properties field for list) |
| |
| // but make sure, that we don't have null values in there |
| for (Object o : properties.keySet()) { |
| if (properties.get(o) == null) { |
| properties.put(o, ""); |
| } |
| } |
| |
| props.putAll(properties); |
| |
| // package descriptor properties |
| props.put(PackageProperties.NAME_GROUP, group); |
| props.put(PackageProperties.NAME_NAME, name); |
| props.put(PackageProperties.NAME_VERSION, version); |
| |
| // maven artifact identification |
| props.put("groupId", project.getGroupId()); |
| props.put("artifactId", project.getArtifactId()); |
| |
| // dependencies |
| if (dependenciesString != null && dependenciesString.length() > 0) { |
| props.put(PackageProperties.NAME_DEPENDENCIES, dependenciesString); |
| if (dependenciesLocations != null && dependenciesLocations.length() > 0) { |
| props.put(PackageProperties.NAME_DEPENDENCIES_LOCATIONS, dependenciesLocations); |
| } |
| } |
| |
| MavenArchiver archiver = new MavenArchiver(); |
| Date createdDate = archiver.parseOutputTimestamp(outputTimestamp); |
| if (createdDate == null) { |
| createdDate = new Date(); |
| } |
| props.put(PackageProperties.NAME_CREATED, iso8601DateFormat.format(createdDate)); |
| |
| // configurable properties |
| props.put(PackageProperties.NAME_REQUIRES_ROOT, String.valueOf(requiresRoot)); |
| props.put(PackageProperties.NAME_ALLOW_INDEX_DEFINITIONS, String.valueOf(allowIndexDefinitions)); |
| props.put(PackageProperties.NAME_PACKAGE_TYPE, packageType.name().toLowerCase()); |
| if (accessControlHandling != null) { |
| props.put(PackageProperties.NAME_AC_HANDLING, accessControlHandling.name().toLowerCase()); |
| } |
| return props; |
| } |
| |
| |
| private Map<String, File> getEmbeddeds() throws MojoFailureException, ConfigurationException { |
| Map<String, File> fileMap = new HashMap<>(); |
| for (Embedded emb : embeddeds) { |
| final Collection<Artifact> artifacts = emb.getMatchingArtifacts(project); |
| if (artifacts.isEmpty()) { |
| if (failOnMissingEmbed) { |
| throw new MojoFailureException("Embedded artifact specified " + emb + ", but no matching dependency artifact found. Add the missing dependency or fix the embed definition."); |
| } else { |
| getLog().warn("No matching artifacts for " + emb); |
| continue; |
| } |
| } |
| if (emb.getDestFileName() != null && artifacts.size() > 1) { |
| getLog().warn("destFileName defined but several artifacts match for " + emb); |
| } |
| |
| String targetPath = emb.getTarget(); |
| if (targetPath == null) { |
| targetPath = embeddedTarget; |
| if (targetPath == null) { |
| final String loc = (prefix.length() == 0) |
| ? "/apps/" |
| : prefix; |
| targetPath = loc + "bundles/install/"; |
| getLog().info("No target path set on " + emb + "; assuming default " + targetPath); |
| } |
| } |
| targetPath = makeAbsolutePath(targetPath); |
| |
| targetPath = Constants.ROOT_DIR + "/" + targetPath; |
| targetPath = FileUtils.normalize(targetPath); |
| if (!targetPath.endsWith("/")) { |
| targetPath += "/"; |
| } |
| |
| getLog().info("Embedding --- " + emb + " ---"); |
| for (final Artifact artifact : artifacts) { |
| final File source = artifact.getFile(); |
| String destFileName = emb.getDestFileName(); |
| |
| // todo: add support for patterns |
| if (destFileName == null) { |
| // If the <destFileName> param is not specified... |
| if (!source.isDirectory()) { |
| // If the artifact file is not a directory, defer to File.getName(). |
| destFileName = source.getName(); |
| } else { |
| // If the dependency file is a directory, the final artifact file has not yet been packaged. |
| // Construct a fallback file name from the artifact coordinates. |
| final String layoutBaseName = Text.getName(embedArtifactLayout.pathOf(artifact)); |
| // Look for a peer module in the session that the artifact is attached to. |
| final MavenProject peerModule = findModuleForArtifact(artifact); |
| if (peerModule != null) { |
| // determine the finalName of the artifact, which is ${artifactId}-${version} by default. |
| final Artifact attached = peerModule.getArtifact(); |
| final String defaultFinalName = attached.getArtifactId() + "-" + attached.getVersion(); |
| final String peerFinalName = peerModule.getBuild().getFinalName(); |
| if (peerFinalName != null) { |
| // remove the default finalName from the beginning of the layout basename, and |
| // prepend the specified finalName to create the destFileName. |
| destFileName = peerFinalName + layoutBaseName.substring(defaultFinalName.length()); |
| } |
| } |
| // If destFileName is still null, fallback to layoutBaseName. |
| if (destFileName == null) { |
| destFileName = layoutBaseName; |
| } |
| } |
| } |
| final String targetPathName = targetPath + destFileName; |
| final String targetNodePathName = targetPathName.substring(Constants.ROOT_DIR.length()); |
| |
| getLog().info(String.format("Embedding %s (from %s) -> %s", artifact.getId(), source.getAbsolutePath(), targetPathName)); |
| fileMap.put(targetPathName, source); |
| |
| if (emb.isFilter()) { |
| addEmbeddedFileToFilter(targetNodePathName, emb.isAllVersionsFilter()); |
| } |
| } |
| } |
| return fileMap; |
| } |
| |
| private Map<String, File> getSubPackages() throws MojoFailureException, ConfigurationException { |
| final String propsRelPath = Constants.META_DIR + "/" + Constants.PROPERTIES_XML; |
| Map<String, File> fileMap = new HashMap<>(); |
| for (SubPackage pack : subPackages) { |
| final Collection<Artifact> artifacts = pack.getMatchingArtifacts(project); |
| if (artifacts.isEmpty()) { |
| getLog().warn("No matching artifacts for sub package " + pack); |
| continue; |
| } |
| |
| // get the package path |
| getLog().info("Embedding subpackage --- " + pack + " ---"); |
| for (Artifact artifact : artifacts) { |
| final Properties props = new Properties(); |
| |
| final File source = artifact.getFile(); |
| if (source.isDirectory()) { |
| File otherWorkDirectory = null; |
| final MavenProject otherProject = findModuleForArtifact(artifact); |
| if (otherProject != null) { |
| final PluginDescriptor pluginDescriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor"); |
| if (pluginDescriptor != null) { |
| Map<String, Object> otherContext = this.session.getPluginContext(pluginDescriptor, otherProject); |
| otherWorkDirectory = getArtifactWorkDirectoryLookup(otherContext).get(getModuleArtifactKey(artifact)); |
| } |
| } |
| |
| // if not identifiable as a filevault content-package dependency, assume a generic archive layout. |
| if (otherWorkDirectory == null) { |
| otherWorkDirectory = source; // points to "target/classes" |
| } |
| |
| File propsXml = new File(otherWorkDirectory, propsRelPath); |
| if (!propsXml.exists()) { |
| // fallback to work dir (assuming the same folder name) |
| propsXml = new File(otherWorkDirectory.getParent(), workDirectory.getName() + "/" + propsRelPath); |
| } |
| try (InputStream input = new FileInputStream(propsXml)) { |
| props.loadFromXML(input); |
| } catch (IOException e) { |
| throw new MojoFailureException("Could not read META-INF/vault/properties.xml from directory '" + |
| otherWorkDirectory + "' to extract metadata: " + e.getMessage(), e); |
| } |
| } else { |
| // load properties |
| try (ZipFile zip = new ZipFile(source)) { |
| ZipEntry e = zip.getEntry(propsRelPath); |
| if (e == null) { |
| throw new IOException("Package does not contain 'META-INF/vault/properties.xml'"); |
| } |
| try (InputStream in = zip.getInputStream(e)) { |
| props.loadFromXML(in); |
| } |
| } catch (IOException e) { |
| throw new MojoFailureException("Could not open subpackage '" + source + "' to extract metadata: " + e.getMessage(), e); |
| } |
| } |
| PackageId pid = new PackageId( |
| props.getProperty(PackageProperties.NAME_GROUP), |
| props.getProperty(PackageProperties.NAME_NAME), |
| props.getProperty(PackageProperties.NAME_VERSION) |
| ); |
| final String targetNodePathName = pid.getInstallationPath() + ".zip"; |
| final String targetPathName = "jcr_root" + targetNodePathName; |
| |
| getLog().info(String.format("Embedding %s (from %s) -> %s", artifact.getId(), source.getAbsolutePath(), targetPathName)); |
| fileMap.put(targetPathName, source); |
| if (pack.isFilter()) { |
| addEmbeddedFileToFilter(targetNodePathName, pack.isAllVersionsFilter()); |
| } |
| } |
| } |
| return fileMap; |
| } |
| |
| /** |
| * Establishes a session-shareable workDirectory lookup map for the given pluginContext. |
| * |
| * @param pluginContext a Map retrieved from {@link MavenSession#getPluginContext(PluginDescriptor, MavenProject)}. |
| * @return a lookup Map. The key is {@link Artifact#getId()} and value is {@link AbstractMetadataPackageMojo#workDirectory}. |
| */ |
| @SuppressWarnings("unchecked") |
| static Map<String, File> getArtifactWorkDirectoryLookup(final Map pluginContext) { |
| final String workDirectoryLookupKey = "workDirectoryLookup"; |
| if (!pluginContext.containsKey(workDirectoryLookupKey)) { |
| pluginContext.put(workDirectoryLookupKey, new ConcurrentHashMap<String, File>()); |
| } |
| return (Map<String, File>) pluginContext.get(workDirectoryLookupKey); |
| } |
| |
| /** |
| * Find the other project which produces the provided artifact. |
| * |
| * @param artifact the dependency artifact needle |
| * @return another project that is a dependency of thisProject |
| */ |
| MavenProject findModuleForArtifact(final Artifact artifact) { |
| for (MavenProject otherProject : session.getProjects()) { |
| if (otherProject != this.project) { |
| final Artifact otherArtifact = otherProject.getArtifact(); |
| if (getModuleArtifactKey(artifact).equals(getModuleArtifactKey(otherArtifact))) { |
| return otherProject; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Construct a handler-independent artifact disambiguation key. This helps with the issue |
| * of matching dependency artifacts, which cannot reliably reference their original artifact handler to match the |
| * correct packaging type, to multimodule artifacts, which include the packaging type in their getId() result. |
| * |
| * I.E. {@link Artifact#getId()} contains either ":content-package:" or ":zip:" depending on whether it comes from |
| * this.session.getProjects() -> MavenProject#getArtifact() or from this.project.getArtifacts(). |
| * |
| * @param artifact the module artifact ({@link MavenProject#getArtifact()}) to identify |
| * @return a handler-independent artifact disambiguation key |
| */ |
| String getModuleArtifactKey(final Artifact artifact) { |
| return this.embedArtifactLayout.pathOf(artifact); |
| } |
| |
| private void addEmbeddedFileToFilter(String embeddedFile, boolean includeAllVersions) throws ConfigurationException { |
| filters.add(getPathFilterSetForEmbeddedFile(embeddedFile, includeAllVersions)); |
| } |
| |
| static PathFilterSet getPathFilterSetForEmbeddedFile(final String embeddedFile, boolean includeAllVersions) |
| throws ConfigurationException { |
| final PathFilterSet pathFilterSet; |
| if (includeAllVersions) { |
| String filename = FilenameUtils.getName(embeddedFile); |
| // shorten the filter root by one level |
| String rootName = StringUtils.chomp(embeddedFile, "/"); |
| String extension = FilenameUtils.getExtension(filename); |
| |
| // now find a mattern which should apply to embeddedFile and all similar artifacts with different versions from format: |
| // ${artifactId}-${version}) |
| Matcher matcher = FILENAME_PATTERN_WITHOUT_VERSION_IN_GROUP1.matcher(filename); |
| if (!matcher.matches()) { |
| throw new IllegalArgumentException("Could not figure out version part in filename '" + filename |
| + "'. For this artifact you cannot use 'isAllVersionsFilter=true'"); |
| } |
| |
| // create new pattern which matches the same artifacts in all versions |
| String pattern = Pattern.quote(rootName + "/" + matcher.group(1)) + ".*" + "\\." + extension + "(/.*)?"; |
| if (!embeddedFile.matches(pattern)) { |
| throw new IllegalArgumentException("Detected pattern '" + pattern + "' does not even match given filename '" + embeddedFile |
| + "'. For this artifact you cannot use 'isAllVersionsFilter=true'"); |
| } |
| pathFilterSet = new PathFilterSet(rootName); |
| pathFilterSet.addInclude(new DefaultPathFilter(pattern)); |
| } else { |
| pathFilterSet = new PathFilterSet(embeddedFile); |
| } |
| return pathFilterSet; |
| } |
| |
| private String makeAbsolutePath(final String relPath) { |
| final String absPath; |
| if (!relPath.startsWith("/")) { |
| absPath = ((prefix.length() == 0) ? "/" : prefix) + relPath; |
| getLog().info("Relative path resolved to " + absPath); |
| } else { |
| absPath = relPath; |
| } |
| |
| return absPath; |
| } |
| |
| private PackageType computePackageType() { |
| final PackageType packageType; |
| // auto detect... |
| boolean hasApps = false; |
| boolean hasOther = false; |
| for (PathFilterSet p : filters.getFilterSets()) { |
| if (PathFilterSet.TYPE_CLEANUP.equals(p.getType())) { |
| continue; |
| } |
| String root = p.getRoot(); |
| if ("/apps".equals(root) || root.startsWith("/apps/") || "/libs".equals(root) || root.startsWith("/libs/")) { |
| hasApps = true; |
| getLog().debug("Detected /apps or /libs filter entry: " + p); |
| } else { |
| hasOther = true; |
| getLog().debug("Detected filter entry outside /apps and /libs: " + p); |
| } |
| } |
| // no embeds and subpackages? |
| getLog().debug("Detected " + embeddeds.length + " bundle(s) and " + subPackages.length + " sub package(s)."); |
| if (embeddeds.length == 0 && subPackages.length == 0) { |
| if (hasApps && !hasOther) { |
| packageType = PackageType.APPLICATION; |
| } else if (hasOther && !hasApps) { |
| packageType = PackageType.CONTENT; |
| } else { |
| packageType = PackageType.MIXED; |
| } |
| } else { |
| if (!hasApps && !hasOther) { |
| packageType = PackageType.CONTAINER; |
| } else { |
| packageType = PackageType.MIXED; |
| } |
| } |
| getLog().info("Auto-detected package type: " + packageType.toString().toLowerCase()); |
| return packageType; |
| } |
| |
| /** |
| * Copies from the class loader resource given by {@link source} into the file given in {@link target} in case |
| * the target file does not exist yet. |
| * @param source the name of the class loader resource |
| * @param target the target file to copy to |
| * @throws IOException |
| */ |
| private void copyFile(String source, File target) throws IOException { |
| // nothing to do if the file exists |
| if (target.exists()) { |
| return; |
| } |
| |
| target.getParentFile().mkdirs(); |
| |
| try (InputStream ins = getClass().getResourceAsStream(source); |
| OutputStream out = new FileOutputStream(target)) { |
| if (ins != null) { |
| IOUtil.copy(ins, out); |
| } else { |
| throw new IllegalArgumentException("Could not find resource " + source); |
| } |
| } |
| } |
| } |