Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@
"target": "doc:/zgwActionSummary"
},
{
"source": "doc:/reviewerRemarks",
"source": "doc:/submission/reviewerRemarks",
"target": "doc:/reviewerRemarks"
},
{
"source": "doc:/approvalDecision",
"source": "doc:/submission/approvalDecision",
"target": "doc:/approvalDecision"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@
"syncTiming": "END"
},
{
"source": "/reviewerRemarks",
"source": "/submission/reviewerRemarks",
"target": "doc:/reviewerRemarks",
"syncTiming": "END"
},
{
"source": "/approvalDecision",
"source": "/submission/approvalDecision",
"target": "doc:/approvalDecision",
"syncTiming": "END"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.ritense.buildingblock.processlink.mapper.BuildingBlockProcessLinkMapp
import com.ritense.buildingblock.processlink.service.BuildingBlockCallActivityListener
import com.ritense.buildingblock.processlink.service.BuildingBlockSupportedProcessLinksHandler
import com.ritense.buildingblock.processlink.service.DefaultBuildingBlockPluginConfigurationResolver
import com.ritense.buildingblock.processlink.service.DefaultBuildingBlockPluginMappingUsageFinder
import com.ritense.buildingblock.repository.BuildingBlockProcessLinkRepository
import com.ritense.buildingblock.repository.BuildingBlockDefinitionArtworkRepository
import com.ritense.buildingblock.repository.BuildingBlockDefinitionRepository
import com.ritense.buildingblock.repository.BuildingBlockInstanceRepository
Expand Down Expand Up @@ -98,6 +100,7 @@ import com.ritense.formflow.service.FormFlowService
import com.ritense.importer.ImportService
import com.ritense.importer.ValtimoImportService
import com.ritense.plugin.service.BuildingBlockPluginConfigurationResolver
import com.ritense.plugin.service.BuildingBlockPluginMappingUsageFinder
import com.ritense.plugin.service.PluginService
import com.ritense.processdocument.service.BuildingBlockProcessLookup
import com.ritense.processdocument.service.ProcessDocumentAssociationService
Expand Down Expand Up @@ -476,6 +479,17 @@ class BuildingBlockAutoConfiguration {
documentService,
)

@Bean
@ConditionalOnMissingBean(BuildingBlockPluginMappingUsageFinder::class)
fun buildingBlockPluginMappingUsageFinder(
buildingBlockProcessLinkRepository: BuildingBlockProcessLinkRepository,
caseDefinitionBuildingBlockLinkRepository: CaseDefinitionBuildingBlockLinkRepository,
): BuildingBlockPluginMappingUsageFinder =
DefaultBuildingBlockPluginMappingUsageFinder(
buildingBlockProcessLinkRepository,
caseDefinitionBuildingBlockLinkRepository,
)

@Bean
@ConditionalOnMissingBean(BuildingBlockCaseAssigneeListener::class)
fun buildingBlockCaseAssigneeListener(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import jakarta.persistence.Embedded
import jakarta.persistence.Entity
import jakarta.persistence.PrimaryKeyJoinColumn
import jakarta.persistence.SecondaryTable
import org.hibernate.annotations.SecondaryRow
import org.hibernate.annotations.Type
import java.util.UUID

Expand All @@ -38,6 +39,12 @@ import java.util.UUID
name = SECONDARY_TABLE_NAME,
pkJoinColumns = [PrimaryKeyJoinColumn(name = "process_link_id")]
)
// A building-block process link ALWAYS has its secondary row. Declaring it non-optional stops
// Hibernate from writing the row through the optional-secondary-table upsert `MERGE`, which on
// Hibernate 6.6 mis-binds the `jsonb` mapping columns as `integer`
// ("column input_mappings is of type jsonb but expression is of type integer") — a plain
// INSERT/UPDATE binds them correctly through the JsonType binder.
@SecondaryRow(table = SECONDARY_TABLE_NAME, optional = false)
@DiscriminatorValue(PROCESS_LINK_TYPE)
class BuildingBlockProcessLink(
id: UUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,31 @@ class DefaultBuildingBlockPluginConfigurationResolver(
private val documentService: DocumentService,
) : BuildingBlockPluginConfigurationResolver {

override fun resolve(execution: DelegateExecution, pluginDefinitionKey: String): UUID? {
val instance = findInstance(execution) ?: return null
val root = findRootInstance(instance)

return findCallActivityMapping(root, pluginDefinitionKey)
?: findCaseLinkMapping(root, pluginDefinitionKey)
}
override fun resolve(execution: DelegateExecution, pluginDefinitionKey: String): UUID? =
resolveMapping(execution) { it[pluginDefinitionKey] }

override fun resolve(task: DelegateTask, pluginDefinitionKey: String): UUID? {
return resolve(task.execution, pluginDefinitionKey)
}

override fun resolveByKeyPrefix(execution: DelegateExecution, keyPrefix: String): UUID? =
resolveMapping(execution) { mappings ->
mappings.entries.firstOrNull { it.key.startsWith(keyPrefix) }?.value
}

/**
* Selects a configuration id from the call-activity process link's mappings first, then the
* case-definition ↔ building-block link's mappings — the original resolution order — returning
* the first non-null.
*/
private fun resolveMapping(execution: DelegateExecution, select: (Map<String, UUID>) -> UUID?): UUID? {
val instance = findInstance(execution) ?: return null
val root = findRootInstance(instance)

return callActivityMappings(root)?.let(select)
?: caseLinkMappings(root)?.let(select)
}

/**
* Prefer the BB document id (Valtimo convention: business key == document id, propagated through
* `<camunda:in businessKey="..."/>` on call activities). This makes the resolver work from any
Expand Down Expand Up @@ -78,30 +91,27 @@ class DefaultBuildingBlockPluginConfigurationResolver(
}

/**
* Resolves plugin configuration from the BuildingBlockProcessLink on the call activity
* that started the root building block.
* The plugin configuration mappings from the BuildingBlockProcessLink on the call activity that
* started the root building block.
*/
private fun findCallActivityMapping(instance: BuildingBlockInstance, pluginDefinitionKey: String): UUID? {
private fun callActivityMappings(instance: BuildingBlockInstance): Map<String, UUID>? {
val activityId = instance.activityId ?: return null
val callerProcessDefinitionId = instance.callerProcessDefinitionId ?: return null

return processLinkService.getProcessLinks(callerProcessDefinitionId, activityId)
.filterIsInstance<BuildingBlockProcessLink>()
.firstOrNull()
?.pluginConfigurationMappings
?.get(pluginDefinitionKey)
}

private fun findCaseLinkMapping(instance: BuildingBlockInstance, pluginDefinitionKey: String): UUID? {
private fun caseLinkMappings(instance: BuildingBlockInstance): Map<String, UUID>? {
val caseDocumentId = instance.caseDocumentId ?: return null
val caseDocument = documentService.get(caseDocumentId.toString())
val caseDefinitionId = caseDocument.definitionId().caseDefinitionId()

val link = linkRepository.findByCaseDefinitionIdAndBuildingBlockDefinitionId(
return linkRepository.findByCaseDefinitionIdAndBuildingBlockDefinitionId(
caseDefinitionId,
instance.definition.id
) ?: return null

return link.pluginConfigurationMappings[pluginDefinitionKey]
)?.pluginConfigurationMappings
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2015-2026 Ritense BV, the Netherlands.
*
* Licensed under EUPL, Version 1.2 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* 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.
*/

package com.ritense.buildingblock.processlink.service

import com.ritense.buildingblock.repository.BuildingBlockProcessLinkRepository
import com.ritense.buildingblock.repository.CaseDefinitionBuildingBlockLinkRepository
import com.ritense.plugin.service.BuildingBlockPluginMappingUsage
import com.ritense.plugin.service.BuildingBlockPluginMappingUsageFinder
import com.ritense.valtimo.contract.annotation.SkipComponentScan
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.util.UUID

@Component
@SkipComponentScan
@Transactional(readOnly = true)
class DefaultBuildingBlockPluginMappingUsageFinder(
private val processLinkRepository: BuildingBlockProcessLinkRepository,
private val caseLinkRepository: CaseDefinitionBuildingBlockLinkRepository,
) : BuildingBlockPluginMappingUsageFinder {

/**
* The mappings live in JSON columns, so matching happens in memory — the platform supports
* both PostgreSQL and MySQL, whose JSON query dialects differ. Both tables hold
* configuration-time data (bounded by the number of linked activities / case-BB pairs), not
* runtime data, so a full scan stays small.
*/
override fun findUsages(configurationId: UUID): List<BuildingBlockPluginMappingUsage> {
val processLinkUsages = processLinkRepository.findAll().flatMap { link ->
link.pluginConfigurationMappings.filterValues { it == configurationId }.keys.map { mappingKey ->
BuildingBlockPluginMappingUsage(
mappingKey = mappingKey,
buildingBlockDefinitionKey = link.buildingBlockDefinitionId.key,
processLinkId = link.id,
processDefinitionId = link.processDefinitionId,
activityId = link.activityId,
)
}
}

val caseLinkUsages = caseLinkRepository.findAll().flatMap { caseLink ->
caseLink.pluginConfigurationMappings.filterValues { it == configurationId }.keys.map { mappingKey ->
BuildingBlockPluginMappingUsage(
mappingKey = mappingKey,
buildingBlockDefinitionKey = caseLink.buildingBlockDefinitionId.key,
caseDefinitionKey = caseLink.caseDefinitionId.key,
caseDefinitionVersionTag = caseLink.caseDefinitionId.versionTag.toString(),
)
}
}

return processLinkUsages + caseLinkUsages
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2015-2026 Ritense BV, the Netherlands.
*
* Licensed under EUPL, Version 1.2 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* 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.
*/

package com.ritense.buildingblock.repository

import com.ritense.buildingblock.processlink.domain.BuildingBlockProcessLink
import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID

interface BuildingBlockProcessLinkRepository : JpaRepository<BuildingBlockProcessLink, UUID>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import com.ritense.buildingblock.processlink.domain.BuildingBlockProcessLink
import com.ritense.buildingblock.repository.ProcessDefinitionBuildingBlockDefinitionRepository
import com.ritense.plugin.service.PluginService
import com.ritense.plugin.web.rest.result.PluginDefinitionsWithDependenciesDto
import com.ritense.plugin.web.rest.result.PluginRequirementSource
import com.ritense.plugin.web.rest.result.PluginWithDependenciesDto
import com.ritense.processlink.repository.ValtimoPluginProcessLinkRepository
import com.ritense.processlink.service.ProcessLinkService
import com.ritense.valtimo.contract.annotation.SkipComponentScan
Expand Down Expand Up @@ -85,12 +87,70 @@ class BuildingBlockPluginDefinitionService(
return keys.toSet()
}

/**
* External plugin references (`external_plugin` process links with a `BUILDING_BLOCK`
* reference) required by a building block, keyed by `pluginId` + manifest version, including
* references from nested building blocks.
*/
fun getExternalPluginReferencesForBuildingBlock(
buildingBlockDefinitionId: BuildingBlockDefinitionId
): Set<Pair<String, String>> {
return getExternalPluginReferencesForBuildingBlockRecursive(buildingBlockDefinitionId, mutableSetOf())
}

private fun getExternalPluginReferencesForBuildingBlockRecursive(
buildingBlockDefinitionId: BuildingBlockDefinitionId,
visitedBuildingBlocks: MutableSet<BuildingBlockDefinitionId>
): Set<Pair<String, String>> {
if (visitedBuildingBlocks.contains(buildingBlockDefinitionId)) {
return emptySet()
}
visitedBuildingBlocks.add(buildingBlockDefinitionId)

val processDefinitionIds = processDefinitionBuildingBlockDefinitionRepository
.findAllByIdBuildingBlockDefinitionId(buildingBlockDefinitionId)
.map { it.id.processDefinitionId.id }

if (processDefinitionIds.isEmpty()) {
return emptySet()
}

val directReferences = pluginProcessLinkRepository
.findExternalPluginReferencesByProcessDefinitionIds(processDefinitionIds)
.map { it.getPluginDefinitionKey() to it.getPluginDefinitionVersion() }
.toSet()

val nestedBuildingBlockDefinitionIds = processDefinitionIds.flatMap { processDefinitionId ->
processLinkService.getProcessLinks(processDefinitionId)
.filterIsInstance<BuildingBlockProcessLink>()
.map { it.buildingBlockDefinitionId }
}.toSet()

val nestedReferences = nestedBuildingBlockDefinitionIds.flatMap { nestedBuildingBlockId ->
getExternalPluginReferencesForBuildingBlockRecursive(nestedBuildingBlockId, visitedBuildingBlocks)
}.toSet()

return directReferences + nestedReferences
}

fun getPluginDefinitionsWithDependenciesForBuildingBlock(
buildingBlockId: BuildingBlockDefinitionId
): PluginDefinitionsWithDependenciesDto {
val pluginKeys = getPluginDefinitionKeysForBuildingBlock(buildingBlockId)
val embedded = pluginService.getPluginDefinitionsWithDependencies(pluginKeys)

val externalReferences = getExternalPluginReferencesForBuildingBlock(buildingBlockId)
val external = externalReferences.map { (pluginId, version) ->
PluginWithDependenciesDto(
pluginDefinitionKey = pluginId,
dependencies = emptyList(),
source = PluginRequirementSource.EXTERNAL,
pluginDefinitionVersion = version,
)
}

return pluginService
.getPluginDefinitionsWithDependencies(pluginKeys)
return PluginDefinitionsWithDependenciesDto(
plugins = embedded.plugins + external
)
}
}
Loading
Loading