AllPackageBuilder.java
- /*
- * #%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.
- * <p>
- * General concept:
- * </p>
- * <ul>
- * <li>Iterates through all content packages that are generated or collected by CONGA and contained in the
- * model.json</li>
- * <li>Enforces the order defined in CONGA by automatically adding dependencies to all packages reflecting the file
- * order in model.json</li>
- * <li>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.</li>
- * <li>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.</li>
- * <li>To avoid problems with nested sub packages, the sub packages are extracted from the packages and treated in the
- * same way as other packages.</li>
- * </ul>
- */
- 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<String> 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<ContentPackageFileSet> contentPackageFileSets = new ArrayList<>();
- private final List<BundleFileSet> 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<InstallableFile> files, Set<String> cloudManagerTarget) {
- List<ContentPackageFile> contentPackages = filterFiles(files, ContentPackageFile.class);
- // collect list of cloud manager environment run modes
- List<String> environmentRunModes = new ArrayList<>();
- if (cloudManagerTarget.isEmpty()) {
- environmentRunModes.add(RUNMODE_DEFAULT);
- }
- else {
- environmentRunModes.addAll(cloudManagerTarget);
- }
- List<ContentPackageFile> 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<BundleFile> 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<ContentPackageFile> getValidContentPackagesStrictValidation(List<? extends ContentPackageFile> 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<ContentPackageFile> 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<ContentPackageFile> getValidContentPackagesWarnValidation(List<? extends ContentPackageFile> 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 <T> List<T> filterFiles(List<? extends InstallableFile> files, Class<T> 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<String, String> 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<Dependency> allPackagesFromFileSets = new HashSet<>();
- for (ContentPackageFileSet fileSet : contentPackageFileSets) {
- for (ContentPackageFile pkg : fileSet.getFiles()) {
- addDependencyInformation(allPackagesFromFileSets, pkg);
- }
- }
- Collection<ContentPackageFileSet> 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<ContentPackageFile> previousPackages = new ArrayList<>();
- for (ContentPackageFile pkg : fileSet.getFiles()) {
- ContentPackageFile previousPkg = getDependencyChainPreviousPackage(pkg, previousPackages);
- // set package name, wire previous package in package dependency
- List<TemporaryContentPackageFile> 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<ContentPackageFile> 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<BundleFileSet> 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<TemporaryContentPackageFile> processContentPackage(ContentPackageFile pkg,
- ContentPackageFile previousPkg, String environmentRunMode,
- Set<Dependency> allPackagesFromFileSets) throws IOException {
- List<TemporaryContentPackageFile> result = new ArrayList<>();
- List<TemporaryContentPackageFile> 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.Builder().setFile(pkg.getFile()).get()) {
- // iterate through entries and write them to the temp. zip file
- try (FileOutputStream fos = new FileOutputStream(tempFile);
- ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(fos)) {
- Enumeration<? extends ZipArchiveEntry> 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<Dependency> 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<Dependency> 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<Dependency> 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<ContentPackageFile> 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<Dependency> allPackagesFromFileSets) {
- return Arrays.stream(deps)
- .filter(dep -> !allPackagesFromFileSets.contains(dep))
- .toArray(size -> new Dependency[size]);
- }
- private static void addDependencyInformation(Set<Dependency> 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;
- }
- }