From 7fd251a31b3288009e534c96cc521caa592463aa Mon Sep 17 00:00:00 2001 From: Klaas Schuijtemaker Date: Mon, 17 Aug 2026 13:53:07 +0200 Subject: [PATCH 1/2] Resolve plugin dependencies from the Valtimo S3 release repository Add the S3 bucket that the backend now dual-publishes to as a dependency repository, so artifacts released there resolve without waiting for the Maven Central sync. --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1d35e27..f9375cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ allprojects { mavenCentral() maven { url = uri("https://s01.oss.sonatype.org/content/repositories/releases/") } maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://valtimo-releases.s3.eu-central-1.amazonaws.com/") } } } From 68da0ff8f80a9c7ec48fa94b3818fc87a1e8bf08 Mon Sep 17 00:00:00 2001 From: Klaas Schuijtemaker Date: Wed, 16 Sep 2026 15:40:59 +0200 Subject: [PATCH 2/2] Start the case at the event's own start event instead of by process key, release 0.1.2 A process may have more than one start event - a plain one for the start form and the message start event the `receive-cloud-event` link hangs on - and Operaton enters a process started by key at whichever it considers the initial activity, which is the plain one. The event's own start event then never runs, so neither do the execution listeners that put the event on the case, and the case is created empty. The case is now created directly and the start message of the linked element correlated to it, with the document id as business key so `doc:` resolves before the process-document association exists. Also makes the two silent skips - no linked case definition, superseded case definition version - visible in the logs, and turns the BPMN lookup of the message name into a clear error instead of a NullPointerException. --- backend/plugin/plugin.properties | 2 +- .../CloudEventAutoConfiguration.kt | 9 +- .../listener/CloudEventProcessLinkListener.kt | 119 +++++++++++++----- documentation/plugin.md | 4 +- documentation/release-notes.md | 5 + frontend/projects/plugin/package.json | 2 +- 6 files changed, 101 insertions(+), 40 deletions(-) diff --git a/backend/plugin/plugin.properties b/backend/plugin/plugin.properties index df8862a..3e55c66 100644 --- a/backend/plugin/plugin.properties +++ b/backend/plugin/plugin.properties @@ -15,4 +15,4 @@ # pluginGroupId=com.ritense.valtimoplugins pluginArtifactId=cloud-event -pluginVersion=0.1.1 +pluginVersion=0.1.2 diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/autoconfiguration/CloudEventAutoConfiguration.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/autoconfiguration/CloudEventAutoConfiguration.kt index 78c7e3d..55548d1 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/autoconfiguration/CloudEventAutoConfiguration.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/autoconfiguration/CloudEventAutoConfiguration.kt @@ -18,11 +18,12 @@ package com.ritense.valtimoplugins.cloudevent.autoconfiguration import com.fasterxml.jackson.databind.ObjectMapper import com.ritense.case.service.CaseDefinitionService +import com.ritense.document.service.DocumentService import com.ritense.inbox.ValtimoEventHandler import com.ritense.outbox.OutboxService import com.ritense.plugin.service.PluginService import com.ritense.processdocument.service.ProcessDefinitionCaseDefinitionService -import com.ritense.processdocument.service.ProcessDocumentService +import com.ritense.processdocument.service.ProcessDocumentAssociationService import com.ritense.processlink.repository.ValtimoPluginProcessLinkRepository import com.ritense.valtimo.contract.config.LiquibaseMasterChangeLogLocation import com.ritense.valtimo.service.ProcessPropertyService @@ -65,7 +66,8 @@ class CloudEventAutoConfiguration { repositoryService: RepositoryService, processPropertyService: ProcessPropertyService, processDefinitionCaseDefinitionService: ProcessDefinitionCaseDefinitionService, - processDocumentService: ProcessDocumentService, + documentService: DocumentService, + processDocumentAssociationService: ProcessDocumentAssociationService, caseDefinitionService: CaseDefinitionService, objectMapper: ObjectMapper, processedCloudEventRepository: ProcessedCloudEventRepository, @@ -76,7 +78,8 @@ class CloudEventAutoConfiguration { repositoryService, processPropertyService, processDefinitionCaseDefinitionService, - processDocumentService, + documentService, + processDocumentAssociationService, caseDefinitionService, objectMapper, processedCloudEventRepository, diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/listener/CloudEventProcessLinkListener.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/listener/CloudEventProcessLinkListener.kt index 4970c79..4c9ec6e 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/listener/CloudEventProcessLinkListener.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimoplugins/cloudevent/listener/CloudEventProcessLinkListener.kt @@ -18,16 +18,17 @@ package com.ritense.valtimoplugins.cloudevent.listener import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory +import com.ritense.authorization.AuthorizationContext import com.ritense.authorization.annotation.RunWithoutAuthorization import com.ritense.case.service.CaseDefinitionService import com.ritense.document.domain.impl.request.NewDocumentRequest +import com.ritense.document.service.DocumentService import com.ritense.inbox.ValtimoEvent import com.ritense.inbox.ValtimoEventHandler import com.ritense.plugin.domain.PluginProcessLink import com.ritense.processdocument.domain.ProcessDefinitionId -import com.ritense.processdocument.domain.impl.request.NewDocumentAndStartProcessRequest import com.ritense.processdocument.service.ProcessDefinitionCaseDefinitionService -import com.ritense.processdocument.service.ProcessDocumentService +import com.ritense.processdocument.service.ProcessDocumentAssociationService import com.ritense.processlink.domain.ActivityTypeWithEventName import com.ritense.processlink.repository.ValtimoPluginProcessLinkRepository import com.ritense.valtimo.service.ProcessPropertyService @@ -42,6 +43,7 @@ import org.operaton.bpm.model.bpmn.instance.CatchEvent import org.operaton.bpm.model.bpmn.instance.MessageEventDefinition import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional +import java.util.UUID open class CloudEventProcessLinkListener( private val pluginProcessLinkRepository: ValtimoPluginProcessLinkRepository, @@ -49,7 +51,8 @@ open class CloudEventProcessLinkListener( private val repositoryService: RepositoryService, private val processPropertyService: ProcessPropertyService, private val processDefinitionCaseDefinitionService: ProcessDefinitionCaseDefinitionService, - private val processDocumentService: ProcessDocumentService, + private val documentService: DocumentService, + private val processDocumentAssociationService: ProcessDocumentAssociationService, private val caseDefinitionService: CaseDefinitionService, private val objectMapper: ObjectMapper, private val processedCloudEventRepository: ProcessedCloudEventRepository, @@ -70,6 +73,7 @@ open class CloudEventProcessLinkListener( .findByPluginActionDefinitionKey(ACTION_KEY) // .findByPluginDefinitionKeyAndPluginActionDefinitionKey(PLUGIN_KEY, ACTION_KEY) if (processLinks.isEmpty()) { + logger.debug { "Cloud event '${event.id}' matched no receive-cloud-event process link" } return } @@ -171,15 +175,28 @@ open class CloudEventProcessLinkListener( try { processDefinitionCaseDefinitionService .findByProcessDefinitionId(ProcessDefinitionId(processLink.processDefinitionId)) - // .findByProcessDefinitionIdOrNull(ProcessDefinitionId(processLink.processDefinitionId)) - ?: return - } catch (_: Exception) { + ?: run { + logger.warn { + "No case definition linked to process definition '${processLink.processDefinitionId}'" + } + return + } + } catch (e: Exception) { + logger.warn(e) { + "No case definition linked to process definition '${processLink.processDefinitionId}'" + } return } + // Only the deployed, active version of a case definition may be started. Without + // this an old process link would keep creating cases against a superseded version. val activeCaseDefinition = caseDefinitionService.getActiveCaseDefinition(processDefinitionCaseDefinition.id.caseDefinitionId.key) if (activeCaseDefinition?.id != processDefinitionCaseDefinition.id.caseDefinitionId) { + logger.debug { + "Skipping process link for '${processLink.processDefinitionId}': it points at a case definition " + + "version that is no longer active" + } return } @@ -188,27 +205,56 @@ open class CloudEventProcessLinkListener( "because canInitializeDocument is false on the linked case definition." } - val processDefinitionKey = - processDefinitionCaseDefinition.processDefinitionKey - ?: error("Process definition key not found for '${processLink.processDefinitionId}'") - - val request = - NewDocumentAndStartProcessRequest( - processDefinitionKey, - NewDocumentRequest( - activeCaseDefinition.id.key, - activeCaseDefinition.id.key, - activeCaseDefinition.id.versionTag.toString(), - JsonNodeFactory.instance.objectNode(), - ), - ).withProcessVars(variables) - + val messageName = getMessageName(processLink) logger.info { - "Starting document process for case '${activeCaseDefinition.id.key}' (${activeCaseDefinition.id.versionTag}) with process definition key '$processDefinitionKey'" + "Creating a case for case definition '${activeCaseDefinition.id.key}' " + + "(${activeCaseDefinition.id.versionTag}) by correlating start message '$messageName' to " + + "'${processLink.activityId}'" } - val result = processDocumentService.newDocumentAndStartProcess(request) - if (result.errors().isNotEmpty()) { - error("Failed to start document process: ${result.errors()}") + + val newDocumentRequest = + NewDocumentRequest( + activeCaseDefinition.id.key, + activeCaseDefinition.id.key, + activeCaseDefinition.id.versionTag.toString(), + JsonNodeFactory.instance.objectNode(), + ) + val documentResult = + AuthorizationContext.runWithoutAuthorization { + documentService.createDocument(newDocumentRequest) + } + val document = + documentResult.resultingDocument().orElse(null) + ?: error("Failed to create a case for the incoming cloud event: ${documentResult.errors()}") + + // Correlated to the start message rather than started by process definition key. + // ProcessDocumentService.newDocumentAndStartProcess, the obvious alternative, starts a + // process by key, and Operaton then enters it at whichever start event it considers the + // process's initial activity. A process with both a plain start event (someone fills in + // the start form) and this message start event is a normal shape, and on that shape "by + // key" silently lands on the plain one: the event's own start event never runs, so + // neither do its execution listeners, and the case is created without any of the cloud + // event on it. + val processInstance = + runtimeService + .createMessageCorrelation(messageName) + .processDefinitionId(processLink.processDefinitionId) + // Valtimo resolves `doc:` for a process instance through the process-document + // association, and falls back to the business key while that association does + // not exist yet. It cannot exist yet here - it is created below, once the + // instance has an id - so the start event's own listeners depend on this + // business key to reach the document. Valtimo's own start path sets it the + // same way. + .processInstanceBusinessKey(document.id().toString()) + .setVariables(variables) + .correlateStartMessage() + + AuthorizationContext.runWithoutAuthorization { + processDocumentAssociationService.createProcessDocumentInstance( + processInstance.id, + UUID.fromString(document.id().toString()), + repositoryService.getProcessDefinition(processLink.processDefinitionId).name, + ) } } @@ -227,16 +273,21 @@ open class CloudEventProcessLinkListener( private fun getMessageName(processLink: PluginProcessLink): String { val model = repositoryService.getBpmnModelInstance(processLink.processDefinitionId) - val element = model.getModelElementById(processLink.activityId) - val messageEventDefinition = - element.eventDefinitions - .filterIsInstance() - .firstOrNull() - ?: throw IllegalStateException( - "No message event definition found on element '${processLink.activityId}' " + - "in process definition '${processLink.processDefinitionId}'", + val element = + model.getModelElementById(processLink.activityId) + ?: error( + "No catch event '${processLink.activityId}' in process definition " + + "'${processLink.processDefinitionId}'", ) - return messageEventDefinition.message.name + return element.eventDefinitions + .filterIsInstance() + .firstOrNull() + ?.message + ?.name + ?: error( + "No message event definition on element '${processLink.activityId}' in process definition " + + "'${processLink.processDefinitionId}'", + ) } companion object { diff --git a/documentation/plugin.md b/documentation/plugin.md index cf48891..619feaf 100644 --- a/documentation/plugin.md +++ b/documentation/plugin.md @@ -86,7 +86,9 @@ When a CloudEvent is received, the following process variables are set on the ta - **Receive Task** -- the waiting execution is signaled with `runtimeService.signal()`. - **Intermediate Catch Event** -- a message is correlated to the waiting execution using the BPMN message name. - **Message Start Event** -- a new process instance is started. For document processes, a new document is created - and the process is started via `ProcessDocumentService`. For system processes, the message is correlated directly. + and the start message of the linked element is correlated to it, so the process starts at that event and not at + whichever start event the engine considers the process's initial activity. For system processes, the message is + correlated directly. ## Database diff --git a/documentation/release-notes.md b/documentation/release-notes.md index 51947c6..5b7e57e 100644 --- a/documentation/release-notes.md +++ b/documentation/release-notes.md @@ -2,6 +2,11 @@ Overzicht van wijzigingen per versie van de Cloud Event-plugin. +## 0.1.2 + +Een dossier dat door een binnenkomende cloud event wordt gestart, toont nu de gegevens van die +gebeurtenis in plaats van leeg te blijven. + ## 0.1.1 Valtimo bijgewerkt naar versie 13.41.0. diff --git a/frontend/projects/plugin/package.json b/frontend/projects/plugin/package.json index 1573414..865da90 100644 --- a/frontend/projects/plugin/package.json +++ b/frontend/projects/plugin/package.json @@ -1,7 +1,7 @@ { "name": "@valtimo-plugins/cloud-event", "license": "EUPL-1.2", - "version": "0.1.1", + "version": "0.1.2", "peerDependencies": { "@angular/common": "^19.2.8", "@angular/core": "^19.2.8"