From 077d5c2cafb80e9c7ef638ce1bf90c9322668727 Mon Sep 17 00:00:00 2001 From: Stephan Pelikan Date: Mon, 26 Feb 2024 10:30:03 +0100 Subject: [PATCH] Introducing new structure of Spring Boot properties --- README.md | 66 ++- pom.xml | 2 +- .../adapter/AdapterAwareProcessService.java | 62 +-- ...apterAwareProcessServiceConfiguration.java | 214 ++------- .../adapter/ModuleAwareBpmnDeployment.java | 123 +++-- .../adapter/VanillaBpProperties.java | 427 +++++++++++++++--- .../springboot/utils/WorkflowAndModule.java | 85 ---- 7 files changed, 543 insertions(+), 436 deletions(-) delete mode 100644 src/main/java/io/vanillabp/springboot/utils/WorkflowAndModule.java diff --git a/README.md b/README.md index 990919e..461b708 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ For each use-case to be implemented, the BPMN and the underlying implementation To encapsulate a workflow module in its runtime environment (e.g. Spring boot container, JEE application container) all necessary configuration and dependencies are bundled as if it were a standalone application. Therefore, each workflow should be an independent module (jar) containing classes and resources (e.g. the BPMN file). +If you run an application consisting of only one workflow module you don't need to define a workflow module explicitly. Instead, the property `spring.application.name` is used as the workflow module id. + ### Configuration In a Spring boot environment one can use [externalized properties](https://www.baeldung.com/spring-yaml) to store configuration details. Typically, a YAML formatted file stored in classpath `config/application.yaml` is used. In a workflow module the same mechanism is used, but the name of the YAML file is customized (e.g. `ride.yaml` for the taxi ride example). @@ -106,6 +108,27 @@ public class RideProperties } ``` +### Loading BPM engine resources + +Each process engine uses files (BPMN, DMN, etc.) to run workflows. Those files are bundled as part of the workflow module and loaded for each module separately. +Therefore each workflow module has to define a specific directory not clashing with other workflow modules in the classpath: + +```yaml +vanillabp: + workflow-modules: + ride: + adapters: + camunda7: + resources-location: classpath*:/ride/camunda7 + camunda8: + resources-location: classpath*:/ride/camunda8 +``` + +*Hint:* The BPMN standard is about defining processes but each process engine needs specific attributes to run them. +Therefore, on migrating workflows from one engine to another, the BPMN files have to be copied and changed +according to new engine used. Having this in mind one should also define a resources location specific to the current engine used, +even if there are no plans to migrate to any other engine yet (see sample above). + ## Spring boot profiles Typically, profiles are used to set environment specific properties (e.g. stages): @@ -202,33 +225,34 @@ The second scenario is fully supported by VanillaBP. It is a minimally invasive In periods of no migration there is only one adapter in classpath and therefore no configuration is required. Once you prepare for migration by adding another adapter, some Spring Boot properties have to be set. The settings are validated at startup and violations will be reported and abort launch. -**Setting adapters for a specific workflow:** +**Setting adapters for a specific workflow-module `ride`:** ```yaml vanillabp: - workflows: - - bpmn-process-id: DemoWorkflow - adapter: camunda8, camunda7 + workflow-modules: + ride: + default-adapter: camunda8, camunda7 ``` -*Hint:* The adapters are used in the order of appearance. Additionally, the first adapter is used to start new workflows. +*Hint:* The adapters are used in the order of their appearance in `default-adapter`. Additionally, the first adapter is used to start new workflows. -**Setting adapters for a specific workflow which is part of a workflow-module:** +**Setting adapters for a specific workflow having BPMN process ID `Ride`:** ```yaml vanillabp: - workflows: - - bpmn-process-id: DemoWorkflow - workflow-module-id: Demo - adapter: camunda8, camunda7 + workflow-modules: + ride: + workflows: + Ride: + default-adapter: camunda8, camunda7 ``` -**Setting adapters for a all not explicitly specified workflows:** +**Setting adapters for all, not explicitly specified workflows:** ```yaml vanillabp: default-adapter: camunda8, camunda7 - workflows: + workflow-modules: ... ``` @@ -239,19 +263,23 @@ Sometimes it is fine to not migrate a workflow (e.g. it will be retired soon). I ```yaml vanillabp: default-adapter: camunda8, camunda7 - workflows: - - bpmn-process-id: DemoWorkflow - adapter: camunda7 + workflow-modules: + ride: + workflows: + Ride: + default-adapter: camunda7 ``` -Using this technique it is also possible to limit migration to specific workflows only: +Using this technique, it is also possible to limit migration to specific workflows only: ```yaml vanillabp: default-adapter: camunda7 - workflows: - - bpmn-process-id: DemoWorkflow - adapter: camunda8, camunda7 + workflow-modules: + ride: + workflows: + Ride: + default-adapter: camunda8, camunda7 ``` ## Noteworthy & Contributors diff --git a/pom.xml b/pom.xml index 38f0a12..793c6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ spring-boot-support Abstract VanillaBP SPI adapter for Spring Boot - 1.0.8-SNAPSHOT + 1.1.0-SNAPSHOT UTF-8 diff --git a/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessService.java b/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessService.java index 5858850..e8b21b7 100644 --- a/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessService.java +++ b/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessService.java @@ -1,7 +1,6 @@ package io.vanillabp.springboot.adapter; import io.vanillabp.spi.process.ProcessService; -import io.vanillabp.springboot.adapter.VanillaBpProperties.WorkflowAndModuleAdapters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.repository.CrudRepository; @@ -24,8 +23,8 @@ * is done for each adapter. *

* @see VanillaBpProperties#getDefaultAdapter() - * @see VanillaBpProperties#getWorkflows() - * @see WorkflowAndModuleAdapters#getAdapter() + * @see VanillaBpProperties.WorkflowModuleAdapterProperties#getDefaultAdapter() + * @see VanillaBpProperties.WorkflowAdapterProperties#getDefaultAdapter() */ public class AdapterAwareProcessService implements ProcessService { @@ -35,6 +34,8 @@ public class AdapterAwareProcessService implements ProcessService { private final Map> processServicesByAdapter; + private final List wiredAdapterIds = new LinkedList<>(); + private String workflowModuleId; private String primaryBpmnProcessId; @@ -62,7 +63,7 @@ public AdapterAwareProcessService( this.workflowAggregateClass = workflowAggregateClass; processServicesByAdapter.forEach((adapterId, adapter) -> adapter.setParent(this)); - + } public CrudRepository getWorkflowAggregateRepository() { @@ -162,6 +163,7 @@ public void wire( if (this.workflowModuleId == null) { this.workflowModuleId = workflowModuleId; } + if (isPrimary) { this.primaryBpmnProcessId = bpmnProcessId; if (messageBasedStartEventsMessageNames != null) { @@ -171,26 +173,28 @@ public void wire( this.signalBasedStartEventsSignalNames.addAll(signalBasedStartEventsSignalNames); } } + + wiredAdapterIds.add(adapterId); + if (wiredAdapterIds.size() == processServicesByAdapter.size()) { // all adapters wired for this service + properties.validatePropertiesFor( + wiredAdapterIds, + workflowModuleId, + bpmnProcessId); + } + this.bpmnProcessIds.add(bpmnProcessId); } - private List determineAdapterIds() { - - return properties - .getWorkflows() - .stream() - .filter(workflow -> workflow.matchesAny(workflowModuleId, bpmnProcessIds)) - .findFirst() - .map(WorkflowAndModuleAdapters::getAdapter) - .filter(adapter -> !adapter.isEmpty()) - .orElse(properties.getDefaultAdapter()); + private List getAdapterIds() { + + return properties.getDefaultAdapterFor(workflowModuleId, primaryBpmnProcessId); } - private String determinePrimaryAdapterId() { + private String getPrimaryAdapterId() { - return determineAdapterIds().get(0); + return getAdapterIds().get(0); } @@ -199,7 +203,7 @@ public DE startWorkflow( final DE workflowAggregate) throws Exception { return processServicesByAdapter - .get(determinePrimaryAdapterId()) + .get(getPrimaryAdapterId()) .startWorkflow(workflowAggregate); } @@ -212,12 +216,12 @@ public DE correlateMessage( if (messageBasedStartEventsMessageNames.contains(messageName)) { return processServicesByAdapter - .get(determinePrimaryAdapterId()) + .get(getPrimaryAdapterId()) .correlateMessage(workflowAggregate, messageName); } else { - return determineAdapterIds() + return getAdapterIds() .stream() .map(processServicesByAdapter::get) .map(adapter -> adapter.correlateMessage(workflowAggregate, messageName)) @@ -239,12 +243,12 @@ public DE correlateMessage( if (messageBasedStartEventsMessageNames.contains(messageName)) { return processServicesByAdapter - .get(determinePrimaryAdapterId()) + .get(getPrimaryAdapterId()) .correlateMessage(workflowAggregate, messageName, correlationId); } else { - return determineAdapterIds() + return getAdapterIds() .stream() .map(processServicesByAdapter::get) .map(adapter -> adapter.correlateMessage(workflowAggregate, messageName, correlationId)) @@ -263,12 +267,12 @@ public DE correlateMessage( if (messageBasedStartEventsMessageNames.contains(message.getClass().getSimpleName())) { return processServicesByAdapter - .get(determinePrimaryAdapterId()) + .get(getPrimaryAdapterId()) .correlateMessage(workflowAggregate, message); } else { - return determineAdapterIds() + return getAdapterIds() .stream() .map(processServicesByAdapter::get) .map(adapter -> adapter.correlateMessage(workflowAggregate, message)) @@ -288,12 +292,12 @@ public DE correlateMessage( if (messageBasedStartEventsMessageNames.contains(message.getClass().getSimpleName())) { return processServicesByAdapter - .get(determinePrimaryAdapterId()) + .get(getPrimaryAdapterId()) .correlateMessage(workflowAggregate, message, correlationId); } else { - return determineAdapterIds() + return getAdapterIds() .stream() .map(processServicesByAdapter::get) .map(adapter -> adapter.correlateMessage(workflowAggregate, message, correlationId)) @@ -310,7 +314,7 @@ public DE completeUserTask( final String taskId) { final var exceptions = new LinkedList>(); - return determineAdapterIds() + return getAdapterIds() .stream() .map(adapterId -> Map.entry(adapterId, processServicesByAdapter.get(adapterId))) .map(adapter -> { @@ -340,7 +344,7 @@ public DE cancelUserTask( final String bpmnErrorCode) { final var exceptions = new LinkedList>(); - return determineAdapterIds() + return getAdapterIds() .stream() .map(adapterId -> Map.entry(adapterId, processServicesByAdapter.get(adapterId))) .map(adapter -> { @@ -369,7 +373,7 @@ public DE completeTask( final String taskId) { final var exceptions = new LinkedList>(); - return determineAdapterIds() + return getAdapterIds() .stream() .map(adapterId -> Map.entry(adapterId, processServicesByAdapter.get(adapterId))) .map(adapter -> { @@ -399,7 +403,7 @@ public DE cancelTask( final String bpmnErrorCode) { final var exceptions = new LinkedList>(); - return determineAdapterIds() + return getAdapterIds() .stream() .map(adapterId -> Map.entry(adapterId, processServicesByAdapter.get(adapterId))) .map(adapter -> { diff --git a/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessServiceConfiguration.java b/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessServiceConfiguration.java index 0a500d5..2f971cb 100644 --- a/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessServiceConfiguration.java +++ b/src/main/java/io/vanillabp/springboot/adapter/AdapterAwareProcessServiceConfiguration.java @@ -1,23 +1,24 @@ package io.vanillabp.springboot.adapter; import io.vanillabp.spi.process.ProcessService; +import io.vanillabp.springboot.modules.WorkflowModuleProperties; +import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; -import org.springframework.util.CollectionUtils; import java.lang.reflect.ParameterizedType; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; @EnableConfigurationProperties(VanillaBpProperties.class) public class AdapterAwareProcessServiceConfiguration { @@ -28,7 +29,13 @@ public class AdapterAwareProcessServiceConfiguration { @Autowired(required = false) private VanillaBpProperties properties; - + + @Value("${spring.application.name}") + private String applicationName; + + @Autowired(required = false) + private List moduleProperties; + @Autowired private List> adapterConfigurations; @@ -38,6 +45,28 @@ public Map, AdapterAwareProcessService> vanillaBpConnectableServices return connectableServices; } + + @PostConstruct + public void validateConfiguration() { + + if (adapterConfigurations.isEmpty()) { + throw new RuntimeException( + "No VanillaBP adapter was found in classpath!"); + } + + final var hasExplicitDefinedWorkflowModules = (moduleProperties != null) + && !moduleProperties.isEmpty(); + final var workflowModuleIds = hasExplicitDefinedWorkflowModules + ? moduleProperties.stream().map(WorkflowModuleProperties::getWorkflowModuleId).toList() + : List.of(applicationName); + final var adapterIds = adapterConfigurations + .stream() + .map(AdapterConfigurationBase::getAdapterId) + .toList(); + + properties.validateProperties(adapterIds, workflowModuleIds); + + } @SuppressWarnings("unchecked") @Bean @@ -73,8 +102,6 @@ public ProcessService adapterAwareProcessService( .getRepository(workflowAggregateClass); final var workflowAggregateIdClass = springDataUtil .getIdType(workflowAggregateClass); - - validateConfiguration(); final var processServicesByAdapter = adapterConfigurations .stream() @@ -103,181 +130,4 @@ public ProcessService adapterAwareProcessService( } - private void validateConfiguration() { - - final var listOfAdapters = adapterConfigurations - .stream() - .map(AdapterConfigurationBase::getAdapterId) - .collect(Collectors.joining("', '")); - - if (adapterConfigurations.isEmpty()) { - - throw new RuntimeException( - "No VanillaBP adapter was found in classpath!"); - - } - - if ((adapterConfigurations.size() > 1) - && ((properties == null) - || CollectionUtils.isEmpty(properties.getDefaultAdapter()))) { - - throw new RuntimeException( - "More than one VanillaBP adapter is found in classpath: '" - + listOfAdapters - + "'! You have to define a default using the property 'vanillabp.default-adapter'."); - - } - - if ((properties != null) - && !CollectionUtils.isEmpty(properties.getDefaultAdapter()) - && properties - .getDefaultAdapter() - .stream() - .filter(adapter -> adapterConfigurations - .stream() - .noneMatch(c -> c.getAdapterId().equals(adapter))) - .findFirst() - .isPresent()) { - - final var missingAdapters = properties - .getDefaultAdapter() - .stream() - .filter(adapter -> adapterConfigurations - .stream() - .noneMatch(c -> c.getAdapterId().equals(adapter))) - .collect(Collectors.joining("', '")); - - throw new RuntimeException( - "Property 'vanillabp.default-adapter' is set to '" - + properties.getDefaultAdapter().stream().collect(Collectors.joining(", ")) - + "' but this/these adapters are not in classpath: '" - + missingAdapters - + "'! Available adapters are: '" - + listOfAdapters - + "'."); - - } - - if (properties == null) { - this.properties = new VanillaBpProperties(); - } - - if (properties.getDefaultAdapter() == null) { - properties.setDefaultAdapter(List.of(adapterConfigurations.get(0).getAdapterId())); - } - - properties - .getWorkflows() - .stream() - .collect(Collectors.groupingBy(workflow -> - workflow.getWorkflowModuleId() - + "#" - + workflow.getBpmnProcessId(), - Collectors.counting())) - .entrySet() - .stream() - .filter(entry -> entry.getValue() > 1) - .peek(entry -> { - final var workflowModuleIdAndBpmnProcessId = entry.getKey().split("#"); - if (workflowModuleIdAndBpmnProcessId[0].equals("null")) { - logger.error( - "BPMN process id '{}' was found more than one time " - + "in property 'vanillabp.adapters'!", - workflowModuleIdAndBpmnProcessId[1]); - } else { - logger.error( - "BPMN process id '{}' of workflow module '{}' was " - + "found more than one time in property 'vanillabp.adapters'!", - workflowModuleIdAndBpmnProcessId[1], - workflowModuleIdAndBpmnProcessId[0]); - } - }) - .findFirst() - .ifPresent(entry -> { - throw new RuntimeException( - "At least one BPMN process id was configured more " - + "than once in property 'vanillabp.workflows'! " - + "Check previous error logs for details."); - }); - - properties - .getWorkflows() - .stream() - .filter(workflow -> { - final var hasUnknownAdapterConfigured = workflow - .getAdapter() - .stream() - .filter(adapter -> adapterConfigurations - .stream() - .noneMatch(c -> c.getAdapterId().equals(adapter))) - .findFirst() - .isPresent(); - if (!hasUnknownAdapterConfigured) { - return false; - } - - final var missingAdapters = workflow - .getAdapter() - .stream() - .filter(adapter -> adapterConfigurations - .stream() - .noneMatch(c -> c.getAdapterId().equals(adapter))) - .collect(Collectors.joining("', '")); - - if (workflow.getWorkflowModuleId() == null) { - logger.error( - "Property 'vanillabp.workflows[bpmn-process-id={}].adapters' is set to '{}' " - + "but this/these adapters are not in classpath: '{}'! " - + "Available adapters are: '{}'.", - workflow.getBpmnProcessId(), - workflow.getAdapter().stream().collect(Collectors.joining(", ")), - missingAdapters, - listOfAdapters); - } else { - logger.error( - "Property 'vanillabp.workflows[bpmn-process-id={}, workflow-module-id={}].adapters' is set to '{}' " - + "but this/these adapters are not in classpath: '{}'! " - + "Available adapters are: '{}'.", - workflow.getBpmnProcessId(), - workflow.getWorkflowModuleId(), - workflow.getAdapter().stream().collect(Collectors.joining(", ")), - missingAdapters, - listOfAdapters); - } - - return true; - }) - .findFirst() - .ifPresent(entry -> { - throw new RuntimeException( - "At least once the property 'vanillabp.workflows.*.adapter' " - + "was set to an adapter not available in classpath! " - + "Check previous error logs for details."); - }); - - final var configuredAdapters = Stream.concat( - properties - .getDefaultAdapter() - .stream(), - properties - .getWorkflows() - .stream() - .flatMap(workflow -> workflow.getAdapter().stream())) - .distinct() - .collect(Collectors.toList()); - final var nonConfigureAdapters = adapterConfigurations - .stream() - .map(AdapterConfigurationBase::getAdapterId) - .filter(adapterId -> configuredAdapters.stream().noneMatch(adapterId::equals)) - .collect(Collectors.joining("', '")); - if (!nonConfigureAdapters.isBlank()) { - throw new RuntimeException( - "Found VanillaBP adapters in classpath which are neither part of property " - + "'vanillabp.default-adapter' nor of 'vanillabp.workflows.*.adapter': '" - + nonConfigureAdapters - + "'!"); - } - - } - } diff --git a/src/main/java/io/vanillabp/springboot/adapter/ModuleAwareBpmnDeployment.java b/src/main/java/io/vanillabp/springboot/adapter/ModuleAwareBpmnDeployment.java index c88dd13..9028d79 100644 --- a/src/main/java/io/vanillabp/springboot/adapter/ModuleAwareBpmnDeployment.java +++ b/src/main/java/io/vanillabp/springboot/adapter/ModuleAwareBpmnDeployment.java @@ -1,93 +1,73 @@ package io.vanillabp.springboot.adapter; import io.vanillabp.springboot.modules.WorkflowModuleProperties; -import io.vanillabp.springboot.utils.WorkflowAndModule; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.StringUtils; -import java.io.File; import java.io.IOException; import java.util.List; -import java.util.Optional; - -import static java.lang.String.format; public abstract class ModuleAwareBpmnDeployment { - public static final String DEFAULT_RESOURCES_PATH = "processes"; - protected abstract Logger getLogger(); protected abstract String getAdapterId(); private final VanillaBpProperties properties; + private final String applicationName; + @Autowired(required = false) private List moduleProperties; public ModuleAwareBpmnDeployment( - final VanillaBpProperties properties) { + final VanillaBpProperties properties, + final String applicationName) { this.properties = properties; + this.applicationName = applicationName; } - + protected void deployAllWorkflowModules() { - - if (moduleProperties == null) { - deployWorkflowModule(null); - return; - } - - moduleProperties.forEach(this::deployWorkflowModule); + + final var hasExplicitDefinedWorkflowModules = (moduleProperties != null) + && !moduleProperties.isEmpty(); + + if (hasExplicitDefinedWorkflowModules) { + moduleProperties.forEach(this::deployWorkflowModule); + return; + } + + if (!StringUtils.hasText(applicationName)) { + throw new RuntimeException( + "No workflow-module configurations found (see " + + "https://github.com/vanillabp/spring-boot-support?tab=readme-ov-file#configuration)\n" + + "and need to use property 'spring.application.name' as the workflow-module-id instead but it is not defined!"); + } else { + getLogger().info( + "No workflow-module configurations found (see " + + "https://github.com/vanillabp/spring-boot-support?tab=readme-ov-file#configuration),\n" + + "will use property 'spring.application.name' as the workflow-module-id instead: {}", + applicationName); + } + deployWorkflowModule(applicationName); } private void deployWorkflowModule( final WorkflowModuleProperties propertySpecification) { - final var workflowModuleId = propertySpecification == null - ? null - : propertySpecification.getWorkflowModuleId(); - final var resourcesPath = resourcesPath(workflowModuleId); - + final var workflowModuleId = propertySpecification.getWorkflowModuleId(); + deployWorkflowModule( - workflowModuleId, - resourcesPath); + workflowModuleId); } - - private String resourcesPath( - final String workflowModuleId) { - - final var adaptersPath = properties - .getAdapters() - .entrySet() - .stream() - .filter(entry -> entry.getKey().equals(getAdapterId())) - .findFirst() - .map(entry -> entry.getValue().getResourcesPath()) - .or(() -> Optional.ofNullable(properties.getResourcesPath())) - .orElse(DEFAULT_RESOURCES_PATH); - - if (workflowModuleId == null) { - return adaptersPath; - } - - final var workflowModulePath = properties - .getWorkflows() - .stream() - .filter(workflowAndModuleAdapters -> workflowAndModuleAdapters.matches(workflowModuleId)) - .findFirst() - .map(WorkflowAndModule::getResourcesPath) - .orElseGet(() -> workflowModuleId); - - return adaptersPath + File.separator + workflowModulePath; - - } protected abstract void doDeployment( String workflowModuleId, @@ -96,14 +76,16 @@ protected abstract void doDeployment( Resource[] cmms) throws Exception; private void deployWorkflowModule( - final String workflowModuleId, - final String basePackageName) { - + final String workflowModuleId) { + + final var resourcesLocation = properties + .getAdapterResourcesLocationFor(workflowModuleId, getAdapterId()); + try { - final var bpmns = findResources(workflowModuleId, basePackageName, "*.bpmn"); - final var cmms = findResources(workflowModuleId, basePackageName, "*.cmmn"); - final var dmns = findResources(workflowModuleId, basePackageName, "*.dmn"); + final var bpmns = findResources(workflowModuleId, resourcesLocation, "*.bpmn"); + final var cmms = findResources(workflowModuleId, resourcesLocation, "*.cmmn"); + final var dmns = findResources(workflowModuleId, resourcesLocation, "*.dmn"); doDeployment( workflowModuleId, @@ -125,20 +107,31 @@ private void deployWorkflowModule( private Resource[] findResources( final String workflowModuleId, - final String basePackageName, + final String adapterResourcesLocation, final String fileNamePattern) throws IOException { - final var resourcesPath = format("%s%s/**/%s", - ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX, - basePackageName.replace('.', '/'), - fileNamePattern); + // test for multi-jar + if ((moduleProperties != null) + && (!adapterResourcesLocation.contains(":") // e.g. file://, classpath://, etc. + || adapterResourcesLocation.startsWith(ResourcePatternResolver.CLASSPATH_URL_PREFIX))) { + getLogger() + .warn("On using workflow modules you should define resource-path using '{}' to ensure BPMN resources are found!\nCurrent resource-path of module '{}' for adapater '{}':\n{}", + ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX, + workflowModuleId, + getAdapterId(), + adapterResourcesLocation); + } + + final var resourcesLocation = adapterResourcesLocation.endsWith("/") + ? adapterResourcesLocation + fileNamePattern + : adapterResourcesLocation + "/" + fileNamePattern; getLogger() .debug("Scanning process archive <{}> for {}", workflowModuleId == null ? "default" : workflowModuleId, - resourcesPath); + resourcesLocation); - return new PathMatchingResourcePatternResolver().getResources(resourcesPath); + return new PathMatchingResourcePatternResolver().getResources(resourcesLocation); } diff --git a/src/main/java/io/vanillabp/springboot/adapter/VanillaBpProperties.java b/src/main/java/io/vanillabp/springboot/adapter/VanillaBpProperties.java index 65f6163..e762e18 100644 --- a/src/main/java/io/vanillabp/springboot/adapter/VanillaBpProperties.java +++ b/src/main/java/io/vanillabp/springboot/adapter/VanillaBpProperties.java @@ -1,82 +1,399 @@ package io.vanillabp.springboot.adapter; -import io.vanillabp.springboot.utils.WorkflowAndModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; @ConfigurationProperties(prefix = VanillaBpProperties.PREFIX, ignoreUnknownFields = true) public class VanillaBpProperties { + private static final Logger logger = LoggerFactory.getLogger(VanillaBpProperties.class); + public static final String PREFIX = "vanillabp"; - - private List defaultAdapter; - - private String resourcesPath; - - private List workflows = List.of(); - - private Map adapters = Map.of(); - - public List getWorkflows() { - return workflows; - } - - public void setWorkflows(List workflows) { - this.workflows = workflows; - } - - public Map getAdapters() { - return adapters; - } - - public void setAdapters(Map adapters) { - this.adapters = adapters; - } - - public List getDefaultAdapter() { - return defaultAdapter; - } - - public void setDefaultAdapter(List defaultAdapter) { - this.defaultAdapter = defaultAdapter; - } - - public String getResourcesPath() { - return resourcesPath; + + private List defaultAdapter = List.of(); + + private Map workflowModules = Map.of(); + + public Map getWorkflowModules() { + return workflowModules; } - - public void setResourcesPath(String resourcesPath) { - this.resourcesPath = resourcesPath; + + public void setWorkflowModules(Map workflowModules) { + + this.workflowModules = workflowModules; + workflowModules.forEach((workflowModuleId, properties) -> { + properties.workflowModuleId = workflowModuleId; + properties.defaultProperties = this; + }); + } - + + public List getDefaultAdapter() { return defaultAdapter; } + + public void setDefaultAdapter(List defaultAdapter) { this.defaultAdapter = defaultAdapter; } + public static class AdapterConfiguration { - private String resourcesPath; + private String resourcesLocation; - public String getResourcesPath() { - return resourcesPath; + public String getResourcesLocation() { + return resourcesLocation; } - public void setResourcesPath(String resourcesPath) { - this.resourcesPath = resourcesPath; + public void setResourcesLocation(String resourcesLocation) { + this.resourcesLocation = resourcesLocation; } } - public static class WorkflowAndModuleAdapters extends WorkflowAndModule { - - private List adapter = List.of(); - - public List getAdapter() { - return adapter; + public static class WorkflowModuleAdapterProperties extends AdapterProperties { + + String workflowModuleId; + + VanillaBpProperties defaultProperties; + + private Map adapters = Map.of(); + + private Map workflows = Map.of(); + + public Map getWorkflows() { + return workflows; } - - public void setAdapter(List adapter) { - this.adapter = adapter; + + public void setWorkflows(Map workflows) { + + this.workflows = workflows; + workflows.forEach((bpmnProcessId, properties) -> { + properties.bpmnProcessId = bpmnProcessId; + properties.workflowModule = this; + }); + } - + + public Map getAdapters() { + return adapters; + } + + public void setAdapters(Map adapters) { + this.adapters = adapters; + } + + public String getWorkflowModuleId() { + return workflowModuleId; + } + + public VanillaBpProperties getDefaultProperties() { + return defaultProperties; + } + + } + + public static class WorkflowAdapterProperties extends AdapterProperties { + + String bpmnProcessId; + + WorkflowModuleAdapterProperties workflowModule; + + public WorkflowModuleAdapterProperties getWorkflowModule() { + return workflowModule; + } + + public String getBpmnProcessId() { + return bpmnProcessId; + } + + } + + private static class AdapterProperties { + + private List defaultAdapter = List.of(); + + public List getDefaultAdapter() { + return defaultAdapter; + } + + public void setDefaultAdapter(List defaultAdapter) { + this.defaultAdapter = defaultAdapter; + } + + } + + public List getDefaultAdapterFor( + final String workflowModuleId, + final String bpmnProcessId) { + + var defaultAdapter = getDefaultAdapter(); + final var workflowModule = getWorkflowModules().get(workflowModuleId); + if (workflowModule != null) { + if (!workflowModule.getDefaultAdapter().isEmpty()) { + defaultAdapter = workflowModule.getDefaultAdapter(); + } + if (bpmnProcessId != null) { + final var workflow = workflowModule.getWorkflows().get(bpmnProcessId); + if (workflow != null) { + if (!workflow.getDefaultAdapter().isEmpty()) { + defaultAdapter = workflowModule.getDefaultAdapter(); + } + } + } + } + return defaultAdapter; + + } + + public String getAdapterResourcesLocationFor( + final String workflowModuleId, + final String adapterId) { + + String resourcesLocation = null; + final var workflowModule = getWorkflowModules().get(workflowModuleId); + if (workflowModule != null) { + final var adapter = workflowModule.getAdapters().get(adapterId); + if (adapter != null) { + resourcesLocation = adapter.getResourcesLocation(); + } + } + if (resourcesLocation == null) { + throw new RuntimeException( + "Property '" + + VanillaBpProperties.PREFIX + + ".workflow-modules." + + workflowModuleId + + ".adapters." + + adapterId + + ".resources-location' not set!\nIt has to point to a location specific to the adapter in order " + + "to avoid future problems once you wish to migrate to another adapter.\nSample: '" + + "classpath*:/workflow-resources/" + + adapterId + + "'"); + } + return resourcesLocation; + + } + + public void validatePropertiesFor( + final List adapterIds, + final String workflowModuleId, + final String bpmnProcessId) { + + // default-adapter + if (adapterIds.size() == 1) { + setDefaultAdapter(adapterIds); + } else { + final var defaultAdapters = getDefaultAdapterFor(workflowModuleId, bpmnProcessId); + if (defaultAdapters.isEmpty()) { + throw new RuntimeException( + "More than one VanillaBP adapter was found in classpath, but no default adapter is configured at\n " + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".workflows." + + bpmnProcessId + + ".default-adapter or \n " + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".default-adapter or \n " + + PREFIX + + ".default-adapter\nAvailable adapters are '" + + String.join("', '", adapterIds) + + "'."); + } + + final var listOfAdapters = String.join("', '", adapterIds); + final var missingAdapters = defaultAdapters + .stream() + .filter(defaultAdapter -> !adapterIds.contains(defaultAdapter)) + .collect(Collectors.joining("', '")); + if (!missingAdapters.isEmpty()) { + throw new RuntimeException( + "Property 'default-adapter' of workflow-module '" + + workflowModuleId + + "' and bpmn-process-id '" + + bpmnProcessId + + "' contains adapters not available in classpath:\n' " + + missingAdapters + + "'!\nAvailable adapters are: '" + + listOfAdapters + + "'."); + } + + } + + } + + public void validateProperties( + final List adapterIds, + final List knownWorkflowModuleIds) { + + // unknown workflow-module properties + final var configAvailableButNotInClasspath = new LinkedList<>( + getWorkflowModules() + .keySet()); + configAvailableButNotInClasspath + .removeAll(knownWorkflowModuleIds); + if (!configAvailableButNotInClasspath.isEmpty()) { + logger.warn("Found properties for workflow-modules\n" + + PREFIX + ".workflow-modules." + + String.join( + "\n" + PREFIX + ".workflow-modules.", + configAvailableButNotInClasspath) + + "\nwhich were not found in the class-path!"); + } + + // adapter configured + if (adapterIds.size() == 1) { + logger.info( + "Found only one VanillaBP adapter '" + + adapterIds.get(0) + + "' in classpath. Please ensure the properties\n" + + PREFIX + + ".workflow-modules" + + String.join( + ".adapters." + + adapterIds.get(0) + + ".resources-location\n" + + PREFIX + + ".workflow-modules", + configAvailableButNotInClasspath) + + ".adapters." + + adapterIds.get(0) + + ".resources-location\nare specific for this adapter in " + + "order to avoid future-problems once you wish to migrate to another adapter."); + } + + // adapters in class-path not used + final var notConfiguredAdapters = new HashMap>() { + @Override + public Set get(final Object key) { + var adapters = super.get(key); + if (adapters == null) { + adapters = new HashSet<>(); + super.put(key.toString(), adapters); + } + return adapters; + } + }; + getDefaultAdapter() + .stream() + .filter(adapterId -> !adapterIds.contains(adapterId)) + .forEach(adapterId -> notConfiguredAdapters.get( + VanillaBpProperties.PREFIX + + ".default-adapter" + ).add(adapterId)); + getWorkflowModules().values().forEach(workflowModule -> { + workflowModule + .getDefaultAdapter() + .stream() + .filter(adapterId -> !adapterIds.contains(adapterId)) + .forEach(adapterId -> notConfiguredAdapters.get( + VanillaBpProperties.PREFIX + + ".workflow-modules." + + workflowModule.workflowModuleId + + ".default-adapter" + ).add(adapterId)); + workflowModule.getWorkflows().values().forEach(workflow -> { + workflow + .getDefaultAdapter() + .stream() + .filter(adapterId -> !adapterIds.contains(adapterId)) + .forEach(adapterId -> notConfiguredAdapters.get( + VanillaBpProperties.PREFIX + + ".workflow-modules." + + workflowModule.workflowModuleId + + ".workflows." + + workflow.getBpmnProcessId() + + ".default-adapter" + ).add(adapterId)); + }); + }); + if (!notConfiguredAdapters.isEmpty()) { + throw new RuntimeException( + "There are VanillaBP adapters configured not found in classpath:\n" + + notConfiguredAdapters + .entrySet() + .stream() + .map(entry -> " " + entry.getKey() + "=" + entry.getValue().stream().collect(Collectors.joining(","))) + .collect(Collectors.joining("\n"))); + } + + // resources-location + knownWorkflowModuleIds + .forEach(workflowModuleId -> { + final var defaultAdapterOfModule = getDefaultAdapterFor(workflowModuleId, null); + final var resourcesLocationOfModule = (defaultAdapterOfModule.isEmpty() + ? defaultAdapter : defaultAdapterOfModule) + .stream() + .filter(adapterId -> getAdapterResourcesLocationFor(workflowModuleId, adapterId) == null) + .toList(); + if (!resourcesLocationOfModule.isEmpty()) { + throw new RuntimeException( + "You need to define properties '" + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".adapters." + + String.join( + ".resources-location\n" + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".adapters.", + defaultAdapter) + + ".resources-location"); + } + getBpmnProcessIdsForWorkflowModule(workflowModuleId) + .forEach(bpmnProcessId -> { + final var defaultAdapter = getDefaultAdapterFor(workflowModuleId, bpmnProcessId); + final var resourcesLocation = defaultAdapter + .stream() + .filter(adapterId -> getAdapterResourcesLocationFor(workflowModuleId, adapterId) == null) + .toList(); + if (!resourcesLocation.isEmpty()) { + throw new RuntimeException( + "You need to define properties '" + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".adapters." + + String.join( + ".resources-location\n" + + PREFIX + + ".workflow-modules." + + workflowModuleId + + ".adapters.", + defaultAdapter) + + ".resources-location"); + } + }); + }); + + } + + private List getBpmnProcessIdsForWorkflowModule( + final String workflowModuleId) { + + final var workflowModule = getWorkflowModules().get(workflowModuleId); + if (workflowModule == null) { + return List.of(); + } + + return workflowModule + .getWorkflows() + .values() + .stream() + .map(WorkflowAdapterProperties::getBpmnProcessId) + .toList(); + } } diff --git a/src/main/java/io/vanillabp/springboot/utils/WorkflowAndModule.java b/src/main/java/io/vanillabp/springboot/utils/WorkflowAndModule.java deleted file mode 100644 index 9b4a8a7..0000000 --- a/src/main/java/io/vanillabp/springboot/utils/WorkflowAndModule.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.vanillabp.springboot.utils; - -import java.util.Collection; - -public class WorkflowAndModule { - - private String workflowModuleId; - - private String bpmnProcessId; - - private String resourcesPath; - - public String getBpmnProcessId() { - return bpmnProcessId; - } - - public void setBpmnProcessId(String bpmnProcessId) { - this.bpmnProcessId = bpmnProcessId; - } - - public String getWorkflowModuleId() { - return workflowModuleId; - } - - public void setWorkflowModuleId(String workflowModuleId) { - this.workflowModuleId = workflowModuleId; - } - - public String getResourcesPath() { - return resourcesPath; - } - - public void setResourcesPath(String resourcesPath) { - this.resourcesPath = resourcesPath; - } - - public boolean matches( - final String workflowModuleId) { - - return matches(workflowModuleId, null); - - } - - public boolean matchesAny( - final String workflowModuleId, - final Collection bpmnProcessIds) { - - if ((this.workflowModuleId != null) - && (workflowModuleId != null)) { - if (!this.workflowModuleId.equals(workflowModuleId)) { - return false; - } - } - - if (this.bpmnProcessId == null) { - return true; - } - - return bpmnProcessIds.contains(this.bpmnProcessId); - - } - - public boolean matches( - final String workflowModuleId, - final String bpmnProcessId) { - - if ((this.workflowModuleId != null) - && (workflowModuleId != null)) { - if (!this.workflowModuleId.equals(workflowModuleId)) { - return false; - } - } - - if (this.bpmnProcessId == null) { - return true; - } - if (bpmnProcessId == null) { - return false; - } - - return this.bpmnProcessId.equals(bpmnProcessId); - - } - -}