diff --git a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl index 1b5bc1e646d..9c4deda34ba 100644 --- a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl +++ b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl @@ -271,6 +271,12 @@ type = "string" desc = "Restrict to tasks that have one of the given keys. The keys need to be in a comma-separated list." /> + + <@lib.parameter name = "taskDefinitionKeyNotIn" + location = "query" + type = "string" + desc = "Exclude instances by a list of task definition keys. The keys need to be in a + comma-separated list." /> <@lib.parameter name = "taskDefinitionKeyLike" location = "query" diff --git a/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl b/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl index ae8783555e6..9ea7ea984b8 100644 --- a/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl +++ b/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl @@ -281,6 +281,12 @@ itemType = "string" desc = "Restrict to tasks that have one of the given keys. The keys need to be in a comma-separated list." /> + <@lib.property + name = "taskDefinitionKeyNotIn" + type = "array" + itemType = "string" + desc = "Exclude instances by a list of task definition keys. The keys need to be in a comma-separated list." /> + <@lib.property name = "taskDefinitionKeyLike" type = "string" diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java index bd0b367b947..eebf8cd3974 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java @@ -137,6 +137,7 @@ public class TaskQueryDto extends AbstractQueryDto { private Boolean includeAssignedTasks; private String taskDefinitionKey; private String[] taskDefinitionKeyIn; + private String[] taskDefinitionKeyNotIn; private String taskDefinitionKeyLike; private String taskId; private String[] taskIdIn; @@ -408,6 +409,11 @@ public void setTaskDefinitionKeyIn(String[] taskDefinitionKeyIn) { this.taskDefinitionKeyIn = taskDefinitionKeyIn; } + @CamundaQueryParam(value = "taskDefinitionKeyNotIn", converter= StringArrayConverter.class) + public void setTaskDefinitionKeyNotIn(String[] taskDefinitionKeyNotIn) { + this.taskDefinitionKeyNotIn = taskDefinitionKeyNotIn; + } + @CamundaQueryParam("taskDefinitionKeyLike") public void setTaskDefinitionKeyLike(String taskDefinitionKeyLike) { this.taskDefinitionKeyLike = taskDefinitionKeyLike; @@ -855,6 +861,10 @@ public String[] getTaskDefinitionKeyIn() { return taskDefinitionKeyIn; } + public String[] getTaskDefinitionKeyNotIn() { + return taskDefinitionKeyNotIn; + } + public String getTaskDefinitionKey() { return taskDefinitionKey; } @@ -1209,6 +1219,9 @@ protected void applyFilters(TaskQuery query) { if (taskDefinitionKeyIn != null && taskDefinitionKeyIn.length > 0) { query.taskDefinitionKeyIn(taskDefinitionKeyIn); } + if (taskDefinitionKeyNotIn != null && taskDefinitionKeyNotIn.length > 0) { + query.taskDefinitionKeyNotIn(taskDefinitionKeyNotIn); + } if (taskDefinitionKey != null) { query.taskDefinitionKey(taskDefinitionKey); } @@ -1615,6 +1628,7 @@ public static TaskQueryDto fromQuery(Query query, boolean isOrQueryActive) dto.assigneeLike = taskQuery.getAssigneeLike(); dto.taskDefinitionKey = taskQuery.getKey(); dto.taskDefinitionKeyIn = taskQuery.getKeys(); + dto.taskDefinitionKeyNotIn = taskQuery.getKeyNotIn(); dto.taskDefinitionKeyLike = taskQuery.getKeyLike(); dto.description = taskQuery.getDescription(); dto.descriptionLike = taskQuery.getDescriptionLike(); diff --git a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java index 75035a87a8e..0fad23f9741 100644 --- a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java +++ b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java @@ -430,6 +430,7 @@ public void testAdditionalParametersExcludingVariables() { .queryParams(booleanQueryParameters) .queryParam("activityInstanceIdIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("activityInstanceIdIn"))) .queryParam("taskDefinitionKeyIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("taskDefinitionKeyIn"))) + .queryParam("taskDefinitionKeyNotIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("taskDefinitionKeyNotIn"))) .queryParam("taskIdIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("taskIdIn"))) .queryParam("processDefinitionKeyIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("processDefinitionKeyIn"))) .queryParam("processInstanceBusinessKeyIn", arrayAsCommaSeperatedList(arrayQueryParameters.get("processInstanceBusinessKeyIn"))) @@ -472,6 +473,7 @@ private Map getCompleteStringArrayQueryParameters() { String[] activityInstanceIds = { "anActivityInstanceId", "anotherActivityInstanceId" }; String[] taskDefinitionKeys = { "aTaskDefinitionKey", "anotherTaskDefinitionKey" }; + String[] taskDefinitionKeyNotIn = { "anUnwantedTaskDefinitionKey", "anotherUnwantedTaskDefinitionKey" }; String[] processDefinitionKeys = { "aProcessDefinitionKey", "anotherProcessDefinitionKey" }; String[] processInstanceBusinessKeys = { "aBusinessKey", "anotherBusinessKey" }; String[] tenantIds = { MockProvider.EXAMPLE_TENANT_ID, MockProvider.ANOTHER_EXAMPLE_TENANT_ID }; @@ -483,6 +485,7 @@ private Map getCompleteStringArrayQueryParameters() { parameters.put("activityInstanceIdIn", activityInstanceIds); parameters.put("taskDefinitionKeyIn", taskDefinitionKeys); + parameters.put("taskDefinitionKeyNotIn", taskDefinitionKeyNotIn); parameters.put("taskIdIn", taskId); parameters.put("processDefinitionKeyIn", processDefinitionKeys); parameters.put("processInstanceBusinessKeyIn", processInstanceBusinessKeys); @@ -596,6 +599,7 @@ private void verifyStringArrayParametersInvocations() { verify(mockQuery).activityInstanceIdIn(stringArrayParameters.get("activityInstanceIdIn")); verify(mockQuery).taskDefinitionKeyIn(stringArrayParameters.get("taskDefinitionKeyIn")); + verify(mockQuery).taskDefinitionKeyNotIn(stringArrayParameters.get("taskDefinitionKeyNotIn")); verify(mockQuery).processDefinitionKeyIn(stringArrayParameters.get("processDefinitionKeyIn")); verify(mockQuery).processInstanceBusinessKeyIn(stringArrayParameters.get("processInstanceBusinessKeyIn")); verify(mockQuery).tenantIdIn(stringArrayParameters.get("tenantIdIn")); diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java b/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java index c23eeb05eb1..b5fb64c0a5f 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java @@ -132,6 +132,7 @@ public class TaskQueryImpl extends AbstractQuery implements Tas protected String key; protected String keyLike; protected String[] taskDefinitionKeys; + protected String[] taskDefinitionKeyNotIn; protected String processDefinitionKey; protected String[] processDefinitionKeys; protected String processDefinitionId; @@ -669,6 +670,12 @@ public TaskQuery taskDefinitionKeyIn(String... taskDefinitionKeys) { this.taskDefinitionKeys = taskDefinitionKeys; return this; } + + @Override + public TaskQuery taskDefinitionKeyNotIn(String... taskDefinitionKeyNotIn) { + this.taskDefinitionKeyNotIn = taskDefinitionKeyNotIn; + return this; + } @Override public TaskQuery taskParentTaskId(String taskParentTaskId) { @@ -1100,6 +1107,7 @@ protected boolean hasExcludingConditions() { || CompareUtil.areNotInAscendingOrder(followUpAfter, followUpDate, followUpBefore) || CompareUtil.areNotInAscendingOrder(createTimeAfter, createTime, createTimeBefore) || CompareUtil.elementIsNotContainedInArray(key, taskDefinitionKeys) + || CompareUtil.elementIsContainedInArray(key, taskDefinitionKeyNotIn) || CompareUtil.elementIsNotContainedInArray(processDefinitionKey, processDefinitionKeys) || CompareUtil.elementIsNotContainedInArray(processInstanceBusinessKey, processInstanceBusinessKeys); } @@ -1675,6 +1683,10 @@ public String getKey() { public String[] getKeys() { return taskDefinitionKeys; } + + public String[] getKeyNotIn() { + return taskDefinitionKeyNotIn; + } public String getKeyLike() { return keyLike; @@ -1804,6 +1816,10 @@ public String[] getTaskDefinitionKeys() { return taskDefinitionKeys; } + public String[] getTaskDefinitionKeyNotIn() { + return taskDefinitionKeyNotIn; + } + public boolean getIsTenantIdSet() { return isWithoutTenantId; } @@ -2100,6 +2116,13 @@ else if (this.getKeyLike() != null) { else if (this.getKeys() != null) { extendedQuery.taskDefinitionKeyIn(this.getKeys()); } + + if (extendingQuery.getKeyNotIn() != null) { + extendedQuery.taskDefinitionKeyNotIn(extendingQuery.getKeyNotIn()); + } + else if (this.getKeyNotIn() != null) { + extendedQuery.taskDefinitionKeyNotIn(this.getKeyNotIn()); + } if (extendingQuery.getParentTaskId() != null) { extendedQuery.taskParentTaskId(extendingQuery.getParentTaskId()); diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java b/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java index 707f37c7834..c389a77f660 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java @@ -80,6 +80,7 @@ public class JsonTaskQueryConverter extends JsonObjectConverter { public static final String UPDATED_AFTER = "updatedAfter"; public static final String KEY = "key"; public static final String KEYS = "keys"; + public static final String KEY_NOT_IN = "keyNotIn"; public static final String KEY_LIKE = "keyLike"; public static final String PARENT_TASK_ID = "parentTaskId"; public static final String PROCESS_DEFINITION_KEY = "processDefinitionKey"; @@ -186,6 +187,7 @@ public JsonObject toJsonObject(TaskQuery taskQuery, boolean isOrQueryActive) { JsonUtil.addDateField(json, UPDATED_AFTER, query.getUpdatedAfter()); JsonUtil.addField(json, KEY, query.getKey()); JsonUtil.addArrayField(json, KEYS, query.getKeys()); + JsonUtil.addArrayField(json, KEY_NOT_IN, query.getKeyNotIn()); JsonUtil.addField(json, KEY_LIKE, query.getKeyLike()); JsonUtil.addField(json, PARENT_TASK_ID, query.getParentTaskId()); JsonUtil.addField(json, PROCESS_DEFINITION_KEY, query.getProcessDefinitionKey()); @@ -416,6 +418,9 @@ protected TaskQuery toObject(JsonObject json, boolean isOrQuery) { if (json.has(KEYS)) { query.taskDefinitionKeyIn(getArray(JsonUtil.getArray(json, KEYS))); } + if (json.has(KEY_NOT_IN)) { + query.taskDefinitionKeyNotIn(getArray(JsonUtil.getArray(json, KEY_NOT_IN))); + } if (json.has(KEY_LIKE)) { query.taskDefinitionKeyLike(JsonUtil.getString(json, KEY_LIKE)); } diff --git a/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java b/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java index 4bcc1edd0aa..5e8ec1f058a 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java +++ b/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java @@ -494,6 +494,11 @@ public interface TaskQuery extends Query { **/ TaskQuery taskDefinitionKeyIn(String... taskDefinitionKeys); + /** + * Only select tasks which do not have one of the taskDefinitionKeys. + **/ + TaskQuery taskDefinitionKeyNotIn(String... taskDefinitionKeys); + /** * Select the tasks which are sub tasks of the given parent task. */ diff --git a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml index db05924fd35..cf20e56f6b1 100644 --- a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml +++ b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml @@ -486,6 +486,13 @@ #{item} + + ${queryType} RES.TASK_DEF_KEY_ not in + + #{item} + + ${queryType} RES.PROC_DEF_ID_ = #{query.processDefinitionId} diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java index ddcbe46388a..00e0c858dd1 100644 --- a/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java +++ b/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java @@ -206,6 +206,7 @@ public void testTaskQuery() { query.taskUpdatedAfterExpression(testString); query.taskDefinitionKey(testString); query.taskDefinitionKeyIn(testKeys); + query.taskDefinitionKeyNotIn(testKeys); query.taskDefinitionKeyLike(testString); query.processDefinitionKey(testString); query.processDefinitionKeyIn(testKeys); @@ -321,6 +322,10 @@ public void testTaskQuery() { for (int i = 0; i < query.getKeys().length; i++) { assertEquals(testKeys[i], query.getKeys()[i]); } + assertEquals(testKeys.length, query.getKeyNotIn().length); + for (int i = 0; i < query.getKeyNotIn().length; i++) { + assertEquals(testKeys[i], query.getKeyNotIn()[i]); + } assertEquals(testString, query.getKeyLike()); assertEquals(testString, query.getProcessDefinitionKey()); for (int i = 0; i < query.getProcessDefinitionKeys().length; i++) { diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java index f3b64ed8427..ff1ccbfb596 100644 --- a/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java +++ b/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java @@ -1116,6 +1116,89 @@ public void testTaskDefinitionKeyIn() throws Exception { assertEquals(0l, count.longValue()); } + @Test + @Deployment(resources="org/camunda/bpm/engine/test/api/task/taskDefinitionProcess.bpmn20.xml") + public void testTaskDefinitionKeyNotInNoKeysProvided() { + + // Given + // Start process instance, 2 tasks will be available with: + // - process definition key "taskDefinitionKeyProcess" + // - task definition keys "taskKey_1" & "taskKey_123" + runtimeService.startProcessInstanceByKey("taskDefinitionKeyProcess"); + + // When + var tasks = taskService.createTaskQuery() + .processDefinitionKey("taskDefinitionKeyProcess") + .taskDefinitionKeyNotIn() + .list(); + // Then + assertThat(tasks) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("taskKey_1", "taskKey_123"); + } + + @Test + @Deployment(resources="org/camunda/bpm/engine/test/api/task/taskDefinitionProcess.bpmn20.xml") + public void testTaskDefinitionKeyNotInOneKeyProvided() { + + // Given + // Start process instance, 2 tasks will be available with: + // - process definition key "taskDefinitionKeyProcess" + // - task definition keys "taskKey_1" & "taskKey_123" + runtimeService.startProcessInstanceByKey("taskDefinitionKeyProcess"); + + // When + var tasks = taskService.createTaskQuery() + .processDefinitionKey("taskDefinitionKeyProcess") + .taskDefinitionKeyNotIn("taskKey_1") + .list(); + // Then + assertThat(tasks) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("taskKey_123"); + } + + @Test + @Deployment(resources="org/camunda/bpm/engine/test/api/task/taskDefinitionProcess.bpmn20.xml") + public void testTaskDefinitionKeyNotInAllKeysProvided() { + + // Given + // Start process instance, 2 tasks will be available with: + // - process definition key "taskDefinitionKeyProcess" + // - task definition keys "taskKey_1" & "taskKey_123" + runtimeService.startProcessInstanceByKey("taskDefinitionKeyProcess"); + + // When + var tasks = taskService.createTaskQuery() + .processDefinitionKey("taskDefinitionKeyProcess") + .taskDefinitionKeyNotIn("taskKey_1", "taskKey_123") + .list(); + // Then + assertThat(tasks) + .isEmpty(); + } + + @Test + @Deployment(resources="org/camunda/bpm/engine/test/api/task/taskDefinitionProcess.bpmn20.xml") + public void testTaskDefinitionKeyNotInInvalidKeyProvided() { + + // Given + // Start process instance, 2 tasks will be available with: + // - process definition key "taskDefinitionKeyProcess" + // - task definition keys "taskKey_1" & "taskKey_123" + runtimeService.startProcessInstanceByKey("taskDefinitionKeyProcess"); + + // When + var tasks = taskService.createTaskQuery() + .processDefinitionKey("taskDefinitionKeyProcess") + .taskDefinitionKeyNotIn("I do not exist", "I don't exist either") + .list(); + // Then + assertThat(tasks) + .extracting(Task::getTaskDefinitionKey) + .containsExactly("taskKey_1", "taskKey_123"); + } + @Deployment(resources="org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml") @Test public void testTaskVariableNameEqualsIgnoreCase() throws Exception { @@ -5372,6 +5455,41 @@ public void testExtendingTaskQueryList_TaskDefinitionKeyIn() { assertEquals(taskDefinitionKey, key[0]); } + @Test + public void testExtendTaskQueryList_TaskDefinitionKeyNotIn() { + // given + var taskDefinitionKey = "someKey"; + var query = taskService.createTaskQuery() + .taskDefinitionKeyNotIn(taskDefinitionKey); + + var extendingQuery = taskService.createTaskQuery(); + + // when + var result = ((TaskQueryImpl)query).extend(extendingQuery); + + // then + assertThat(((TaskQueryImpl) result).getKeyNotIn()) + .containsExactly(taskDefinitionKey); + } + + @Test + public void testExtendingTaskQueryList_TaskDefinitionKeyNotIn() { + // given + var taskDefinitionKey = "someKey"; + var query = taskService.createTaskQuery(); + + var extendingQuery = taskService + .createTaskQuery() + .taskDefinitionKeyNotIn(taskDefinitionKey); + + // when + var result = ((TaskQueryImpl)query).extend(extendingQuery); + + // then + assertThat(((TaskQueryImpl) result).getKeyNotIn()) + .containsExactly(taskDefinitionKey); + } + @Test public void testQueryWithCandidateUsers() { BpmnModelInstance process = Bpmn.createExecutableProcess("process")