Skip to content

Commit b17a1bf

Browse files
Merge branch 'main' of github.com:flowable/flowable-engine into flowable-release-8.1.0
2 parents e5f1030 + 8b02b0d commit b17a1bf

9 files changed

Lines changed: 448 additions & 12 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,14 @@ public interface CmmnHistoryConfigurationSettings {
6060
boolean isHistoryEnabledForPlanItemInstance(PlanItemInstanceEntity planItemInstanceEntity);
6161

6262
/**
63-
* Returns whether history is enabled for the provided user task.
63+
* Returns whether history is enabled for the provided human task.
6464
*/
65-
boolean isHistoryEnabledForUserTask(TaskInfo taskInfo);
65+
boolean isHistoryEnabledForHumanTask(TaskInfo taskInfo);
66+
67+
/**
68+
* Returns whether task history is enabled for the provided case definition id.
69+
*/
70+
boolean isHistoryEnabledForHumanTask(String caseDefinitionId);
6671

6772
/**
6873
* Returns whether history is enabled for the provided variable instance.

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
import org.apache.commons.lang3.StringUtils;
1616
import org.flowable.cmmn.api.repository.CaseDefinition;
1717
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
18-
import org.flowable.cmmn.engine.impl.process.ProcessInstanceService;
1918
import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity;
2019
import org.flowable.cmmn.engine.impl.persistence.entity.MilestoneInstanceEntity;
2120
import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity;
21+
import org.flowable.cmmn.engine.impl.process.ProcessInstanceService;
2222
import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil;
2323
import org.flowable.cmmn.model.Case;
2424
import org.flowable.cmmn.model.CmmnModel;
@@ -170,11 +170,16 @@ public boolean isHistoryEnabledForPlanItemInstance(PlanItemInstanceEntity planIt
170170
}
171171

172172
@Override
173-
public boolean isHistoryEnabledForUserTask(TaskInfo taskInfo) {
173+
public boolean isHistoryEnabledForHumanTask(TaskInfo taskInfo) {
174174
String scopeDefinitionId = taskInfo.getScopeDefinitionId();
175+
return isHistoryEnabledForHumanTask(scopeDefinitionId);
176+
}
177+
178+
@Override
179+
public boolean isHistoryEnabledForHumanTask(String caseDefinitionId) {
175180
HistoryLevel engineHistoryLevel = cmmnEngineConfiguration.getHistoryLevel();
176-
if (isEnableCaseDefinitionHistoryLevel() && scopeDefinitionId != null) {
177-
HistoryLevel caseDefinitionLevel = getCaseDefinitionHistoryLevel(scopeDefinitionId);
181+
if (isEnableCaseDefinitionHistoryLevel() && caseDefinitionId != null) {
182+
HistoryLevel caseDefinitionLevel = getCaseDefinitionHistoryLevel(caseDefinitionId);
178183
if (caseDefinitionLevel != null) {
179184
if (LOGGER.isDebugEnabled()) {
180185
LOGGER.debug("Current history level: {}, level required: {}", caseDefinitionLevel, HistoryLevel.TASK);
@@ -222,7 +227,10 @@ public boolean isHistoryEnabledForVariableInstance(VariableInstanceEntity variab
222227
@Override
223228
public boolean isHistoryEnabledForIdentityLink(IdentityLinkEntity identityLinkEntity) {
224229
String caseDefinitionId = getCaseDefinitionId(identityLinkEntity);
225-
return isHistoryLevelAtLeast(HistoryLevel.AUDIT, caseDefinitionId);
230+
if (identityLinkEntity.getTaskId() != null) {
231+
return isHistoryEnabledForHumanTask(caseDefinitionId);
232+
}
233+
return isHistoryLevelAtLeast(HistoryLevel.INSTANCE, caseDefinitionId);
226234
}
227235

228236
protected String getCaseDefinitionId(IdentityLinkEntity identityLink) {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,14 @@ public void recordVariableRemoved(VariableInstanceEntity variableInstanceEntity)
286286

287287
@Override
288288
public void recordTaskCreated(TaskEntity task) {
289-
if (getHistoryConfigurationSettings().isHistoryEnabledForUserTask(task)) {
289+
if (getHistoryConfigurationSettings().isHistoryEnabledForHumanTask(task)) {
290290
cmmnEngineConfiguration.getTaskServiceConfiguration().getHistoricTaskService().recordTaskCreated(task);
291291
}
292292
}
293293

294294
@Override
295295
public void recordTaskEnd(TaskEntity task, String userId, String deleteReason, Date endTime) {
296-
if (getHistoryConfigurationSettings().isHistoryEnabledForUserTask(task)) {
296+
if (getHistoryConfigurationSettings().isHistoryEnabledForHumanTask(task)) {
297297
HistoricTaskInstanceEntity historicTaskInstance = cmmnEngineConfiguration.getTaskServiceConfiguration().getHistoricTaskService().recordTaskEnd(task, deleteReason, endTime);
298298
if (historicTaskInstance != null) {
299299
historicTaskInstance.setState(Task.COMPLETED);
@@ -305,14 +305,14 @@ public void recordTaskEnd(TaskEntity task, String userId, String deleteReason, D
305305

306306
@Override
307307
public void recordTaskInfoChange(TaskEntity task, Date changeTime) {
308-
if (getHistoryConfigurationSettings().isHistoryEnabledForUserTask(task)) {
308+
if (getHistoryConfigurationSettings().isHistoryEnabledForHumanTask(task)) {
309309
cmmnEngineConfiguration.getTaskServiceConfiguration().getHistoricTaskService().recordTaskInfoChange(task, changeTime, cmmnEngineConfiguration);
310310
}
311311
}
312312

313313
@Override
314314
public void recordHistoricTaskDeleted(HistoricTaskInstance task) {
315-
if (task != null && getHistoryConfigurationSettings().isHistoryEnabledForUserTask(task)) {
315+
if (task != null && getHistoryConfigurationSettings().isHistoryEnabledForHumanTask(task)) {
316316
TaskHelper.deleteHistoricTask(task.getId(), cmmnEngineConfiguration);
317317
}
318318
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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.impl.persistence.entity.HistoricIdentityLinkEntity;
27+
import org.flowable.task.api.Task;
28+
import org.flowable.task.api.history.HistoricTaskInstance;
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+
HistoricTaskInstance historyTaskInstance = cmmnHistoryService.createHistoricTaskInstanceQuery().taskId(task.getId()).singleResult();
148+
assertThat(historyTaskInstance).isNull();
149+
150+
// Task identity links: candidate user + candidate group
151+
// At activity level, historic tasks are not stored, so we query via the service directly
152+
List<HistoricIdentityLinkEntity> taskLinks = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
153+
return cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
154+
.getHistoricIdentityLinkService()
155+
.findHistoricIdentityLinksByTaskId(task.getId());
156+
});
157+
assertThat(taskLinks).hasSize(0);
158+
159+
cmmnTaskService.complete(task.getId());
160+
161+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
162+
}
163+
164+
@Test
165+
@CmmnDeployment(resources = "org/flowable/cmmn/test/history/testOneSimpleHumanTaskWithHistoryLevelNone.cmmn")
166+
public void testNoneHistoryLevelDoesNotStoreIdentityLinks() {
167+
Authentication.setAuthenticatedUserId("johndoe");
168+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
169+
.caseDefinitionKey("myCase")
170+
.start();
171+
172+
cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "participant1", IdentityLinkType.PARTICIPANT);
173+
174+
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
175+
cmmnTaskService.addUserIdentityLink(task.getId(), "candidateUser1", IdentityLinkType.CANDIDATE);
176+
177+
CmmnHistoryTestHelper.waitForJobExecutorToProcessAllHistoryJobs(cmmnEngineConfiguration, cmmnManagementService, 7000, 200);
178+
179+
List<HistoricIdentityLink> caseIdentityLinks = cmmnHistoryService.getHistoricIdentityLinksForCaseInstance(caseInstance.getId());
180+
assertThat(caseIdentityLinks).isEmpty();
181+
182+
List<?> taskLinks = cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
183+
return cmmnEngineConfiguration.getIdentityLinkServiceConfiguration()
184+
.getHistoricIdentityLinkService()
185+
.findHistoricIdentityLinksByTaskId(task.getId());
186+
});
187+
assertThat(taskLinks).isEmpty();
188+
189+
cmmnTaskService.complete(task.getId());
190+
}
191+
}
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 isHistoryEnabledForUserTask(processDefinitionId);
296+
}
297+
return isHistoryLevelAtLeast(HistoryLevel.INSTANCE, processDefinitionId);
295298
}
296299

297300
protected String getProcessDefinitionId(IdentityLinkEntity identityLink) {

0 commit comments

Comments
 (0)