Skip to content

Commit

Permalink
fix(#1031): Don't delete task correlations on task update events (#1032)
Browse files Browse the repository at this point in the history
* fix(#1031): update existing entity instead of creating new one
- Prevent hibernate from merging entities
- closes #1031

* chore(#1031): add documentation to public function

* chore(#1031): adjust import style, update payloadAttributes instead of overwrite

---------

Co-authored-by: Michael von Bargen <[email protected]>
  • Loading branch information
MichaelVonB and Michael von Bargen authored Aug 19, 2024
1 parent e52deee commit c69e53c
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import io.holunda.polyflow.view.jpa.auth.AuthorizationPrincipal.Companion.group
import io.holunda.polyflow.view.jpa.auth.AuthorizationPrincipal.Companion.user
import io.holunda.polyflow.view.jpa.data.DataEntryRepository
import io.holunda.polyflow.view.jpa.data.toDataEntry
import io.holunda.polyflow.view.jpa.task.TaskEntity
import io.holunda.polyflow.view.jpa.task.TaskRepository
import io.holunda.polyflow.view.jpa.task.TaskEntity
import io.holunda.polyflow.view.jpa.task.toTask
import io.holunda.polyflow.view.jpa.task.update
import io.holunda.polyflow.view.jpa.task.toEntity
import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.hasApplication
import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAssignedTo
import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAssigneeSet
import io.holunda.polyflow.view.jpa.task.TaskRepository.Companion.isAuthorizedFor
import io.holunda.polyflow.view.jpa.task.toEntity
import io.holunda.polyflow.view.jpa.task.toTask
import io.holunda.polyflow.view.jpa.update.updateTaskQuery
import io.holunda.polyflow.view.query.PageableSortableQuery
import io.holunda.polyflow.view.query.task.*
Expand Down Expand Up @@ -387,22 +388,14 @@ class JpaPolyflowViewTaskService(
if (isDisabledByProperty()) {
return
}

taskRepository
.findById(event.id)
.ifEmpty {
logger.warn { "Cannot update task '${event.id}' because it does not exist in the database" }
}
.ifPresent { entity ->

val updated = taskRepository.save(
event.toEntity(
objectMapper,
entity,
polyflowJpaViewProperties.payloadAttributeLevelLimit,
polyflowJpaViewProperties.taskJsonPathFilters()
)
)
entity.update(event, objectMapper, polyflowJpaViewProperties.payloadAttributeLevelLimit, polyflowJpaViewProperties.taskJsonPathFilters())
val updated = taskRepository.save(entity)
emitTaskUpdate(updated)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import io.holunda.polyflow.view.jpa.payload.PayloadAttribute
import io.holunda.polyflow.view.jpa.process.toSourceReference
import io.holunda.polyflow.view.jpa.process.toSourceReferenceEmbeddable
import org.camunda.bpm.engine.variable.VariableMap
import org.camunda.bpm.engine.variable.Variables.createVariables
import org.camunda.bpm.engine.variable.Variables
import java.time.Instant


Expand Down Expand Up @@ -47,44 +47,31 @@ fun TaskCreatedEngineEvent.toEntity(
)

/**
* Update event to entity.
* Applies a TaskAttributeUpdatedEngineEvent update to an existing TaskEntity
*/
fun TaskAttributeUpdatedEngineEvent.toEntity(
objectMapper: ObjectMapper,
oldEntity: TaskEntity,
limit: Int,
filters: List<Pair<JsonPathFilterFunction, FilterType>>
) = TaskEntity(
taskId = this.id,
taskDefinitionKey = this.taskDefinitionKey,
sourceReference = this.sourceReference.toSourceReferenceEmbeddable(),
authorizedPrincipals = oldEntity.authorizedPrincipals,
assignee = oldEntity.assignee,
name = this.name ?: oldEntity.name,
priority = this.priority ?: oldEntity.priority,
correlations = if (this.correlations.isNotEmpty()) {
this.correlations.map { entry -> DataEntryId(entryType = entry.key, entryId = "${entry.value}") }.toMutableSet()
} else {
oldEntity.correlations
},
payload = if (this.payload.isNotEmpty()) {
this.payload.toPayloadJson(objectMapper)
} else {
oldEntity.payload
},
payloadAttributes = if (this.payload.isNotEmpty()) {
this.payload.toJsonPathsWithValues(limit, filters).map { attr -> PayloadAttribute(attr) }.toMutableSet()
} else {
oldEntity.payloadAttributes
},
businessKey = this.businessKey ?: oldEntity.businessKey,
description = this.description ?: oldEntity.description,
formKey = oldEntity.formKey,
createdDate = oldEntity.createdDate,
followUpDate = this.followUpDate?.toInstant() ?: oldEntity.followUpDate,
dueDate = this.dueDate?.toInstant() ?: oldEntity.dueDate,
owner = this.owner ?: oldEntity.owner
)
fun TaskEntity.update(event: TaskAttributeUpdatedEngineEvent,
objectMapper: ObjectMapper,
limit: Int,
filters: List<Pair<JsonPathFilterFunction, FilterType>>) {
this.taskDefinitionKey = event.taskDefinitionKey
this.sourceReference = event.sourceReference.toSourceReferenceEmbeddable()
this.name = event.name ?: this.name
this.priority = event.priority ?: this.priority
if (event.correlations.isNotEmpty()) {
this.correlations.clear()
this.correlations.addAll(event.correlations.map { entry -> DataEntryId(entryType = entry.key, entryId = "${entry.value}") })
}
if (event.payload.isNotEmpty()) {
this.payload = event.payload.toPayloadJson(objectMapper)
this.payloadAttributes.clear()
this.payloadAttributes.addAll(event.payload.toJsonPathsWithValues(limit, filters).map { attr -> PayloadAttribute(attr) }.toMutableSet())
}
businessKey = event.businessKey ?: this.businessKey
description = event.description ?: this.description
followUpDate = event.followUpDate?.toInstant() ?: this.followUpDate
dueDate = event.dueDate?.toInstant() ?: this.dueDate
owner = event.owner ?: this.owner
}

/**
* Entity to API DTO.
Expand Down Expand Up @@ -116,4 +103,4 @@ fun TaskEntity.toTask(
/**
* Create a variable map from stored data entries list.
*/
fun MutableSet<DataEntryId>.toCorrelations(): VariableMap = createVariables().apply { this@toCorrelations.associate { it.entryType to it.entryId } }
fun MutableSet<DataEntryId>.toCorrelations(): VariableMap = Variables.fromMap(this.associate { it.entryType to it.entryId })
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.holixon.axon.gateway.query.RevisionValue
import io.holunda.camunda.taskpool.api.business.*
import io.holunda.camunda.taskpool.api.task.TaskAssignedEngineEvent
import io.holunda.camunda.taskpool.api.task.TaskAttributeUpdatedEngineEvent
import io.holunda.camunda.taskpool.api.task.TaskCompletedEngineEvent
import io.holunda.camunda.taskpool.api.task.TaskCreatedEngineEvent
import io.holunda.camunda.variable.serializer.serialize
Expand All @@ -15,6 +16,7 @@ import io.holunda.polyflow.view.jpa.process.toSourceReference
import io.holunda.polyflow.view.query.data.DataEntriesForUserQuery
import io.holunda.polyflow.view.query.task.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.data.MapEntry
import org.axonframework.messaging.MetaData
import org.axonframework.queryhandling.GenericSubscriptionQueryUpdateMessage
import org.axonframework.queryhandling.QueryUpdateEmitter
Expand Down Expand Up @@ -221,6 +223,22 @@ internal class JpaPolyflowViewServiceTaskITest {
),
metaData = RevisionValue(revision = 1).toMetaData()
)

jpaPolyflowViewService.on(
event = TaskAttributeUpdatedEngineEvent(
id = id4,
taskDefinitionKey = "task.def.0815",
name = "task name 4",
priority = 10,
sourceReference = processReference().toSourceReference(),
payload = createVariables().apply { putAll(createPayload("otherValue")) },
correlations = newCorrelations().apply {
put(dataType1, dataId1)
put(dataType2, dataId2)
},
businessKey = "business-4",
), metaData = MetaData.emptyInstance()
)
}

@AfterEach
Expand Down Expand Up @@ -255,6 +273,10 @@ internal class JpaPolyflowViewServiceTaskITest {
assertThat(zoro.elements[0].task.name).isEqualTo("task name 4")
assertThat(zoro.elements[0].dataEntries).isNotEmpty.hasSize(1)
assertThat(zoro.elements[0].dataEntries[0].entryId).isEqualTo(dataId2)
assertThat(zoro.elements[0].task.correlations).containsOnly(
MapEntry.entry("io.polyflow.test1", dataId1),
MapEntry.entry("io.polyflow.test2", dataId2)
)

val strawhats = jpaPolyflowViewService.query(TasksWithDataEntriesForUserQuery(user = User("other", setOf("strawhats")), assignedToMeOnly = false))
assertThat(strawhats.elements).isNotEmpty.hasSize(2)
Expand Down Expand Up @@ -479,7 +501,7 @@ internal class JpaPolyflowViewServiceTaskITest {
@Test
fun `query updates are sent`() {
captureEmittedQueryUpdates()
assertThat(emittedQueryUpdates).hasSize(36)
assertThat(emittedQueryUpdates).hasSize(41)

assertThat(emittedQueryUpdates.filter { it.queryType == TaskForIdQuery::class.java && it.asTask().id == id }).hasSize(2)
assertThat(emittedQueryUpdates.filter { it.queryType == TaskForIdQuery::class.java && it.asTask().id == id2 }).hasSize(2)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.holunda.polyflow.view.jpa.task

import com.fasterxml.jackson.databind.ObjectMapper
import io.holunda.camunda.taskpool.api.task.ProcessReference
import io.holunda.polyflow.view.jpa.data.DataEntryId
import io.holunda.polyflow.view.jpa.payload.PayloadAttribute
import io.holunda.polyflow.view.jpa.process.toSourceReferenceEmbeddable
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.data.MapEntry
import org.junit.jupiter.api.Test

class ConverterExtKtTest {

@Test
fun `should convert to task`() {
val objectMapper = ObjectMapper()
val entity = TaskEntity(
taskId = "taskId",
taskDefinitionKey = "taskDefinitionKey",
name = "name",
priority = 50,
sourceReference = ProcessReference(
instanceId = "instanceId",
executionId = "executionId",
definitionId = "definitionId",
definitionKey = "definitionKey",
name = "name",
applicationName = "applicationName",
).toSourceReferenceEmbeddable(),
authorizedPrincipals = mutableSetOf("GROUP:FOO", "GROUP:BAR"),
correlations = mutableSetOf(DataEntryId("1", "foo"), DataEntryId("2", "bar")),
payloadAttributes = mutableSetOf(PayloadAttribute("foo", "bar")),
payload = """
{"foo": "bar" }
""".trimIndent(),
formKey = "formKey",
)

val task = entity.toTask(objectMapper)

assertThat(task.id).isEqualTo("taskId")
assertThat(task.taskDefinitionKey).isEqualTo("taskDefinitionKey")
assertThat(task.name).isEqualTo("name")
assertThat(task.priority).isEqualTo(50)
assertThat(task.sourceReference.instanceId).isEqualTo("instanceId")
assertThat(task.sourceReference.executionId).isEqualTo("executionId")
assertThat(task.sourceReference.definitionId).isEqualTo("definitionId")
assertThat(task.sourceReference.definitionKey).isEqualTo("definitionKey")
assertThat(task.sourceReference.name).isEqualTo("name")
assertThat(task.sourceReference.applicationName).isEqualTo("applicationName")
assertThat(task.candidateGroups).containsExactlyInAnyOrder("FOO", "BAR")
assertThat(task.correlations).containsOnly(MapEntry.entry("bar", "2"), MapEntry.entry("foo", "1"))
assertThat(task.payload).containsOnly(MapEntry.entry("foo", "bar"))
assertThat(task.formKey).isEqualTo("formKey")

}
}

0 comments on commit c69e53c

Please sign in to comment.