diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java
index b22a2f8f5..b995ccb2b 100644
--- a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java
+++ b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java
@@ -40,7 +40,8 @@ public class ObjectAttributeChangeDetector {
// this has to be checked after we deal with List data types, because
// we want to allow different implementations of the List interface to work as well.
- if (!oldObject.getClass().equals(newObject.getClass())) {
+ if (!oldObject.getClass().equals(newObject.getClass())
+ && !oldObject.getClass().isAssignableFrom(newObject.getClass())) {
throw new SystemException(
String.format(
"The classes differ between the oldObject(%s) and newObject(%s). "
diff --git a/history/taskana-simplehistory-provider/pom.xml b/history/taskana-simplehistory-provider/pom.xml
index 847c8e354..3e9be7e38 100644
--- a/history/taskana-simplehistory-provider/pom.xml
+++ b/history/taskana-simplehistory-provider/pom.xml
@@ -91,7 +91,12 @@
${version.oracle}
test
-
+
+ pro.taskana
+ taskana-test-api
+ ${project.version}
+ test
+
diff --git a/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskDeletionAccTest.java b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskDeletionAccTest.java
new file mode 100644
index 000000000..8c70d3022
--- /dev/null
+++ b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/CreateHistoryEventOnTaskDeletionAccTest.java
@@ -0,0 +1,121 @@
+package acceptance.events.task;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import pro.taskana.classification.api.ClassificationService;
+import pro.taskana.classification.api.models.ClassificationSummary;
+import pro.taskana.common.api.TaskanaEngine;
+import pro.taskana.common.test.security.JaasExtension;
+import pro.taskana.common.test.security.WithAccessId;
+import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
+import pro.taskana.spi.history.api.TaskanaHistory;
+import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
+import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
+import pro.taskana.task.api.TaskService;
+import pro.taskana.task.api.TaskState;
+import pro.taskana.task.api.models.Task;
+import pro.taskana.testapi.DefaultTestEntities;
+import pro.taskana.testapi.TaskanaInject;
+import pro.taskana.testapi.TaskanaIntegrationTest;
+import pro.taskana.testapi.WithServiceProvider;
+import pro.taskana.testapi.builder.TaskBuilder;
+import pro.taskana.workbasket.api.WorkbasketService;
+import pro.taskana.workbasket.api.models.WorkbasketSummary;
+
+@WithServiceProvider(
+ serviceProviderInterface = TaskanaHistory.class,
+ serviceProviders = SimpleHistoryServiceImpl.class)
+@TaskanaIntegrationTest
+@ExtendWith(JaasExtension.class)
+class CreateHistoryEventOnTaskDeletionAccTest {
+ @TaskanaInject TaskanaEngine taskanaEngine;
+ @TaskanaInject TaskService taskService;
+ @TaskanaInject WorkbasketService workbasketService;
+ @TaskanaInject ClassificationService classificationService;
+ ClassificationSummary defaultClassificationSummary;
+ WorkbasketSummary defaultWorkbasketSummary;
+ Task task1;
+ Task task2;
+ Task task3;
+ Task task4;
+ SimpleHistoryServiceImpl historyService;
+
+ @WithAccessId(user = "admin")
+ @BeforeAll
+ void setUp() throws Exception {
+ historyService = new SimpleHistoryServiceImpl();
+ historyService.initialize(taskanaEngine);
+
+ defaultClassificationSummary =
+ DefaultTestEntities.defaultTestClassification()
+ .buildAndStoreAsSummary(classificationService);
+ defaultWorkbasketSummary =
+ DefaultTestEntities.defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);
+
+ task1 = createTask().buildAndStore(taskService);
+ task2 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
+ task3 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
+ task4 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
+ }
+
+ @WithAccessId(user = "admin")
+ @Test
+ void should_CreateDeleteHistoryEvent_When_TaskIsDeleted() throws Exception {
+ historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
+
+ taskService.deleteTask(task4.getId());
+
+ List events =
+ historyService.createTaskHistoryQuery().taskIdIn(task4.getId()).list();
+ assertThat(events).hasSize(1);
+ assertDeleteHistoryEvent(events.get(0).getId(), "admin", task4.getId());
+ }
+
+ @WithAccessId(user = "admin")
+ @Test
+ void should_CreateDeleteHistoryEvent_When_TaskIsForceDeleted() throws Exception {
+ historyService.deleteHistoryEventsByTaskIds(List.of(task1.getId()));
+
+ taskService.forceDeleteTask(task1.getId());
+
+ List events =
+ historyService.createTaskHistoryQuery().taskIdIn(task1.getId()).list();
+ assertThat(events).hasSize(1);
+ assertDeleteHistoryEvent(events.get(0).getId(), "admin", task1.getId());
+ }
+
+ @WithAccessId(user = "admin")
+ @Test
+ void should_CreateDeleteHistoryEvents_When_MultipleTasksAreDeleted() throws Exception {
+ List taskIds = List.of(task2.getId(), task3.getId());
+ historyService.deleteHistoryEventsByTaskIds(taskIds);
+
+ taskService.deleteTasks(taskIds);
+
+ TaskHistoryEvent eventTask2 =
+ historyService.createTaskHistoryQuery().taskIdIn(task2.getId()).single();
+ TaskHistoryEvent eventTask3 =
+ historyService.createTaskHistoryQuery().taskIdIn(task3.getId()).single();
+ assertDeleteHistoryEvent(eventTask2.getId(), "admin", task2.getId());
+ assertDeleteHistoryEvent(eventTask3.getId(), "admin", task3.getId());
+ }
+
+ private void assertDeleteHistoryEvent(String eventId, String expectedUser, String taskId)
+ throws Exception {
+ TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId);
+ assertThat(event.getUserId()).isEqualTo(expectedUser);
+ assertThat(event.getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
+ assertThat(event.getTaskId()).isEqualTo(taskId);
+ }
+
+ private TaskBuilder createTask() {
+ return TaskBuilder.newTask()
+ .classificationSummary(defaultClassificationSummary)
+ .workbasketSummary(defaultWorkbasketSummary)
+ .primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build());
+ }
+}
diff --git a/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java
index bae80a867..364752173 100644
--- a/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java
+++ b/history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java
@@ -15,6 +15,7 @@ import pro.taskana.common.test.security.WithAccessId;
import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl;
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
+import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
import pro.taskana.task.api.exceptions.TaskNotFoundException;
@ExtendWith(JaasExtension.class)
@@ -47,7 +48,8 @@ class DeleteHistoryEventsOnTaskDeletionAccTest extends AbstractAccTest {
listEvents =
taskHistoryQueryMapper.queryHistoryEvents(
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskid));
- assertThat(listEvents).isEmpty();
+ assertThat(listEvents).hasSize(1);
+ assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
}
@Test
@@ -87,7 +89,9 @@ class DeleteHistoryEventsOnTaskDeletionAccTest extends AbstractAccTest {
taskHistoryQueryMapper.queryHistoryEvents(
(TaskHistoryQueryImpl)
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
- assertThat(listEvents).isEmpty();
+ assertThat(listEvents).hasSize(2);
+ assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
+ assertThat(listEvents.get(1).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
}
@Test
@@ -119,7 +123,7 @@ class DeleteHistoryEventsOnTaskDeletionAccTest extends AbstractAccTest {
listEvents =
taskHistoryQueryMapper.queryHistoryEvents(
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskId));
- assertThat(listEvents).hasSize(2);
+ assertThat(listEvents).hasSize(3);
}
@Test
@@ -152,7 +156,7 @@ class DeleteHistoryEventsOnTaskDeletionAccTest extends AbstractAccTest {
taskHistoryQueryMapper.queryHistoryEvents(
(TaskHistoryQueryImpl)
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
- assertThat(listEvents).hasSize(2);
+ assertThat(listEvents).hasSize(4);
}
private void createTaskanaEngineWithNewConfig(boolean deleteHistoryOnTaskDeletionEnabled)
diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskDeletedEvent.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskDeletedEvent.java
new file mode 100644
index 000000000..4010aa678
--- /dev/null
+++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskDeletedEvent.java
@@ -0,0 +1,18 @@
+package pro.taskana.spi.history.api.events.task;
+
+import java.time.Instant;
+import pro.taskana.task.api.models.TaskSummary;
+
+public class TaskDeletedEvent extends TaskHistoryEvent {
+
+ public TaskDeletedEvent(
+ String id,
+ TaskSummary taskSummary,
+ String taskId,
+ String userId) {
+ super(id, taskSummary, userId, null);
+ eventType = TaskHistoryEventType.DELETED.getName();
+ created = Instant.now();
+ super.taskId = taskId;
+ }
+}
diff --git a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
index 6ed98104d..96576bb4a 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java
@@ -10,7 +10,8 @@ public enum TaskHistoryEventType {
COMPLETED("COMPLETED"),
CANCELLED("CANCELLED"),
TERMINATED("TERMINATED"),
- TRANSFERRED("TRANSFERRED");
+ TRANSFERRED("TRANSFERRED"),
+ DELETED("DELETED");
private String name;
diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java
index 951ab168e..2ee039455 100644
--- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java
+++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java
@@ -46,6 +46,7 @@ import pro.taskana.spi.history.api.events.task.TaskClaimCancelledEvent;
import pro.taskana.spi.history.api.events.task.TaskClaimedEvent;
import pro.taskana.spi.history.api.events.task.TaskCompletedEvent;
import pro.taskana.spi.history.api.events.task.TaskCreatedEvent;
+import pro.taskana.spi.history.api.events.task.TaskDeletedEvent;
import pro.taskana.spi.history.api.events.task.TaskRequestChangesEvent;
import pro.taskana.spi.history.api.events.task.TaskRequestReviewEvent;
import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
@@ -704,6 +705,9 @@ public class TaskServiceImpl implements TaskService {
.isDeleteHistoryEventsOnTaskDeletionEnabled()) {
historyEventManager.deleteEvents(taskIds);
}
+ if (historyEventManager.isEnabled()) {
+ taskIds.forEach(this::createTaskDeletedEvent);
+ }
}
return bulkLog;
} finally {
@@ -1709,6 +1713,10 @@ public class TaskServiceImpl implements TaskService {
historyEventManager.deleteEvents(Collections.singletonList(taskId));
}
+ if (historyEventManager.isEnabled()) {
+ createTaskDeletedEvent(taskId);
+ }
+
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Task {} deleted.", taskId);
}
@@ -2241,6 +2249,15 @@ public class TaskServiceImpl implements TaskService {
taskanaEngine.getEngine().getCurrentUserContext().getUserid())));
}
+ private void createTaskDeletedEvent(String taskId) {
+ historyEventManager.createEvent(
+ new TaskDeletedEvent(
+ IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
+ newTask().asSummary(),
+ taskId,
+ taskanaEngine.getEngine().getCurrentUserContext().getUserid()));
+ }
+
private TaskImpl duplicateTaskExactly(TaskImpl task) {
TaskImpl oldTask = task.copy();
oldTask.setId(task.getId());