All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.wcm.devops.conga.plugins.aem.maven.allpackage.AllPackageBuilder Maven / Gradle / Ivy

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2020 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.devops.conga.plugins.aem.maven.allpackage;

import static io.wcm.devops.conga.generator.util.FileUtil.getCanonicalPath;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.RUNMODE_AUTHOR;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.RUNMODE_PUBLISH;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.eliminateAuthorPublishDuplicates;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.isAuthorAndPublish;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.isOnlyAuthor;
import static io.wcm.devops.conga.plugins.aem.maven.allpackage.RunModeUtil.isOnlyPublish;
import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_DEPENDENCIES;
import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_NAME;
import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_PACKAGE_TYPE;
import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.vault.packaging.Dependency;
import org.apache.jackrabbit.vault.packaging.DependencyUtil;
import org.apache.jackrabbit.vault.packaging.PackageType;
import org.apache.jackrabbit.vault.packaging.VersionRange;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import io.wcm.devops.conga.plugins.aem.maven.AutoDependenciesMode;
import io.wcm.devops.conga.plugins.aem.maven.BuildOutputTimestamp;
import io.wcm.devops.conga.plugins.aem.maven.PackageTypeValidation;
import io.wcm.devops.conga.plugins.aem.maven.PackageVersionMode;
import io.wcm.devops.conga.plugins.aem.maven.RunModeOptimization;
import io.wcm.devops.conga.plugins.aem.maven.model.BundleFile;
import io.wcm.devops.conga.plugins.aem.maven.model.ContentPackageFile;
import io.wcm.devops.conga.plugins.aem.maven.model.InstallableFile;
import io.wcm.tooling.commons.contentpackagebuilder.ContentPackage;
import io.wcm.tooling.commons.contentpackagebuilder.ContentPackageBuilder;
import io.wcm.tooling.commons.contentpackagebuilder.PackageFilter;

/**
 * Builds "all" package based on given set of content packages.
 * 

* General concept: *

*
    *
  • Iterates through all content packages that are generated or collected by CONGA and contained in the * model.json
  • *
  • Enforces the order defined in CONGA by automatically adding dependencies to all packages reflecting the file * order in model.json
  • *
  • Because the dependency chain may be different for each runmode (author/publish), each package is added once for * each runmode. Internally this separate dependency change for author and publish is optimized to have each package * included only once for author+publish, unless it has a different chain of dependencies for both runmodes, in which * case it is included separately for each run mode.
  • *
  • To avoid conflicts with duplicate packages with different dependency chains the names of packages that are * included in different versions for author/publish are changed and a runmode suffix (.author or .publish) is added, * and it is put in a corresponding install folder.
  • *
  • To avoid problems with nested sub packages, the sub packages are extracted from the packages and treated in the * same way as other packages.
  • *
*/ public final class AllPackageBuilder { private final File targetFile; private final String groupName; private final String packageName; private String version; private AutoDependenciesMode autoDependenciesMode = AutoDependenciesMode.OFF; private RunModeOptimization runModeOptimization = RunModeOptimization.OFF; private PackageTypeValidation packageTypeValidation = PackageTypeValidation.STRICT; private PackageVersionMode packageVersionMode = PackageVersionMode.DEFAULT; private Log log; private BuildOutputTimestamp buildOutputTimestamp; private static final String RUNMODE_DEFAULT = "$default$"; private static final Set ALLOWED_PACKAGE_TYPES = Set.of( PackageType.APPLICATION.name().toLowerCase(), PackageType.CONTAINER.name().toLowerCase(), PackageType.CONTENT.name().toLowerCase()); private static final String VERSION_SUFFIX_SEPARATOR = "-"; private final List contentPackageFileSets = new ArrayList<>(); private final List bundleFileSets = new ArrayList<>(); /** * @param targetFile Target file * @param groupName Group name * @param packageName Package name */ public AllPackageBuilder(File targetFile, String groupName, String packageName) { this.targetFile = targetFile; this.groupName = groupName; this.packageName = packageName; } /** * @param value Automatically generate dependencies between content packages based on file order in CONGA * configuration. * @return this */ public AllPackageBuilder autoDependenciesMode(AutoDependenciesMode value) { this.autoDependenciesMode = value; return this; } /** * @param value Configure run mode optimization. * @return this */ public AllPackageBuilder runModeOptimization(RunModeOptimization value) { this.runModeOptimization = value; return this; } /** * @param value How to validate package types to be included in "all" package. * @return this */ public AllPackageBuilder packageTypeValidation(PackageTypeValidation value) { this.packageTypeValidation = value; return this; } /** * @param value How to handle versions of packages and sub-packages inside "all" package. * @return this */ public AllPackageBuilder packageVersionMode(PackageVersionMode value) { this.packageVersionMode = value; return this; } /** * @param value Maven logger * @return this */ public AllPackageBuilder logger(Log value) { this.log = value; return this; } /** * @param value Package version * @return this */ public AllPackageBuilder version(String value) { this.version = value; return this; } /** * @param value Build output timestamp * @return this */ public AllPackageBuilder buildOutputTimestamp(BuildOutputTimestamp value) { this.buildOutputTimestamp = value; return this; } private Log getLog() { if (this.log == null) { this.log = new SystemStreamLog(); } return this.log; } /** * Add content packages and OSGi bundles to be contained in "all" content package. * @param files Content packages (invalid will be filtered out) and OSGi bundles * @param cloudManagerTarget Target environments/run modes the packages should be attached to * @throws IllegalArgumentException If and invalid package type is detected */ public void add(List files, Set cloudManagerTarget) { List contentPackages = filterFiles(files, ContentPackageFile.class); // collect list of cloud manager environment run modes List environmentRunModes = new ArrayList<>(); if (cloudManagerTarget.isEmpty()) { environmentRunModes.add(RUNMODE_DEFAULT); } else { environmentRunModes.addAll(cloudManagerTarget); } List validContentPackages; switch (packageTypeValidation) { case STRICT: validContentPackages = getValidContentPackagesStrictValidation(contentPackages); break; case WARN: validContentPackages = getValidContentPackagesWarnValidation(contentPackages); break; default: throw new IllegalArgumentException("Unsupported package type validation: " + packageTypeValidation); } if (!validContentPackages.isEmpty()) { contentPackageFileSets.add(new ContentPackageFileSet(validContentPackages, environmentRunModes)); } // add OSGi bundles List bundles = filterFiles(files, BundleFile.class); if (!bundles.isEmpty()) { bundleFileSets.add(new BundleFileSet(bundles, environmentRunModes)); } } /** * Get valid content packages in strict mode: Ignore content packages without package type (with warning), * fail build if Content package with "mixed" mode is found. * @param contentPackages Content packages * @return Valid content packages */ private List getValidContentPackagesStrictValidation(List contentPackages) { // generate warning for each content packages without package type that is skipped contentPackages.stream() .filter(pkg -> !hasPackageType(pkg)) .forEach(pkg -> getLog().warn("Skipping content package without package type: " + getCanonicalPath(pkg.getFile()))); // fail build if content packages with non-allowed package types exist List invalidPackageTypeContentPackages = contentPackages.stream() .filter(AllPackageBuilder::hasPackageType) .filter(pkg -> !isValidPackageType(pkg)) .collect(Collectors.toList()); if (!invalidPackageTypeContentPackages.isEmpty()) { throw new IllegalArgumentException("Content packages found with unsupported package types: " + invalidPackageTypeContentPackages.stream() .map(pkg -> pkg.getName() + " -> " + pkg.getPackageType()) .collect(Collectors.joining(", "))); } // collect AEM content packages with package type return contentPackages.stream() .filter(AllPackageBuilder::hasPackageType) .collect(Collectors.toList()); } /** * Get all content packages, generate warnings if package type is missing or "mixed" mode package type is used. * @param contentPackages Content packages * @return Valid content packages */ private List getValidContentPackagesWarnValidation(List contentPackages) { // generate warning for each content packages without package type contentPackages.stream() .filter(pkg -> !hasPackageType(pkg)) .forEach(pkg -> getLog().warn("Found content package without package type: " + getCanonicalPath(pkg.getFile()))); // generate warning for each content packages with invalid package type contentPackages.stream() .filter(AllPackageBuilder::hasPackageType) .filter(pkg -> !isValidPackageType(pkg)) .forEach(pkg -> getLog().warn("Found content package with invalid package type: " + getCanonicalPath(pkg.getFile()) + " -> " + pkg.getPackageType())); // return all content packages return contentPackages.stream().collect(Collectors.toList()); } private static List filterFiles(List files, Class fileClass) { return files.stream() .filter(fileClass::isInstance) .map(fileClass::cast) .collect(Collectors.toList()); } /** * Build "all" content package. * @param properties Specifies additional properties to be set in the properties.xml file. * @return true if "all" package was generated, false if no valid package was found. * @throws IOException I/O exception */ public boolean build(Map properties) throws IOException { if (contentPackageFileSets.isEmpty()) { return false; } // prepare content package metadata ContentPackageBuilder builder = new ContentPackageBuilder() .group(groupName) .name(packageName) .packageType("container"); if (version != null) { builder.version(version); } // define root path for "all" package String rootPath = buildRootPath(groupName, packageName); builder.filter(new PackageFilter(rootPath)); // additional package properties if (properties != null) { properties.entrySet().forEach(entry -> builder.property(entry.getKey(), entry.getValue())); } // build content package try (ContentPackage contentPackage = builder.build(targetFile)) { buildAddContentPackages(contentPackage, rootPath); buildAddBundles(contentPackage, rootPath); } return true; } @SuppressWarnings("java:S3776") // ignore complexity private void buildAddContentPackages(ContentPackage contentPackage, String rootPath) throws IOException { // build set with dependencies instances for each package contained in all filesets Set allPackagesFromFileSets = new HashSet<>(); for (ContentPackageFileSet fileSet : contentPackageFileSets) { for (ContentPackageFile pkg : fileSet.getFiles()) { addDependencyInformation(allPackagesFromFileSets, pkg); } } Collection processedFileSets; if (runModeOptimization == RunModeOptimization.ELIMINATE_DUPLICATES) { // eliminate duplicates which are same for author and publish processedFileSets = eliminateAuthorPublishDuplicates(contentPackageFileSets, environmentRunMode -> new ContentPackageFileSet(new ArrayList<>(), Collections.singletonList(environmentRunMode))); } else { processedFileSets = contentPackageFileSets; } for (ContentPackageFileSet fileSet : processedFileSets) { for (String environmentRunMode : fileSet.getEnvironmentRunModes()) { List previousPackages = new ArrayList<>(); for (ContentPackageFile pkg : fileSet.getFiles()) { ContentPackageFile previousPkg = getDependencyChainPreviousPackage(pkg, previousPackages); // set package name, wire previous package in package dependency List processedFiles = processContentPackage(pkg, previousPkg, environmentRunMode, allPackagesFromFileSets); // add processed content packages to "all" content package - and delete the temporary files try { for (TemporaryContentPackageFile processedFile : processedFiles) { String path = buildPackagePath(processedFile, rootPath, environmentRunMode); contentPackage.addFile(path, processedFile.getFile()); if (log.isDebugEnabled()) { log.debug(" Add " + processedFile.getPackageInfoWithDependencies()); } } } finally { processedFiles.stream() .map(TemporaryContentPackageFile::getFile) .forEach(FileUtils::deleteQuietly); } previousPackages.add(pkg); } } } } /** * Gets the previous package in the order defined by CONGA to define as package dependency in current package. * @param currentPackage Current package * @param previousPackages List of previous packages * @return Package to define as dependency, or null if no dependency should be defined */ private @Nullable ContentPackageFile getDependencyChainPreviousPackage(@NotNull ContentPackageFile currentPackage, @NotNull List previousPackages) { if ((autoDependenciesMode == AutoDependenciesMode.OFF) || (autoDependenciesMode == AutoDependenciesMode.IMMUTABLE_ONLY && isMutable(currentPackage))) { return null; } // get last previous package return previousPackages.stream() // if not IMMUTABLE_MUTABLE_COMBINED active only that of the same mutability type .filter(item -> (autoDependenciesMode == AutoDependenciesMode.IMMUTABLE_MUTABLE_COMBINED) || mutableMatches(item, currentPackage)) // make sure author-only or publish-only packages are only taken into account if the current package has same restriction .filter(item -> isAuthorAndPublish(item) || (isOnlyAuthor(item) && isOnlyAuthor(currentPackage)) || (isOnlyPublish(item) && isOnlyPublish(currentPackage))) // get last in list .reduce((first, second) -> second).orElse(null); } private void buildAddBundles(ContentPackage contentPackage, String rootPath) throws IOException { Collection processedFileSets; if (runModeOptimization == RunModeOptimization.ELIMINATE_DUPLICATES) { // eliminate duplicates which are same for author and publish processedFileSets = eliminateAuthorPublishDuplicates(bundleFileSets, environmentRunMode -> new BundleFileSet(new ArrayList<>(), Collections.singletonList(environmentRunMode))); } else { processedFileSets = bundleFileSets; } for (BundleFileSet bundleFileSet : processedFileSets) { for (String environmentRunMode : bundleFileSet.getEnvironmentRunModes()) { for (BundleFile bundleFile : bundleFileSet.getFiles()) { String path = buildBundlePath(bundleFile, rootPath, environmentRunMode); contentPackage.addFile(path, bundleFile.getFile()); } } } } private static boolean hasPackageType(ContentPackageFile pkg) { // accept only content packages with a package type set return pkg.getPackageType() != null; } private static boolean isValidPackageType(ContentPackageFile pkg) { // check if the package type is an allowed package type return ALLOWED_PACKAGE_TYPES.contains(pkg.getPackageType()); } private static boolean isMutable(ContentPackageFile pkg) { return StringUtils.equals("content", pkg.getPackageType()); } private static boolean mutableMatches(ContentPackageFile pkg1, ContentPackageFile pkg2) { if (pkg1 == null || pkg2 == null) { return false; } return isMutable(pkg1) == isMutable(pkg2); } /** * Build root path to be used for embedded package. * @param groupName Group name * @param packageName Package name * @return Package path */ private static String buildRootPath(String groupName, String packageName) { return "/apps/" + groupName + "-" + packageName + "-packages"; } /** * Generate suffix for instance and environment run modes. * @param file File * @return Suffix string */ private static String buildRunModeSuffix(InstallableFile file, String environmentRunMode) { StringBuilder runModeSuffix = new StringBuilder(); if (isOnlyAuthor(file)) { runModeSuffix.append(".").append(RUNMODE_AUTHOR); } else if (isOnlyPublish(file)) { runModeSuffix.append(".").append(RUNMODE_PUBLISH); } if (!StringUtils.equals(environmentRunMode, RUNMODE_DEFAULT)) { runModeSuffix.append(".").append(environmentRunMode); } return runModeSuffix.toString(); } /** * Generate suffix for versions of content packages. * @param pkg Content package * @param ignoreSnapshot Do not build version suffix for SNAPSHOT versions * @return Suffix string */ private String buildVersionSuffix(ContentPackageFile pkg, boolean ignoreSnapshot) { StringBuilder versionSuffix = new StringBuilder(); if (this.packageVersionMode == PackageVersionMode.RELEASE_SUFFIX_VERSION && (!ArtifactUtils.isSnapshot(pkg.getVersion()) || !ignoreSnapshot) && !StringUtils.equals(pkg.getVersion(), this.version) && this.version != null) { versionSuffix.append(VERSION_SUFFIX_SEPARATOR) // replace dots with underlines in version suffix to avoid confusion with main version number .append(StringUtils.replace(this.version, ".", "_")); } return versionSuffix.toString(); } /** * Build path to be used for embedded package. * @param pkg Package * @param rootPath Root path * @return Package path */ @SuppressWarnings("java:S1075") // no filesystem path private String buildPackagePath(ContentPackageFile pkg, String rootPath, String environmentRunMode) { if (packageTypeValidation == PackageTypeValidation.STRICT && !isValidPackageType(pkg)) { throw new IllegalArgumentException("Package " + pkg.getPackageInfo() + " has invalid package type: '" + pkg.getPackageType() + "'."); } String runModeSuffix = buildRunModeSuffix(pkg, environmentRunMode); // add run mode suffix to both install folder path and package file name String path = rootPath + "/" + Objects.toString(pkg.getPackageType(), "misc") + "/install" + runModeSuffix; String versionSuffix = ""; String packageVersion = pkg.getVersion(); String packageVersionWithoutSuffix = packageVersion; if (this.packageVersionMode == PackageVersionMode.RELEASE_SUFFIX_VERSION && this.version != null) { packageVersionWithoutSuffix = StringUtils.removeEnd(packageVersion, buildVersionSuffix(pkg, false)); } if (packageVersion != null && pkg.getFile().getName().contains(packageVersionWithoutSuffix)) { versionSuffix = "-" + packageVersion; } String fileName = pkg.getName() + versionSuffix + "." + FilenameUtils.getExtension(pkg.getFile().getName()); return path + "/" + fileName; } /** * Build path to be used for embedded bundle. * @param bundleFile Bundle * @param rootPath Root path * @return Package path */ private static String buildBundlePath(BundleFile bundleFile, String rootPath, String environmentRunMode) { String runModeSuffix = buildRunModeSuffix(bundleFile, environmentRunMode); // add run mode suffix to both install folder path and package file name String path = rootPath + "/application/install" + runModeSuffix; return path + "/" + bundleFile.getFile().getName(); } /** * Rewrite content package ZIP file while adding to "all" package: * Add dependency to previous package in CONGA configuration file oder. * @param pkg Package to process (can be parent packe of the actual file) * @param previousPkg Previous package to get dependency information from. * Is null if no previous package exists or auto dependency mode is switched off. * @param environmentRunMode Environment run mode * @param allPackagesFromFileSets Set with all packages from all file sets as dependency instances * @return Returns a list of content package *temporary* files - have to be deleted when processing is completed. * @throws IOException I/O error */ @SuppressWarnings("java:S3776") // ignore complexity private List processContentPackage(ContentPackageFile pkg, ContentPackageFile previousPkg, String environmentRunMode, Set allPackagesFromFileSets) throws IOException { List result = new ArrayList<>(); List subPackages = new ArrayList<>(); // create temp zip file to create rewritten copy of package File tempFile = File.createTempFile(FilenameUtils.getBaseName(pkg.getFile().getName()), ".zip"); // open original content package try (ZipFile zipFileIn = new ZipFile(pkg.getFile())) { // iterate through entries and write them to the temp. zip file try (FileOutputStream fos = new FileOutputStream(tempFile); ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(fos)) { Enumeration zipInEntries = zipFileIn.getEntries(); while (zipInEntries.hasMoreElements()) { ZipArchiveEntry zipInEntry = zipInEntries.nextElement(); if (!zipInEntry.isDirectory()) { try (InputStream is = zipFileIn.getInputStream(zipInEntry)) { boolean processedEntry = false; // if entry is properties.xml, update dependency information if (StringUtils.equals(zipInEntry.getName(), "META-INF/vault/properties.xml")) { FileVaultProperties fileVaultProps = new FileVaultProperties(is); Properties props = fileVaultProps.getProperties(); addSuffixToPackageName(props, pkg, environmentRunMode); addSuffixToVersion(props, pkg); // update package dependencies ContentPackageFile dependencyFile = previousPkg; if (autoDependenciesMode == AutoDependenciesMode.OFF) { dependencyFile = null; } updateDependencies(pkg, props, dependencyFile, environmentRunMode, allPackagesFromFileSets); // if package type is missing in package properties, put in the type defined in model String packageType = pkg.getPackageType(); if (props.get(NAME_PACKAGE_TYPE) == null && packageType != null) { props.put(NAME_PACKAGE_TYPE, packageType); } ZipArchiveEntry zipOutEntry = newZipEntry(zipInEntry); zipOut.putArchiveEntry(zipOutEntry); fileVaultProps.storeToXml(zipOut); zipOut.closeArchiveEntry(); processedEntry = true; } // process sub-packages as well: add runmode suffix and update dependencies else if (StringUtils.equals(FilenameUtils.getExtension(zipInEntry.getName()), "zip")) { File tempSubPackageFile = File.createTempFile(FilenameUtils.getBaseName(zipInEntry.getName()), ".zip"); try (FileOutputStream subPackageFos = new FileOutputStream(tempSubPackageFile)) { IOUtils.copy(is, subPackageFos); } // check if contained ZIP file is really a content package // then process it as well, remove if from the content package is was contained in // and add it as "1st level package" to the all package TemporaryContentPackageFile tempSubPackage = new TemporaryContentPackageFile(tempSubPackageFile, pkg.getVariants()); if (packageTypeValidation == PackageTypeValidation.STRICT && !isValidPackageType(tempSubPackage)) { throw new IllegalArgumentException("Package " + pkg.getPackageInfo() + " contains sub package " + tempSubPackage.getPackageInfo() + " with invalid package type: '" + StringUtils.defaultString(tempSubPackage.getPackageType()) + "'"); } if (StringUtils.isNoneBlank(tempSubPackage.getGroup(), tempSubPackage.getName())) { subPackages.add(tempSubPackage); processedEntry = true; } else { FileUtils.deleteQuietly(tempSubPackageFile); } } // otherwise transfer the binary data 1:1 if (!processedEntry) { ZipArchiveEntry zipOutEntry = newZipEntry(zipInEntry); zipOut.putArchiveEntry(zipOutEntry); IOUtils.copy(is, zipOut); zipOut.closeArchiveEntry(); } } } } } // add sub package metadata to set with dependency information for (TemporaryContentPackageFile tempSubPackage : subPackages) { addDependencyInformation(allPackagesFromFileSets, tempSubPackage); } // process sub packages and add to result for (TemporaryContentPackageFile tempSubPackage : subPackages) { result.addAll(processContentPackage(tempSubPackage, previousPkg, environmentRunMode, allPackagesFromFileSets)); } result.add(new TemporaryContentPackageFile(tempFile, pkg.getVariants())); } return result; } private ZipArchiveEntry newZipEntry(ZipArchiveEntry in) { ZipArchiveEntry out = new ZipArchiveEntry(in.getName()); if (buildOutputTimestamp != null && buildOutputTimestamp.isValid()) { out.setLastModifiedTime(buildOutputTimestamp.toFileTime()); } else if (in.getLastModifiedTime() != null) { out.setLastModifiedTime(in.getLastModifiedTime()); } return out; } /** * Add dependency information to dependencies string in properties (if it does not exist already). * @param pkg Current content package * @param props Properties * @param dependencyFile Dependency package * @param allPackagesFromFileSets Set with all packages from all file sets as dependency instances */ private void updateDependencies(ContentPackageFile pkg, Properties props, ContentPackageFile dependencyFile, String environmentRunMode, Set allPackagesFromFileSets) { String[] existingDepsStrings = StringUtils.split(props.getProperty(NAME_DEPENDENCIES), ","); Dependency[] existingDeps = null; if (existingDepsStrings != null && existingDepsStrings.length > 0) { existingDeps = Dependency.fromString(existingDepsStrings); } if (existingDeps != null) { existingDeps = autoDependenciesMode == AutoDependenciesMode.OFF ? rewriteReferencesToManagedPackages(pkg, environmentRunMode, allPackagesFromFileSets, existingDeps) : removeReferencesToManagedPackages(existingDeps, allPackagesFromFileSets); } Dependency[] deps; if (dependencyFile != null) { Dependency newDependency = createDependencyFromContentPackageFile(dependencyFile, environmentRunMode); deps = addDependency(existingDeps, newDependency); } else { deps = existingDeps; } if (deps != null) { String dependenciesString = Dependency.toString(deps); props.put(NAME_DEPENDENCIES, dependenciesString); } } private @NotNull Dependency createDependencyFromContentPackageFile(@NotNull ContentPackageFile dependencyFile, @NotNull String environmentRunMode) { String runModeSuffix = buildRunModeSuffix(dependencyFile, environmentRunMode); String dependencyVersion = dependencyFile.getVersion() + buildVersionSuffix(dependencyFile, true); return new Dependency(dependencyFile.getGroup(), dependencyFile.getName() + runModeSuffix, VersionRange.fromString(dependencyVersion)); } private static Dependency[] addDependency(Dependency[] existingDeps, Dependency newDependency) { if (existingDeps != null) { return DependencyUtil.add(existingDeps, newDependency); } else { return new Dependency[] { newDependency }; } } private static void addSuffixToPackageName(Properties props, ContentPackageFile pkg, String environmentRunMode) { String runModeSuffix = buildRunModeSuffix(pkg, environmentRunMode); String packageName = props.getProperty(NAME_NAME) + runModeSuffix; props.put(NAME_NAME, packageName); } private void addSuffixToVersion(Properties props, ContentPackageFile pkg) { // package version if (StringUtils.isEmpty(pkg.getVersion())) { return; } String suffixedVersion = pkg.getVersion() + buildVersionSuffix(pkg, true); props.put(NAME_VERSION, suffixedVersion); } private @NotNull Dependency[] rewriteReferencesToManagedPackages(@NotNull ContentPackageFile pkg, @NotNull String environmentRunMode, @NotNull Set allPackagesFromFileSets, @NotNull Dependency[] deps) { return Arrays.stream(deps) .map(dep -> rewriteReferenceIfDependencyIsManagedPackage(pkg, environmentRunMode, allPackagesFromFileSets, dep)) .toArray(Dependency[]::new); } private @NotNull Dependency rewriteReferenceIfDependencyIsManagedPackage(@NotNull ContentPackageFile pkg, @NotNull String environmentRunMode, @NotNull Set allPackagesFromFileSets, @NotNull Dependency dep) { // not a managed package, return as is if (!allPackagesFromFileSets.contains(dep)) { return dep; } return findContentPackageFileForDependency(pkg, dep) // found a content package file for the dependency, rewrite the dependency .map(contentPackageFile -> createDependencyFromContentPackageFile(contentPackageFile, environmentRunMode)) // found no content package file for the dependency, use current run mode suffix .orElseGet(() -> createDependencyWithCurrentPackageRunModeSuffix(pkg, environmentRunMode, dep)); } private @NotNull Optional findContentPackageFileForDependency(@NotNull ContentPackageFile pkg, @NotNull Dependency dep) { // look for content package in all file sets return contentPackageFileSets.stream() // prefer file set which contains the current package to use current run mode .sorted((fileSet1, fileSet2) -> sortFileSetsContainingPackageFirst(pkg, fileSet1, fileSet2)) .flatMap(fileSet -> fileSet.getFiles().stream()) .filter(contentPackageFile -> isContentPackageForDependency(contentPackageFile, dep)) .findFirst(); } private int sortFileSetsContainingPackageFirst(@NotNull ContentPackageFile pkg, @NotNull ContentPackageFileSet fileSet1, @NotNull ContentPackageFileSet fileSet2) { int fileSet1ContainsPackage = fileSet1.getFiles().contains(pkg) ? 1 : 0; int fileSet2ContainsPackage = fileSet2.getFiles().contains(pkg) ? 1 : 0; return fileSet2ContainsPackage - fileSet1ContainsPackage; } private boolean isContentPackageForDependency(@NotNull ContentPackageFile contentPackageFile, @NotNull Dependency dep) { return contentPackageFile.getGroup().equals(dep.getGroup()) && contentPackageFile.getName().equals(dep.getName()); } private @NotNull Dependency createDependencyWithCurrentPackageRunModeSuffix(@NotNull ContentPackageFile pkg, @NotNull String environmentRunMode, @NotNull Dependency dep) { String runModeSuffix = buildRunModeSuffix(pkg, environmentRunMode); return new Dependency(dep.getGroup(), dep.getName() + runModeSuffix, dep.getRange()); } /** * Removes existing references to packages contained in the list of packages to manage by this builder because * they are added new (and probably with a different package name) during processing. * @param deps Dependencies list * @param allPackagesFromFileSets Set with all packages from all file sets as dependency instances * @return Dependencies list */ private static Dependency[] removeReferencesToManagedPackages(Dependency[] deps, Set allPackagesFromFileSets) { return Arrays.stream(deps) .filter(dep -> !allPackagesFromFileSets.contains(dep)) .toArray(size -> new Dependency[size]); } private static void addDependencyInformation(Set allPackagesFromFileSets, ContentPackageFile pkg) { allPackagesFromFileSets.add(new Dependency(pkg.getGroup(), pkg.getName(), VersionRange.fromString(pkg.getVersion()))); } public String getGroupName() { return this.groupName; } public String getPackageName() { return this.packageName; } public File getTargetFile() { return this.targetFile; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy