QuarkusMigrationAdapterTransformer.java

package io.vanillabp.integration.deployment.config;

import static io.vanillabp.integration.deployment.VanillaBpIntegrationProcessor.PREFIX_ADAPTER_PACKAGE;
import static io.vanillabp.integration.deployment.config.QuarkusMigrationAdapterProperties.PREFIX;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import io.quarkus.deployment.Capabilities;
import io.vanillabp.integration.config.MigrationAdapterProperties;
import io.vanillabp.integration.config.WorkflowModuleAdapterProperties;
import io.vanillabp.integration.deployment.VanillaBpIntegrationProcessor;
import io.vanillabp.integration.deployment.processservice.VanillaBpMigratableProcessServiceBuildItem;
import lombok.Builder;

/**
 * Turns {@link QuarkusMigrationAdapterTransformer} into
 * {@link MigrationAdapterProperties}. Validates values of properties
 * which are specific to Quarkus. Further validation is done by
 * {@link MigrationAdapterProperties#validateProperties(List, List)}
 * or {@link MigrationAdapterProperties#validatePropertiesFor(List, String, String)}.
 */
@Builder
public class QuarkusMigrationAdapterTransformer {

  private final QuarkusMigrationAdapterProperties properties;

  private final Capabilities capabilities;

  /**
   * Transforms {@link QuarkusMigrationAdapterTransformer} into
   * {@link MigrationAdapterProperties}.
   *
   * @param processServiceBuildItems Build items provided by other VanillaBP adapter extensions
   * @return The {@link MigrationAdapterProperties}
   * @throws IllegalStateException If validation fails
   */
  public MigrationAdapterProperties getAndValidatePropertiesConfigured(
      final List<VanillaBpMigratableProcessServiceBuildItem> processServiceBuildItems) throws IllegalStateException {

    final var result = new MigrationAdapterProperties();

    final var adaptersConfigured = getAndValidateAdaptersConfigured();
    result.setAdapters(adaptersConfigured);

    final var prioritizedAdaptersConfigured = getAndValidatePrioritizedAdaptersConfigured(
        adaptersConfigured);
    result.setPrioritizedAdapters(prioritizedAdaptersConfigured);

    final var workflowModulesConfigured = getAndValidateWorkflowModulesConfigured();
    result.setWorkflowModules(workflowModulesConfigured);

    final var buildItemsNotConfigured = processServiceBuildItems
        .stream()
        .map(VanillaBpMigratableProcessServiceBuildItem::getAdapterName)
        .filter(Predicate.not(adaptersConfigured::containsValue))
        .collect(Collectors.joining("\n  "));
    if (!buildItemsNotConfigured.isEmpty()) {
      throw new IllegalStateException(
          """
              Found VanillaBpMigratableProcessServiceBuildItem mapping to adapters not found in properties '%s.adapters.*.type':
                %s
              This happens only in case the adapter extensions VanillaBpMigratableProcessServiceBuildItem
              adapter name does not match the suffix of its capability prefixed by '%s'!"""
              .formatted(PREFIX, buildItemsNotConfigured, PREFIX_ADAPTER_PACKAGE));
    }

    final var buildItemsAdapters = processServiceBuildItems
        .stream()
        .map(VanillaBpMigratableProcessServiceBuildItem::getAdapterName)
        .toList();
    final var missingBuildItems = adaptersConfigured
        .values()
        .stream()
        .filter(type -> !buildItemsAdapters.contains(type))
        .collect(Collectors.joining("\n  "));
    if (!missingBuildItems.isEmpty()) {
      throw new IllegalStateException(
          """
              Illegal VanillaBP adapter extensions:
                %s
              Missing VanillaBpMigratableProcessServiceBuildItem!"""
              .formatted(missingBuildItems));
    }
    final var buildItemsWithoutCapability = buildItemsAdapters
        .stream()
        .filter(adapter -> !adaptersConfigured.containsValue(adapter))
        .collect(Collectors.joining("\n  "));
    if (!buildItemsWithoutCapability.isEmpty()) {
      throw new IllegalStateException(
          """
              Illegal VanillaBP adapter extensions:
                '%s'
              Not matching their extension capabilities!"""
              .formatted(missingBuildItems));
    }

    return result;

  }

  private Map<String, WorkflowModuleAdapterProperties> getAndValidateWorkflowModulesConfigured() {

    return Map.of();

  }

  private Map<String, String> getAndValidateAdaptersConfigured() {

    // determine adapters by examining capabilities of Quarkus extensions available:
    final var adapterPackagesProvidedByOtherExtensions = capabilities
        .getCapabilities()
        .stream()
        .filter(capability -> capability.startsWith(
            VanillaBpIntegrationProcessor.PREFIX_ADAPTER_PACKAGE))
        .toList();
    final var adapterNamesProvidedByOtherExtensions = adapterPackagesProvidedByOtherExtensions
        .stream()
        .map(pkg -> pkg.substring(
            VanillaBpIntegrationProcessor.PREFIX_ADAPTER_PACKAGE.length()))
        .toList();
    if (adapterPackagesProvidedByOtherExtensions.isEmpty()) {
      throw new IllegalStateException(
          "No extensions found with capabilities '%s*'! Add Quarkus extensions providing VanillaBP adapters."
              .formatted(VanillaBpIntegrationProcessor.PREFIX_ADAPTER_PACKAGE));
    }

    // build result map (key = adapter name, value = adapter type)
    final var result = properties
        .adapters()
        .entrySet()
        .stream()
        .map(config -> Map.entry(config.getKey(), config.getValue().type().orElse(config.getKey())))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    if (result.isEmpty()) {
      final var missingConfigSections = adapterNamesProvidedByOtherExtensions
          .stream()
          .map(adapter -> "%s.adapters.xxxx.type=%s".formatted(PREFIX, adapter))
          .collect(Collectors.joining("\n  "));
      throw new IllegalStateException(
          """
              No adapters configured! Add properties sections for your BPMS (e.g. xxx) having type set to adapters found in classpath:
                %s"""
              .formatted(missingConfigSections));
    }

    // check for unknown adapters
    final var unknownAdapters = result
        .entrySet()
        .stream()
        .filter(adapter -> !adapterNamesProvidedByOtherExtensions.contains(adapter.getValue()))
        .map(adapter -> "'%s' found in '%s.adapters.%s.type'".formatted(adapter.getValue(), PREFIX, adapter.getKey()))
        .collect(Collectors.joining("\n  "));
    if (!unknownAdapters.isEmpty()) {
      throw new IllegalStateException(
          """
              Properties '%s.adapters.*.type' must contain VanillaBP adapters added as Quarkus extension!
              These adapters are unknown:
                %s
              Available adapter types provided by Quarkus extensions currently loaded: %s."""
              .formatted(PREFIX, unknownAdapters,
                  String.join(", ", adapterNamesProvidedByOtherExtensions)));
    }

    final var unconfiguredAdapters = adapterNamesProvidedByOtherExtensions
        .stream()
        .filter(adapter -> !result.containsValue(adapter))
        .collect(Collectors.joining(", "));
    if (!unconfiguredAdapters.isEmpty()) {
      throw new IllegalStateException(
          """
              No '%s.adapters.*' properties sections having types provided by Quarkus extension!
              Add section section if intended or remove extensions for these types: %s."""
              .formatted(PREFIX, unconfiguredAdapters));
    }

    return result;

  }

  private List<String> getAndValidatePrioritizedAdaptersConfigured(
      final Map<String, String> adapters) {

    // It is OK to skip property vanillabp.prioritized-adapters in case
    // only one adapter is configured:
    if (properties.prioritizedAdapters().isEmpty() && (adapters.size() == 1)) {
      return adapters.keySet().stream().toList();
    }

    // if more than one adapter is configured then the
    // property vanillabp.prioritized-adapters has to list each adapter
    // configured:
    if (properties.prioritizedAdapters()
        .isEmpty() || (adapters.size() != properties.prioritizedAdapters().get().size())) {
      throw new IllegalStateException(
          """
              The property '%s.prioritized-adapters' must list all the adapters configured in '%s.adapters.*' to define
              the order in which adapters are addressed to find workflows running.
              Configured adapters are: %s."""
              .formatted(PREFIX, PREFIX, String.join(", ", adapters.keySet())));
    }

    final var unknownAdapters = properties
        .prioritizedAdapters()
        .get()
        .stream()
        .filter(adapter -> !adapters.containsKey(adapter))
        .map(adapter -> "%s -> '%s.adapters.%s'".formatted(adapter, PREFIX, adapter))
        .collect(Collectors.joining("\n  "));
    if (!unknownAdapters.isEmpty()) {
      throw new IllegalStateException(
          """
              The property '%s.prioritized-adapters' lists these adapters for which no property sections were found:
                %s"""
              .formatted(PREFIX, unknownAdapters));
    }

    return properties.prioritizedAdapters().get();

  }

}