From d3ade5ad7d98d1c8402b7592f9eda639608755ae Mon Sep 17 00:00:00 2001 From: "gergely.juhasz@camunda.com" Date: Thu, 16 Jan 2025 15:53:15 +0100 Subject: [PATCH] feat(engine): HistoricProcessInstanceQuery processInstanceIdNotIn filter related to: #4896 --- .../lib/commons/history-process-instance.ftl | 6 ++ .../HistoricProcessInstanceQueryDto.java | 9 ++ ...icProcessInstanceRestServiceQueryTest.java | 37 +++++++ .../history/HistoricProcessInstanceQuery.java | 6 ++ .../HistoricProcessInstanceQueryImpl.java | 15 ++- .../bpm/engine/impl/util/CompareUtil.java | 9 ++ .../entity/HistoricProcessInstance.xml | 7 ++ .../history/HistoricProcessInstanceTest.java | 96 +++++++++++++++++++ 8 files changed, 184 insertions(+), 1 deletion(-) diff --git a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/history-process-instance.ftl b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/history-process-instance.ftl index 548f00d4644..2629e1c5cb6 100644 --- a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/history-process-instance.ftl +++ b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/history-process-instance.ftl @@ -52,6 +52,12 @@ "desc": "Filter by process instance ids. ${listTypeDescription}." }, + "processInstanceIdNotIn": { + "type": "array", + "itemType": "string", + "desc": "Exclude instances by process instance ids. ${listTypeDescription}." + }, + "processDefinitionId": { "type": "string", "desc": "Filter by the process definition the instances run on." diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/history/HistoricProcessInstanceQueryDto.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/history/HistoricProcessInstanceQueryDto.java index c944c2288ed..e13bcee92d4 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/history/HistoricProcessInstanceQueryDto.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/history/HistoricProcessInstanceQueryDto.java @@ -73,6 +73,7 @@ public class HistoricProcessInstanceQueryDto extends AbstractQueryDto processInstanceIds; + private List processInstanceIdNotIn; private String processDefinitionId; private String processDefinitionKey; private List processDefinitionKeys; @@ -145,6 +146,11 @@ public void setProcessInstanceIds(Set processInstanceIds) { this.processInstanceIds = processInstanceIds; } + @CamundaQueryParam(value = "processInstanceIdNotIn", converter = StringSetConverter.class) + public void setProcessInstanceIdNotIn(List processInstanceIdNotIn) { + this.processInstanceIdNotIn = processInstanceIdNotIn; + } + public String getProcessDefinitionId() { return processDefinitionId; } @@ -413,6 +419,9 @@ protected void applyFilters(HistoricProcessInstanceQuery query) { if (processInstanceIds != null) { query.processInstanceIds(processInstanceIds); } + if (processInstanceIdNotIn != null && !processInstanceIdNotIn.isEmpty()) { + query.processInstanceIdNotIn(processInstanceIdNotIn.toArray(new String[0])); + } if (processDefinitionId != null) { query.processDefinitionId(processDefinitionId); } diff --git a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/history/HistoricProcessInstanceRestServiceQueryTest.java b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/history/HistoricProcessInstanceRestServiceQueryTest.java index 7ed79ed567b..99d0a3ac2bb 100644 --- a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/history/HistoricProcessInstanceRestServiceQueryTest.java +++ b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/history/HistoricProcessInstanceRestServiceQueryTest.java @@ -1078,6 +1078,43 @@ private void verifyProcessInstanceIdSetInvocation() { verify(mockedQuery).list(); } + @Test + public void testQueryByProcessInstanceIdNotIn() { + given() + .queryParam("processInstanceIdNotIn", "firstProcessInstanceId,secondProcessInstanceId") + .then() + .expect() + .statusCode(Status.OK.getStatusCode()) + .when() + .get(HISTORIC_PROCESS_INSTANCE_RESOURCE_URL); + + verify(mockedQuery).processInstanceIdNotIn("firstProcessInstanceId", "secondProcessInstanceId"); + verify(mockedQuery).list(); + } + + @Test + public void testQueryByProcessInstanceIdNotInAsPost() { + Map> parameters = new HashMap>(); + + Set processInstanceIds = new HashSet(); + processInstanceIds.add("firstProcessInstanceId"); + processInstanceIds.add("secondProcessInstanceId"); + + parameters.put("processInstanceIdNotIn", processInstanceIds); + + given() + .contentType(POST_JSON_CONTENT_TYPE) + .body(parameters) + .then() + .expect() + .statusCode(Status.OK.getStatusCode()) + .when() + .post(HISTORIC_PROCESS_INSTANCE_RESOURCE_URL); + + verify(mockedQuery).processInstanceIdNotIn("firstProcessInstanceId", "secondProcessInstanceId"); + verify(mockedQuery).list(); + } + @Test public void testQueryByProcessDefinitionKeyNotIn() { given() diff --git a/engine/src/main/java/org/camunda/bpm/engine/history/HistoricProcessInstanceQuery.java b/engine/src/main/java/org/camunda/bpm/engine/history/HistoricProcessInstanceQuery.java index 6cef89483c4..68a672f3def 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/history/HistoricProcessInstanceQuery.java +++ b/engine/src/main/java/org/camunda/bpm/engine/history/HistoricProcessInstanceQuery.java @@ -48,6 +48,12 @@ public interface HistoricProcessInstanceQuery extends Query processInstanceIds); + /** + * Only select historic process instances whose id is not in the given set of ids. + * {@link ProcessInstance) ids and {@link HistoricProcessInstance} ids match. + */ + HistoricProcessInstanceQuery processInstanceIdNotIn(String... processInstanceIdNotIn); + /** * Only select historic process instances for the given process definition */ diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/HistoricProcessInstanceQueryImpl.java b/engine/src/main/java/org/camunda/bpm/engine/impl/HistoricProcessInstanceQueryImpl.java index ba4a6fa7bb2..304bb3bc107 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/HistoricProcessInstanceQueryImpl.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/HistoricProcessInstanceQueryImpl.java @@ -87,6 +87,7 @@ public class HistoricProcessInstanceQueryImpl extends AbstractVariableQueryImpl< protected String processDefinitionKey; protected String[] processDefinitionKeys; protected Set processInstanceIds; + protected String[] processInstanceIdNotIn; protected String[] tenantIds; protected boolean isTenantIdSet; protected String[] executedActivityIds; @@ -120,6 +121,12 @@ public HistoricProcessInstanceQuery processInstanceIds(Set processInstan return this; } + public HistoricProcessInstanceQuery processInstanceIdNotIn(String... processDefinitionIdNotIn){ + ensureNotNull("processDefinitionIdNotIn", (Object[]) processDefinitionIdNotIn); + this.processInstanceIdNotIn = processDefinitionIdNotIn; + return this; + } + public HistoricProcessInstanceQueryImpl processDefinitionId(String processDefinitionId) { this.processDefinitionId = processDefinitionId; return this; @@ -310,7 +317,9 @@ protected boolean hasExcludingConditions() { || CompareUtil.areNotInAscendingOrder(startedAfter, startedBefore) || CompareUtil.areNotInAscendingOrder(finishedAfter, finishedBefore) || CompareUtil.elementIsContainedInList(processDefinitionKey, processKeyNotIn) - || CompareUtil.elementIsNotContainedInList(processInstanceId, processInstanceIds); + || CompareUtil.elementIsNotContainedInList(processInstanceId, processInstanceIds) + || CompareUtil.elementIsContainedInArray(processInstanceId, processInstanceIdNotIn) + || CompareUtil.elementsAreContainedInArray(processInstanceIds, processInstanceIdNotIn); } public HistoricProcessInstanceQuery orderByProcessInstanceBusinessKey() { @@ -559,6 +568,10 @@ public Set getProcessInstanceIds() { return processInstanceIds; } + public String[] getProcessInstanceIdNotIn() { + return processInstanceIdNotIn; + } + public String getStartedBy() { return startedBy; } diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/util/CompareUtil.java b/engine/src/main/java/org/camunda/bpm/engine/impl/util/CompareUtil.java index fd7fabe726f..5476662869e 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/util/CompareUtil.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/util/CompareUtil.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; /** @@ -158,4 +159,12 @@ public static > T min(T obj1, T obj2) { public static > T max(T obj1, T obj2) { return obj1.compareTo(obj2) >= 0 ? obj1 : obj2; } + + public static boolean elementsAreContainedInArray(Collection subset, T[] superset) { + if (subset != null && !subset.isEmpty() && superset != null && superset.length > 0 && superset.length >= subset.size()) { + return new HashSet<>(Arrays.asList(superset)).containsAll(subset); + } + return false; + } +// } } diff --git a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/HistoricProcessInstance.xml b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/HistoricProcessInstance.xml index a41ccaf649c..a05858e0557 100644 --- a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/HistoricProcessInstance.xml +++ b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/HistoricProcessInstance.xml @@ -471,6 +471,13 @@ + + ${queryType} SELF.PROC_INST_ID_ NOT IN + + #{processInstanceId} + + ${queryType} SELF.CASE_INST_ID_ = #{query.caseInstanceId} diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/history/HistoricProcessInstanceTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/history/HistoricProcessInstanceTest.java index 7c6ea48d4a9..6f4b9d5233d 100644 --- a/engine/src/test/java/org/camunda/bpm/engine/test/history/HistoricProcessInstanceTest.java +++ b/engine/src/test/java/org/camunda/bpm/engine/test/history/HistoricProcessInstanceTest.java @@ -36,6 +36,7 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -2595,6 +2596,101 @@ public void shouldQueryByVariableValue_12() { .containsExactlyInAnyOrder(processInstanceIdOne, processInstanceIdTwo); } + @Test + @Deployment(resources = {"org/camunda/bpm/engine/test/history/oneTaskProcess.bpmn20.xml"}) +public void shouldExcludeByProcessInstanceIdNotIn() { + // GIVEN + String processInstanceIdOne = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + String processInstanceIdTwo = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + + // WHEN + List processInstances = historyService.createHistoricProcessInstanceQuery().list(); + List excludedFirst = historyService.createHistoricProcessInstanceQuery() + .processInstanceIdNotIn(processInstanceIdOne).list(); + List excludedAll = historyService.createHistoricProcessInstanceQuery() + .processInstanceIdNotIn(processInstanceIdOne, processInstanceIdTwo).list(); + + // THEN + assertThat(processInstances) + .extracting("processInstanceId") + .containsExactlyInAnyOrder(processInstanceIdOne, processInstanceIdTwo); + assertThat(excludedFirst) + .extracting("processInstanceId") + .containsExactly(processInstanceIdTwo); + assertThat(excludedAll) + .extracting("processInstanceId") + .isEmpty(); + } + + @Test + @Deployment(resources = "org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml") + public void testWithNonExistentProcessInstanceIdNotIn() { + // GIVEN + String processInstanceIdOne = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + String processInstanceIdTwo = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + + String nonExistentProcessInstanceId = "ThisIsAFake"; + + // WHEN + List processInstances = historyService.createHistoricProcessInstanceQuery().list(); + List excludedNonExistent = historyService.createHistoricProcessInstanceQuery() + .processInstanceIdNotIn(nonExistentProcessInstanceId).list(); + + // THEN + assertThat(processInstances) + .extracting("processInstanceId") + .containsExactlyInAnyOrder(processInstanceIdOne, processInstanceIdTwo); + assertThat(excludedNonExistent) + .extracting("processInstanceId") + .containsExactly(processInstanceIdOne, processInstanceIdTwo); + } + + @Test + @Deployment(resources = "org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml") + public void testQueryByOneInvalidProcessInstanceIdNotIn() { + try { + // when + historyService.createHistoricProcessInstanceQuery() + .processInstanceIdNotIn((String) null); + fail(); + } catch(ProcessEngineException expected) { + // then Exception is expected + } + } + + @Test + @Deployment(resources = "org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml") + public void testExcludingProcessInstanceAndProcessInstanceIdNotIn() { + // GIVEN + String processInstanceIdOne = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + + // WHEN + long count = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceIdOne) + .processInstanceIdNotIn(processInstanceIdOne).count(); + + // THEN making a query that has contradicting conditions should succeed + assertThat(count).isEqualTo(0L); + } + + @Test + @Deployment(resources = "org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml") + public void testExcludingProcessInstanceIdsAndProcessInstanceIdNotIn() { + // GIVEN + String processInstanceIdOne = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + String processInstanceIdTwo = runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + runtimeService.startProcessInstanceByKey("oneTaskProcess").getProcessInstanceId(); + + // WHEN + long count = historyService.createHistoricProcessInstanceQuery() + .processInstanceIds(new HashSet<>(Arrays.asList(processInstanceIdOne, processInstanceIdTwo))) + .processInstanceIdNotIn(processInstanceIdOne, processInstanceIdTwo).count(); + + // THEN making a query that has contradicting conditions should succeed + assertThat(count).isEqualTo(0L); + } + protected void deployment(String... resources) { testHelper.deploy(resources); }