RunModeUtil.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 java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import io.wcm.devops.conga.plugins.aem.maven.model.InstallableFile;

final class RunModeUtil {

  static final String RUNMODE_AUTHOR = "author";
  static final String RUNMODE_PUBLISH = "publish";

  private RunModeUtil() {
    // static methods only
  }

  /**
   * Checks if the given package is to be installed on both author and publish instances
   * @param file Content package
   * @return true if author and publish run mode (or no run mode = no restriction)
   */
  public static boolean isAuthorAndPublish(InstallableFile file) {
    Set<String> runModes = mapVariantsToRunModes(file.getVariants());
    return (!runModes.contains(RUNMODE_AUTHOR) && !runModes.contains(RUNMODE_PUBLISH))
        || (runModes.contains(RUNMODE_AUTHOR) && runModes.contains(RUNMODE_PUBLISH));
  }

  /**
   * Checks if the given variants map to author run mode, but not to publish run mode.
   * @param file Content package
   * @return true if only author run modes
   */
  public static boolean isOnlyAuthor(InstallableFile file) {
    Set<String> runModes = mapVariantsToRunModes(file.getVariants());
    return runModes.contains(RUNMODE_AUTHOR) && !runModes.contains(RUNMODE_PUBLISH);
  }

  /**
   * Checks if the given variants map to publish run mode, but not to author run mode.
   * @param file Content package
   * @return true if only publish run modes
   */
  public static boolean isOnlyPublish(InstallableFile file) {
    Set<String> runModes = mapVariantsToRunModes(file.getVariants());
    return runModes.contains(RUNMODE_PUBLISH) && !runModes.contains(RUNMODE_AUTHOR);
  }

  private static Set<String> mapVariantsToRunModes(Collection<String> variants) {
    return variants.stream()
        .map(RunModeUtil::mapVariantToRunMode)
        .collect(Collectors.toSet());
  }

  /**
   * Maps well-known variant names from CONGA AEM definitions to the corresponding run modes.
   * If the variant name is not well-known the variant name is used as run mode.
   * @param variant Variant
   * @return Run mode
   */
  private static String mapVariantToRunMode(String variant) {
    if ("aem-author".equals(variant)) {
      return RUNMODE_AUTHOR;
    }
    else if ("aem-publish".equals(variant)) {
      return RUNMODE_PUBLISH;
    }
    return variant;
  }

  /**
   * Builds an optimized list of file sets separated for each environment run mode, but combined for author and publish
   * variant files. Those files are reduced to single items if they are present in both author and publish variants. The
   * order of the resulting file sets if driven by the first file set(s) in the list, additional files from other file
   * sets are added at the end of the result list(s).
   * @param fileSets Existing list of filesets
   * @param fileSetFactory Creates a new (empty) file set for given environment run mode
   * @return Optimized list of file sets (one per environment run mode)
   */
  public static <T extends InstallableFile, S extends FileSet<T>> Collection<S> eliminateAuthorPublishDuplicates(
      List<S> fileSets, Function<String, S> fileSetFactory) {
    Map<String, S> result = new LinkedHashMap<>();
    fileSets.forEach(fileSet -> fileSet.getEnvironmentRunModes().forEach(environmentRunMode -> {
        FileSet<T> resultFileSet = result.computeIfAbsent(environmentRunMode, fileSetFactory);
        fileSet.getFiles().forEach(file -> {
          Optional<T> existingFile = resultFileSet.getFiles().stream()
              .filter(item -> isSameFileNameHash(item, file))
              .findFirst();
          if (existingFile.isPresent()) {
            // if file was already added from other file set: eliminate duplicate, but add run modes
            existingFile.get().getVariants().addAll(file.getVariants());
          }
          else {
            resultFileSet.getFiles().add(file);
          }
        });
    }));
    // eliminate author+publish run modes if both are set on same file
    result.values().forEach(
        fileSet -> fileSet.getFiles().forEach(file -> removeAuthorPublishRunModeIfBothPresent(file.getVariants())));
    return result.values();
  }

  private static boolean isSameFileNameHash(InstallableFile file1, InstallableFile file2) {
    if (!StringUtils.equals(file1.getFile().getName(), file2.getFile().getName())) {
      return false;
    }
    return file1.getHashCode().equals(file2.getHashCode());
  }

  /**
   * Removes author and publish runmodes from given set if both are present.
   * @param runModes Run modes
   */
  private static void removeAuthorPublishRunModeIfBothPresent(Set<String> runModes) {
    if (runModes.contains(RUNMODE_AUTHOR) && runModes.contains(RUNMODE_PUBLISH)) {
      runModes.remove(RUNMODE_AUTHOR);
      runModes.remove(RUNMODE_PUBLISH);
    }
  }

}