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 b22a2f8f5c..b995ccb2b5 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 static String determineChangesInAttributes(T oldObject, T newObject) // 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 847c8e354c..3e9be7e389 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 0000000000..8c70d30229 --- /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 bae80a8677..3647521735 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.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 @@ void should_DeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionEnabled() t 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 @@ void should_DeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionEnabled() 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 @@ void should_NotDeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionDisabled listEvents = taskHistoryQueryMapper.queryHistoryEvents( (TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskId)); - assertThat(listEvents).hasSize(2); + assertThat(listEvents).hasSize(3); } @Test @@ -152,7 +156,7 @@ void should_NotDeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionDisabl 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 0000000000..4010aa6789 --- /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 6ed98104d2..96576bb4a8 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 951ab168e8..2ee0394554 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.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 BulkOperationResults deleteTasks(List t .isDeleteHistoryEventsOnTaskDeletionEnabled()) { historyEventManager.deleteEvents(taskIds); } + if (historyEventManager.isEnabled()) { + taskIds.forEach(this::createTaskDeletedEvent); + } } return bulkLog; } finally { @@ -1709,6 +1713,10 @@ private void deleteTask(String taskId, boolean forceDelete) historyEventManager.deleteEvents(Collections.singletonList(taskId)); } + if (historyEventManager.isEnabled()) { + createTaskDeletedEvent(taskId); + } + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Task {} deleted.", taskId); } @@ -2241,6 +2249,15 @@ private void createTasksCompletedEvents(List taskSummarie 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());