SpringBootMigrationAdapterTransformer.java
package io.vanillabp.integration.config;
import static io.vanillabp.integration.config.SpringBootMigrationAdapterProperties.PREFIX;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import io.vanillabp.integration.adapter.migration.config.AdapterProperties;
import io.vanillabp.integration.adapter.migration.config.MigrationAdapterProperties;
import io.vanillabp.integration.adapter.migration.config.WorkflowModuleAdapterProperties;
import lombok.Builder;
/**
* Turns {@link SpringBootMigrationAdapterProperties} 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(toBuilder = true)
public class SpringBootMigrationAdapterTransformer {
/**
* The properties to transform
*/
private SpringBootMigrationAdapterProperties properties;
/**
* Adapters found in classpath
*/
private List<String> adaptersFound;
/**
* Workflow modules found in the classpath
*/
private List<String> workflowModulesFound;
/**
* Transforms {@link SpringBootMigrationAdapterProperties} into
* {@link MigrationAdapterProperties}.
*
* @return The {@link MigrationAdapterProperties}
*/
public MigrationAdapterProperties getAndValidatePropertiesConfigured() {
final var result = new MigrationAdapterProperties();
result.setResourcesLocation(properties.getResourcesLocation());
// validate properties of adapters against adapters found in classpath
final var adaptersConfigured = getAndValidateAdaptersConfigured();
result.setAdapters(adaptersConfigured);
// validate priorities of adapters configured against adapters found in classpath
final var prioritizedAdaptersConfigured = getAndValidatePrioritizedAdaptersConfigured(
adaptersConfigured);
result.setPrioritizedAdapters(prioritizedAdaptersConfigured);
// validate properties of workflow modules against workflow modules found in classpath
final var workflowModulesConfigured = getAndValidateWorkflowModulesConfigured();
result.setWorkflowModules(workflowModulesConfigured);
result.validateProperties(adaptersFound, workflowModulesFound);
return result;
}
/**
* Determine workflow module properties and validate them against workflow modules found in classpath.
*
* @return Map of workflow modules (key = workflow module ID, value = properties)
*/
private Map<String, WorkflowModuleAdapterProperties> getAndValidateWorkflowModulesConfigured() {
final var result = properties
.getWorkflowModules()
.entrySet()
.stream()
.map(workflowModule -> Map.entry(
workflowModule.getKey(),
(WorkflowModuleAdapterProperties) WorkflowModuleAdapterProperties
.builder()
.workflowModuleId(workflowModule.getKey())
.prioritizedAdapters(workflowModule.getValue().getPrioritizedAdapters())
.workflows(Map.of()) // TODO fill workflows
.adapters(workflowModule
.getValue()
.getAdapters()
.entrySet()
.stream()
.map(adapter -> Map.entry(
adapter.getKey(),
AdapterProperties
.builder()
.resourcesLocation(adapter.getValue().getResourcesLocation())
.build()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
.build()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (result.isEmpty() && !workflowModulesFound.isEmpty()) {
final var missingConfigSections = workflowModulesFound
.stream()
.map(module -> "%s.workflow-modules.%s".formatted(PREFIX, module))
.collect(Collectors.joining("', '"));
throw new IllegalStateException(
"No workflow-modules configured! Add properties sections '%s'.".formatted(missingConfigSections));
}
// check for unconfigured workflow modules
final var unconfiguredModules = workflowModulesFound
.stream()
.filter(module -> !result.containsKey(module))
.collect(Collectors.joining("\n, "));
if (!unconfiguredModules.isEmpty()) {
throw new IllegalStateException(
"""
Unconfigured VanillaBP workflow modules were found in classpath:
%s
Add property keys '%s.workflow-modules.*' to configure them."""
.formatted(unconfiguredModules, PREFIX));
}
// check for unknown adapters
final var unknownModules = result
.keySet()
.stream()
.filter(workflowModuleAdapterProperties -> !workflowModulesFound.contains(workflowModuleAdapterProperties))
.map(workflowModuleAdapterProperties -> "%s.workflow-modules.%s".formatted(PREFIX,
workflowModuleAdapterProperties))
.collect(Collectors.joining("\n, "));
if (!unknownModules.isEmpty()) {
throw new IllegalStateException(
"""
Property keys '%s.workflow-modules.*' must name VanillaBP workflow modules available in classpath!
These unknown workflow modules were found in properties:
%s
Available workflow modules currently loaded in classpath: '%s'."""
.formatted(PREFIX, unknownModules, String.join("', '", workflowModulesFound)));
}
return result;
}
/**
* Determine adapters configured based adapters found in classpath and properties configured.
*
* @return Map of adapters (key = adapter name, value = adapter type)
*/
private Map<String, String> getAndValidateAdaptersConfigured() {
if (adaptersFound.isEmpty()) {
throw new IllegalStateException(
"No adapters found in classpath! Add dependencies providing VanillaBP adapters.");
}
// build result map (key = adapter name, value = adapter type)
final var result = properties
.getAdapters()
.entrySet()
.stream()
.map(config -> Map.entry(config.getKey(),
Optional.ofNullable(config.getValue().getType()).orElse(config.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (result.isEmpty()) {
final var missingConfigSections = adaptersFound
.stream()
.map(adapter -> "%s.adapters.xxx.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 -> !adaptersFound.contains(adapter.getValue()))
.map(adapter -> "'%s' found in '%s.adapters.%s.type'".formatted(adapter.getValue(), PREFIX, adapter.getKey()))
.collect(Collectors.joining(", "));
if (!unknownAdapters.isEmpty()) {
throw new IllegalStateException(
"""
Properties '%s.adapters.*.type' must contain VanillaBP adapters available in classpath!
These adapters are unknown: %s.
Available adapter types currently loaded in classpath: '%s'."""
.formatted(PREFIX, unknownAdapters, String.join("', '", adaptersFound)));
}
return result;
}
/**
* Determine priorities of adapters configured.
*
* @param adapters The adapters found
* @return List of adapter ordered by configured priorities
*/
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.getPrioritizedAdapters().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:
final var adapterIdsConfigured = String.join(", ", adapters.keySet());
if (properties.getPrioritizedAdapters()
.isEmpty() || (adapters.size() != properties.getPrioritizedAdapters().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, adapterIdsConfigured));
}
final var unknownAdapters = properties
.getPrioritizedAdapters()
.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.getPrioritizedAdapters();
}
}