Repository: holunda-io/camunda-bpm-taskpool
Issue: taskService.saveTask() without changes causes NoHandlerForCommandException 

### Steps to reproduce

* camunda-bpm-taskpool version:
* Camunda BPM version: 3.8.1
* JDK version:  11
* Operating system: MacOS
* Complete executable reproducer: consider the following test method added to `TaskCollectorITest`:
  ```kotlin
  @Test
  fun `updates attributes via API with update listener when nothing changed`() {

    // deploy
    driver.deployProcess(
      createUserTaskProcess(
        taskListeners = listOf(
          "update" to "#{changeTaskAttributes}"
        )
      )
    )

    // start
    val instance = driver.startProcessInstance()
    driver.assertProcessInstanceWaitsInUserTask(instance)
    verify(commandListGateway).sendToGateway(listOf(createTaskCommand()))

    // trigger the update event on the task
    taskService.saveTask(task())

    val updateCommand = updateTaskCommand()
    verify(commandListGateway).sendToGateway(listOf(updateCommand))

    verifyNoMoreInteractions(commandListGateway)
  }
  ```

This is a slight variation on `updates attributes via API with update listener` where nothing is actually changed in the task. This might seem futile but in our setup, it is used to force a task update after process variables have changed.

The command sent to the gateway is not an `UpdateAttributeTaskCommand` but an `UpdateAttributesHistoricTaskCommand`. From that class's description: `This command has no command handler in the core and is never sent to it.`
It is not intended to be sent to the gateway but should be merged into other commands by the accumulator, which doesn't happen if it's the only command. 

So the problem is that without changes to the task, no `UpdateAttributeTaskCommand` is created and thus the `UpdateAttributesHistoricTaskCommand` stands alone where it never should.

* Steps: In a real-world setup, you can reproduce this by calling `taskService.saveTask()` with an original, unchanged task.

### Expected behaviour

Calling saveTask() with no changes to the task itself should create an `UpdateAttributeTaskCommand` enriched with the current process variables.

### Actual behaviour

An `UpdateAttributesHistoricTaskCommand` is issued which cannot be processed and an exception is thrown:
```
org.axonframework.commandhandling.NoHandlerForCommandException: No handler was subscribed for command [io.holunda.camunda.taskpool.api.task.UpdateAttributesHistoricTaskCommand].
```


Changed files:
integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt

diff --git a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
index cd37440fc..9f5776ba9 100755
--- a/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/main/kotlin/io/holunda/polyflow/taskpool/collector/task/TaskEventCollectorService.kt
@@ -107,8 +107,8 @@ class TaskEventCollectorService(
   @EventListener(condition = "#task.eventName.equals('update')")
   fun update(task: DelegateTask): UpdateAttributeTaskCommand? =
     if (task is TaskEntity) {
-      if (task.isAssigneeChange() || !task.hasChangedProperties()) {
-        // this is already handled by assignment event, or it is an empty fired during taskService call of candidate update.
+      if (task.isAssigneeChange()) {
+        // this is already handled by assignment event
         null
       } else {
         task.toUpdateCommand(camundaTaskpoolCollectorProperties.applicationName)
diff --git a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
index 00c13ed42..f7c78c0a4 100644
--- a/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
+++ b/integration/camunda-bpm/taskpool-collector/src/test/kotlin/io/holunda/polyflow/taskpool/itest/tx/TaskCollectorITest.kt
@@ -366,6 +366,36 @@ internal class TaskCollectorITest {
     verifyNoMoreInteractions(commandListGateway)
   }
 
+  @Test
+  fun `updates attributes via API with update listener when task is unchanged`() {
+
+    // deploy
+    driver.deployProcess(
+      createUserTaskProcess(
+        taskListeners = listOf(
+          "update" to "#{changeTaskAttributes}"
+        )
+      )
+    )
+
+    // start
+    val instance = driver.startProcessInstance()
+    driver.assertProcessInstanceWaitsInUserTask(instance)
+    verify(commandListGateway).sendToGateway(listOf(createTaskCommand()))
+
+    // trigger the update event on the task
+    runtimeService.setVariable(instance.processInstanceId, "bla", "blubb")
+    taskService.saveTask(task())
+
+    val updateCommand = updateTaskCommand(variables = Variables.createVariables().apply {
+      putAll(DEFAULT_VARIABLES)
+      putValue("bla", "blubb")
+    })
+    verify(commandListGateway).sendToGateway(listOf(updateCommand))
+
+    verifyNoMoreInteractions(commandListGateway)
+  }
+
   /**
    * The process is started and wait in a user task.
    * The update command is send after the TX commit.