Skip to content

Commit c44f44d

Browse files
committed
Closes #2378 - Add DeleteHistoryEvent for Task
1 parent 1abf68e commit c44f44d

File tree

7 files changed

+242
-7
lines changed

7 files changed

+242
-7
lines changed

common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public static <T> String determineChangesInAttributes(T oldObject, T newObject)
4040

4141
// this has to be checked after we deal with List data types, because
4242
// we want to allow different implementations of the List interface to work as well.
43-
if (!oldObject.getClass().equals(newObject.getClass())) {
43+
if (!oldObject.getClass().equals(newObject.getClass())
44+
&& !oldObject.getClass().isAssignableFrom(newObject.getClass())) {
4445
throw new SystemException(
4546
String.format(
4647
"The classes differ between the oldObject(%s) and newObject(%s). "

history/taskana-simplehistory-provider/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@
9191
<version>${version.oracle}</version>
9292
<scope>test</scope>
9393
</dependency>
94-
94+
<dependency>
95+
<groupId>pro.taskana</groupId>
96+
<artifactId>taskana-test-api</artifactId>
97+
<version>${project.version}</version>
98+
<scope>test</scope>
99+
</dependency>
95100
</dependencies>
96101

97102
</project>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package acceptance.events.task;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.lang.reflect.Field;
6+
import java.util.List;
7+
import java.util.stream.IntStream;
8+
import org.apache.ibatis.session.SqlSessionManager;
9+
import org.json.JSONArray;
10+
import org.json.JSONObject;
11+
import org.junit.jupiter.api.BeforeAll;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.ExtendWith;
14+
import pro.taskana.classification.api.ClassificationService;
15+
import pro.taskana.classification.api.models.ClassificationSummary;
16+
import pro.taskana.common.api.TaskanaEngine;
17+
import pro.taskana.common.test.security.JaasExtension;
18+
import pro.taskana.common.test.security.WithAccessId;
19+
import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
20+
import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl;
21+
import pro.taskana.simplehistory.impl.TaskanaHistoryEngineImpl;
22+
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
23+
import pro.taskana.spi.history.api.TaskanaHistory;
24+
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
25+
import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
26+
import pro.taskana.task.api.TaskService;
27+
import pro.taskana.task.api.TaskState;
28+
import pro.taskana.task.api.models.Task;
29+
import pro.taskana.testapi.DefaultTestEntities;
30+
import pro.taskana.testapi.TaskanaInject;
31+
import pro.taskana.testapi.TaskanaIntegrationTest;
32+
import pro.taskana.testapi.WithServiceProvider;
33+
import pro.taskana.testapi.builder.TaskBuilder;
34+
import pro.taskana.workbasket.api.WorkbasketService;
35+
import pro.taskana.workbasket.api.models.WorkbasketSummary;
36+
37+
@WithServiceProvider(
38+
serviceProviderInterface = TaskanaHistory.class,
39+
serviceProviders = SimpleHistoryServiceImpl.class)
40+
@TaskanaIntegrationTest
41+
@ExtendWith(JaasExtension.class)
42+
class CreateHistoryEventOnTaskDeletionAccTest {
43+
@TaskanaInject TaskanaEngine taskanaEngine;
44+
@TaskanaInject TaskService taskService;
45+
@TaskanaInject WorkbasketService workbasketService;
46+
@TaskanaInject ClassificationService classificationService;
47+
ClassificationSummary defaultClassificationSummary;
48+
WorkbasketSummary defaultWorkbasketSummary;
49+
Task task1;
50+
Task task2;
51+
Task task3;
52+
Task task4;
53+
private SimpleHistoryServiceImpl historyService = new SimpleHistoryServiceImpl();
54+
private TaskanaHistoryEngineImpl taskanaHistoryEngine;
55+
56+
@WithAccessId(user = "admin")
57+
@BeforeAll
58+
void setUp() throws Exception {
59+
taskanaHistoryEngine = TaskanaHistoryEngineImpl.createTaskanaEngine(taskanaEngine);
60+
historyService.initialize(taskanaEngine);
61+
62+
defaultClassificationSummary =
63+
DefaultTestEntities.defaultTestClassification()
64+
.buildAndStoreAsSummary(classificationService);
65+
defaultWorkbasketSummary =
66+
DefaultTestEntities.defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);
67+
68+
task1 = createTask().buildAndStore(taskService);
69+
task2 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
70+
task3 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
71+
task4 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
72+
}
73+
74+
@WithAccessId(user = "admin")
75+
@Test
76+
void should_CreateDeleteHistoryEvent_When_TaskIsNormallyDeleted() throws Exception {
77+
historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
78+
TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper();
79+
80+
taskService.deleteTask(task4.getId());
81+
82+
List<TaskHistoryEvent> events =
83+
taskHistoryQueryMapper.queryHistoryEvents(
84+
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task4.getId()));
85+
assertThat(events).hasSize(1);
86+
assertDeleteHistoryEvent(
87+
events.get(0).getId(), TaskState.COMPLETED.toString(), "DELETED", "admin");
88+
}
89+
90+
@WithAccessId(user = "admin")
91+
@Test
92+
void should_CreateDeleteHistoryEvent_When_TaskIsForceDeleted() throws Exception {
93+
historyService.deleteHistoryEventsByTaskIds(List.of(task1.getId()));
94+
TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper();
95+
96+
taskService.forceDeleteTask(task1.getId());
97+
98+
List<TaskHistoryEvent> events =
99+
taskHistoryQueryMapper.queryHistoryEvents(
100+
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(task1.getId()));
101+
assertThat(events).hasSize(1);
102+
assertDeleteHistoryEvent(events.get(0).getId(), TaskState.READY.toString(), "DELETED", "admin");
103+
}
104+
105+
@WithAccessId(user = "admin")
106+
@Test
107+
void should_CreateDeleteHistoryEvents_When_MultipleTasksAreDeleted() throws Exception {
108+
List<String> taskIds = List.of(task2.getId(), task3.getId());
109+
historyService.deleteHistoryEventsByTaskIds(taskIds);
110+
TaskHistoryQueryMapper taskHistoryQueryMapper = getHistoryQueryMapper();
111+
112+
taskService.deleteTasks(taskIds);
113+
114+
List<TaskHistoryEvent> events =
115+
taskHistoryQueryMapper.queryHistoryEvents(
116+
(TaskHistoryQueryImpl)
117+
historyService.createTaskHistoryQuery().taskIdIn(taskIds.toArray(new String[0])));
118+
assertThat(events)
119+
.extracting(TaskHistoryEvent::getTaskId)
120+
.containsExactlyInAnyOrderElementsOf(taskIds);
121+
for (TaskHistoryEvent event : events) {
122+
assertDeleteHistoryEvent(event.getId(), TaskState.COMPLETED.toString(), "DELETED", "admin");
123+
}
124+
}
125+
126+
TaskHistoryQueryMapper getHistoryQueryMapper()
127+
throws NoSuchFieldException, IllegalAccessException {
128+
Field sessionManagerField = TaskanaHistoryEngineImpl.class.getDeclaredField("sessionManager");
129+
sessionManagerField.setAccessible(true);
130+
SqlSessionManager sqlSessionManager =
131+
(SqlSessionManager) sessionManagerField.get(taskanaHistoryEngine);
132+
133+
return sqlSessionManager.getMapper(TaskHistoryQueryMapper.class);
134+
}
135+
136+
private void assertDeleteHistoryEvent(
137+
String eventId, String expectedOldTaskState, String expectedNewTaskState, String expectedUser)
138+
throws Exception {
139+
TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId);
140+
assertThat(event.getDetails()).isNotNull();
141+
JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes");
142+
assertThat(changes.length()).isPositive();
143+
144+
JSONObject stateChange =
145+
IntStream.range(0, changes.length())
146+
.mapToObj(changes::getJSONObject)
147+
.filter(obj -> "state".equals(obj.optString("fieldName")))
148+
.findFirst()
149+
.orElse(null);
150+
String oldState = stateChange.get("oldValue").toString();
151+
String newState = stateChange.get("newValue").toString();
152+
assertThat(oldState).isEqualTo(expectedOldTaskState);
153+
assertThat(newState).isEqualTo(expectedNewTaskState);
154+
155+
assertThat(event.getOldValue()).isEqualTo(expectedOldTaskState);
156+
assertThat(event.getNewValue()).isEqualTo(expectedNewTaskState);
157+
assertThat(event.getUserId()).isEqualTo(expectedUser);
158+
assertThat(event.getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
159+
}
160+
161+
private TaskBuilder createTask() {
162+
return TaskBuilder.newTask()
163+
.classificationSummary(defaultClassificationSummary)
164+
.workbasketSummary(defaultWorkbasketSummary)
165+
.primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build());
166+
}
167+
}

history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl;
1616
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
1717
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
18+
import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
1819
import pro.taskana.task.api.exceptions.TaskNotFoundException;
1920

2021
@ExtendWith(JaasExtension.class)
@@ -47,7 +48,8 @@ void should_DeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionEnabled() t
4748
listEvents =
4849
taskHistoryQueryMapper.queryHistoryEvents(
4950
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskid));
50-
assertThat(listEvents).isEmpty();
51+
assertThat(listEvents).hasSize(1);
52+
assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
5153
}
5254

5355
@Test
@@ -87,7 +89,9 @@ void should_DeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionEnabled()
8789
taskHistoryQueryMapper.queryHistoryEvents(
8890
(TaskHistoryQueryImpl)
8991
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
90-
assertThat(listEvents).isEmpty();
92+
assertThat(listEvents).hasSize(2);
93+
assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
94+
assertThat(listEvents.get(1).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
9195
}
9296

9397
@Test
@@ -119,7 +123,7 @@ void should_NotDeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionDisabled
119123
listEvents =
120124
taskHistoryQueryMapper.queryHistoryEvents(
121125
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskId));
122-
assertThat(listEvents).hasSize(2);
126+
assertThat(listEvents).hasSize(3);
123127
}
124128

125129
@Test
@@ -152,7 +156,7 @@ void should_NotDeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionDisabl
152156
taskHistoryQueryMapper.queryHistoryEvents(
153157
(TaskHistoryQueryImpl)
154158
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
155-
assertThat(listEvents).hasSize(2);
159+
assertThat(listEvents).hasSize(4);
156160
}
157161

158162
private void createTaskanaEngineWithNewConfig(boolean deleteHistoryOnTaskDeletionEnabled)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package pro.taskana.spi.history.api.events.task;
2+
3+
import java.time.Instant;
4+
import pro.taskana.task.api.models.TaskSummary;
5+
6+
public class TaskDeletedEvent extends TaskHistoryEvent {
7+
8+
public TaskDeletedEvent(
9+
String id,
10+
TaskSummary taskSummary,
11+
String oldState,
12+
String newState,
13+
String userId,
14+
String details) {
15+
super(id, taskSummary, userId, details);
16+
eventType = TaskHistoryEventType.DELETED.getName();
17+
created = Instant.now();
18+
this.oldValue = oldState;
19+
this.newValue = newState;
20+
}
21+
}

lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ public enum TaskHistoryEventType {
1010
COMPLETED("COMPLETED"),
1111
CANCELLED("CANCELLED"),
1212
TERMINATED("TERMINATED"),
13-
TRANSFERRED("TRANSFERRED");
13+
TRANSFERRED("TRANSFERRED"),
14+
DELETED("DELETED");
1415

1516
private String name;
1617

lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.stream.Collectors;
2222
import java.util.stream.Stream;
2323
import org.apache.ibatis.exceptions.PersistenceException;
24+
import org.json.JSONArray;
25+
import org.json.JSONObject;
2426
import org.slf4j.Logger;
2527
import org.slf4j.LoggerFactory;
2628
import pro.taskana.classification.api.ClassificationService;
@@ -46,6 +48,7 @@
4648
import pro.taskana.spi.history.api.events.task.TaskClaimedEvent;
4749
import pro.taskana.spi.history.api.events.task.TaskCompletedEvent;
4850
import pro.taskana.spi.history.api.events.task.TaskCreatedEvent;
51+
import pro.taskana.spi.history.api.events.task.TaskDeletedEvent;
4952
import pro.taskana.spi.history.api.events.task.TaskRequestChangesEvent;
5053
import pro.taskana.spi.history.api.events.task.TaskRequestReviewEvent;
5154
import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
@@ -636,6 +639,8 @@ public BulkOperationResults<String, TaskanaException> deleteTasks(List<String> t
636639
}
637640

638641
if (!taskIds.isEmpty()) {
642+
List<TaskSummary> tasksToDelete =
643+
createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
639644
attachmentMapper.deleteMultipleByTaskIds(taskIds);
640645
objectReferenceMapper.deleteMultipleByTaskIds(taskIds);
641646
taskMapper.deleteMultiple(taskIds);
@@ -647,6 +652,9 @@ public BulkOperationResults<String, TaskanaException> deleteTasks(List<String> t
647652
.isDeleteHistoryEventsOnTaskDeletionEnabled()) {
648653
historyEventManager.deleteEvents(taskIds);
649654
}
655+
if (historyEventManager.isEnabled()) {
656+
tasksToDelete.forEach(this::createTaskDeletedEvent);
657+
}
650658
}
651659
return bulkLog;
652660
} finally {
@@ -1627,6 +1635,10 @@ private void deleteTask(String taskId, boolean forceDelete)
16271635
historyEventManager.deleteEvents(Collections.singletonList(taskId));
16281636
}
16291637

1638+
if (historyEventManager.isEnabled()) {
1639+
createTaskDeletedEvent(task.asSummary());
1640+
}
1641+
16301642
if (LOGGER.isDebugEnabled()) {
16311643
LOGGER.debug("Task {} deleted.", taskId);
16321644
}
@@ -2157,6 +2169,30 @@ private void createTasksCompletedEvents(List<? extends TaskSummary> taskSummarie
21572169
taskanaEngine.getEngine().getCurrentUserContext().getUserid())));
21582170
}
21592171

2172+
private void createTaskDeletedEvent(TaskSummary taskSummary) {
2173+
TaskSummaryImpl emptyTaskSummary = new TaskSummaryImpl();
2174+
String changeDetails =
2175+
ObjectAttributeChangeDetector.determineChangesInAttributes(taskSummary, emptyTaskSummary);
2176+
JSONObject jsonObject = new JSONObject(changeDetails);
2177+
JSONArray changesArray = jsonObject.getJSONArray("changes");
2178+
for (int i = 0; i < changesArray.length(); i++) {
2179+
JSONObject changeObject = changesArray.getJSONObject(i);
2180+
2181+
if (changeObject.getString("fieldName").equals("state")) {
2182+
changeObject.put("newValue", "DELETED");
2183+
}
2184+
}
2185+
String modifiedChangeDetails = jsonObject.toString();
2186+
historyEventManager.createEvent(
2187+
new TaskDeletedEvent(
2188+
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
2189+
taskSummary,
2190+
taskSummary.getState().toString(),
2191+
"DELETED",
2192+
taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
2193+
modifiedChangeDetails));
2194+
}
2195+
21602196
private TaskImpl duplicateTaskExactly(TaskImpl task) {
21612197
TaskImpl oldTask = task.copy();
21622198
oldTask.setId(task.getId());

0 commit comments

Comments
 (0)