Skip to content

Commit c385eda

Browse files
store identity links for instance, task and activity history level
1 parent 8bcfecb commit c385eda

7 files changed

Lines changed: 444 additions & 2 deletions

File tree

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/DefaultCmmnHistoryConfigurationSettings.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ public boolean isHistoryEnabledForVariableInstance(VariableInstanceEntity variab
222222
@Override
223223
public boolean isHistoryEnabledForIdentityLink(IdentityLinkEntity identityLinkEntity) {
224224
String caseDefinitionId = getCaseDefinitionId(identityLinkEntity);
225-
return isHistoryLevelAtLeast(HistoryLevel.AUDIT, caseDefinitionId);
225+
if (identityLinkEntity.getTaskId() != null) {
226+
return isHistoryLevelAtLeast(HistoryLevel.TASK, caseDefinitionId);
227+
}
228+
return isHistoryLevelAtLeast(HistoryLevel.INSTANCE, caseDefinitionId);
226229
}
227230

228231
protected String getCaseDefinitionId(IdentityLinkEntity identityLink) {
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/* Licensed under the Apache License, Version 2.0 (the "License");
2+
* you may not use this file except in compliance with the License.
3+
* You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
package org.flowable.cmmn.test.history;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
17+
import java.util.List;
18+
19+
import org.flowable.cmmn.api.runtime.CaseInstance;
20+
import org.flowable.cmmn.engine.test.CmmnDeployment;
21+
import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper;
22+
import org.flowable.cmmn.test.FlowableCmmnTestCase;
23+
import org.flowable.common.engine.impl.identity.Authentication;
24+
import org.flowable.identitylink.api.IdentityLinkType;
25+
import org.flowable.identitylink.api.history.HistoricIdentityLink;
26+
import org.flowable.identitylink.service.HistoricIdentityLinkService;
27+
import org.flowable.identitylink.service.impl.persistence.entity.HistoricIdentityLinkEntity;
28+
import org.flowable.task.api.Task;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.Test;
31+
32+
public class HistoryLevelIdentityLinkTest extends FlowableCmmnTestCase {
33+
34+
@AfterEach
35+
public void tearDown() {
36+
Authentication.setAuthenticatedUserId(null);
37+
}
38+
39+
@Test
40+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/oneHumanTaskHistoryLevelInstance.cmmn")
41+
public void testInstanceHistoryLevelStoresCaseInstanceIdentityLinks() {
42+
Authentication.setAuthenticatedUserId("johndoe");
43+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
44+
.caseDefinitionKey("oneTaskCase")
45+
.start();
46+
47+
cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "participant1", IdentityLinkType.PARTICIPANT);
48+
49+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
50+
51+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
52+
assertThat(caseIdentityLinks).hasSize(2);
53+
assertThat(caseIdentityLinks)
54+
.extracting(HistoricIdentityLink::getType)
55+
.containsExactlyInAnyOrder(IdentityLinkType.STARTER, IdentityLinkType.PARTICIPANT);
56+
assertThat(caseIdentityLinks)
57+
.extracting(HistoricIdentityLink::getUserId)
58+
.containsExactlyInAnyOrder("johndoe", "participant1");
59+
60+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
61+
cmmnTaskService.complete(task.getId());
62+
}
63+
64+
@Test
65+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/oneHumanTaskHistoryLevelInstance.cmmn")
66+
public void testInstanceHistoryLevelDoesNotStoreTaskIdentityLinks() {
67+
Authentication.setAuthenticatedUserId("johndoe");
68+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
69+
.caseDefinitionKey("oneTaskCase")
70+
.start();
71+
72+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
73+
cmmnTaskService.addUserIdentityLink(task.getId(), "candidateUser1", IdentityLinkType.CANDIDATE);
74+
cmmnTaskService.addGroupIdentityLink(task.getId(), "candidateGroup1", IdentityLinkType.CANDIDATE);
75+
76+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
77+
78+
// At instance level, task identity links should not be stored,
79+
// but the interceptor creates participant links on the case instance for candidate users
80+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
81+
assertThat(caseIdentityLinks)
82+
.extracting(HistoricIdentityLink::getType)
83+
.containsOnly(IdentityLinkType.STARTER, IdentityLinkType.PARTICIPANT);
84+
85+
// Verify no historic identity links exist for the task directly (using management command
86+
// because getHistoricIdentityLinksForTask requires a historic task which doesn't exist at instance level)
87+
List<?> taskLinks = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
88+
return cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
89+
.getHistoricIdentityLinkService()
90+
.findHistoricIdentityLinksByTaskId(task.getId());
91+
});
92+
assertThat(taskLinks).isEmpty();
93+
94+
cmmnTaskService.complete(task.getId());
95+
}
96+
97+
@Test
98+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/oneHumanTaskHistoryLevelTask.cmmn")
99+
public void testTaskHistoryLevelStoresCaseInstanceAndTaskIdentityLinks() {
100+
Authentication.setAuthenticatedUserId("johndoe");
101+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
102+
.caseDefinitionKey("oneTaskCase")
103+
.start();
104+
105+
cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "participant1", IdentityLinkType.PARTICIPANT);
106+
107+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
108+
cmmnTaskService.addUserIdentityLink(task.getId(), "candidateUser1", IdentityLinkType.CANDIDATE);
109+
cmmnTaskService.addGroupIdentityLink(task.getId(), "candidateGroup1", IdentityLinkType.CANDIDATE);
110+
111+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
112+
113+
// Case instance links: starter + participant + participant from candidate user interceptor
114+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
115+
assertThat(caseIdentityLinks).hasSize(3);
116+
117+
// Task identity links: candidate user + candidate group
118+
List<HistoricIdentityLink> taskIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForTask(task.getId());
119+
assertThat(taskIdentityLinks).hasSize(2);
120+
assertThat(taskIdentityLinks)
121+
.extracting(HistoricIdentityLink::getType)
122+
.containsExactlyInAnyOrder(IdentityLinkType.CANDIDATE, IdentityLinkType.CANDIDATE);
123+
124+
cmmnTaskService.complete(task.getId());
125+
}
126+
127+
@Test
128+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/oneHumanTaskHistoryLevelActivity.cmmn")
129+
public void testActivityHistoryLevelStoresCaseInstanceAndTaskIdentityLinks() {
130+
Authentication.setAuthenticatedUserId("johndoe");
131+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
132+
.caseDefinitionKey("oneTaskCase")
133+
.start();
134+
135+
cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "participant1", IdentityLinkType.PARTICIPANT);
136+
137+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
138+
cmmnTaskService.addUserIdentityLink(task.getId(), "candidateUser1", IdentityLinkType.CANDIDATE);
139+
cmmnTaskService.addGroupIdentityLink(task.getId(), "candidateGroup1", IdentityLinkType.CANDIDATE);
140+
141+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
142+
143+
// Case instance links: starter + participant + participant from candidate user interceptor
144+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
145+
assertThat(caseIdentityLinks).hasSize(3);
146+
147+
// Task identity links: candidate user + candidate group
148+
// At activity level, historic tasks are not stored, so we query via the service directly
149+
List<HistoricIdentityLinkEntity> taskLinks = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
150+
return cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
151+
.getHistoricIdentityLinkService()
152+
.findHistoricIdentityLinksByTaskId(task.getId());
153+
});
154+
assertThat(taskLinks).hasSize(2);
155+
156+
cmmnTaskService.complete(task.getId());
157+
158+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
159+
160+
// Clean up task identity links that are not associated with a case instance
161+
// (at activity level, no historic task exists so these are not cascade-deleted)
162+
cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
163+
HistoricIdentityLinkService historicIdentityLinkService = cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
164+
.getHistoricIdentityLinkService();
165+
for (HistoricIdentityLinkEntity link : taskLinks) {
166+
historicIdentityLinkService.deleteHistoricIdentityLink(link.getId());
167+
}
168+
return null;
169+
});
170+
}
171+
172+
@Test
173+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/testOneSimpleHumanTaskWithHistoryLevelNone.cmmn")
174+
public void testNoneHistoryLevelDoesNotStoreIdentityLinks() {
175+
Authentication.setAuthenticatedUserId("johndoe");
176+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
177+
.caseDefinitionKey("myCase")
178+
.start();
179+
180+
cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "participant1", IdentityLinkType.PARTICIPANT);
181+
182+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
183+
cmmnTaskService.addUserIdentityLink(task.getId(), "candidateUser1", IdentityLinkType.CANDIDATE);
184+
185+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
186+
187+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
188+
assertThat(caseIdentityLinks).isEmpty();
189+
190+
List<?> taskLinks = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
191+
return cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
192+
.getHistoricIdentityLinkService()
193+
.findHistoricIdentityLinksByTaskId(task.getId());
194+
});
195+
assertThat(taskLinks).isEmpty();
196+
197+
cmmnTaskService.complete(task.getId());
198+
}
199+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
3+
xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
4+
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
5+
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI"
6+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7+
xmlns:flowable="http://flowable.org/cmmn"
8+
targetNamespace="http://flowable.org/cmmn">
9+
<case id="oneTaskCase">
10+
<casePlanModel id="myPlanModel" name="My CasePlanModel">
11+
<extensionElements>
12+
<flowable:historyLevel>activity</flowable:historyLevel>
13+
</extensionElements>
14+
<planItem id="planItem1" name="The Task" definitionRef="theTask" />
15+
<humanTask id="theTask" name="The Task" />
16+
</casePlanModel>
17+
</case>
18+
</definitions>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
3+
xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
4+
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
5+
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI"
6+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7+
xmlns:flowable="http://flowable.org/cmmn"
8+
targetNamespace="http://flowable.org/cmmn">
9+
<case id="oneTaskCase">
10+
<casePlanModel id="myPlanModel" name="My CasePlanModel">
11+
<extensionElements>
12+
<flowable:historyLevel>instance</flowable:historyLevel>
13+
</extensionElements>
14+
<planItem id="planItem1" name="The Task" definitionRef="theTask" />
15+
<humanTask id="theTask" name="The Task" />
16+
</casePlanModel>
17+
</case>
18+
</definitions>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
3+
xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
4+
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
5+
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI"
6+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7+
xmlns:flowable="http://flowable.org/cmmn"
8+
targetNamespace="http://flowable.org/cmmn">
9+
<case id="oneTaskCase">
10+
<casePlanModel id="myPlanModel" name="My CasePlanModel">
11+
<extensionElements>
12+
<flowable:historyLevel>task</flowable:historyLevel>
13+
</extensionElements>
14+
<planItem id="planItem1" name="The Task" definitionRef="theTask" />
15+
<humanTask id="theTask" name="The Task" />
16+
</casePlanModel>
17+
</case>
18+
</definitions>

modules/flowable-engine/src/main/java/org/flowable/engine/impl/history/DefaultHistoryConfigurationSettings.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,10 @@ public boolean isHistoryEnabledForVariables(String processDefinitionId) {
291291
@Override
292292
public boolean isHistoryEnabledForIdentityLink(IdentityLinkEntity identityLink) {
293293
String processDefinitionId = getProcessDefinitionId(identityLink);
294-
return isHistoryLevelAtLeast(HistoryLevel.AUDIT, processDefinitionId);
294+
if (identityLink.getTaskId() != null) {
295+
return isHistoryLevelAtLeast(HistoryLevel.TASK, processDefinitionId);
296+
}
297+
return isHistoryLevelAtLeast(HistoryLevel.INSTANCE, processDefinitionId);
295298
}
296299

297300
protected String getProcessDefinitionId(IdentityLinkEntity identityLink) {

0 commit comments

Comments
 (0)