Skip to content

Commit e3d6f94

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

File tree

7 files changed

+232
-7
lines changed

7 files changed

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

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> deletedTasks =
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+
deletedTasks.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)