From 21f7623e874446f0ba467ef587812f82ac4fdfbb Mon Sep 17 00:00:00 2001 From: Greg Harley <> Date: Tue, 10 Mar 2026 13:52:09 -0500 Subject: [PATCH] Update return code to 400 when invalid activity ids are provided --- .../dynamic/AbstractDynamicStateManager.java | 5 +- .../ChangeStateForCallActivityTest.java | 7 +-- ...cessInstanceMigrationCallActivityTest.java | 9 ++-- .../api/runtime/ExecutionResourceTest.java | 18 +++++++ ...stanceChangeActivityStateResourceTest.java | 48 +++++++++++++++++++ 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java index 6ef86924dd6..4bf791083e8 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java @@ -56,6 +56,7 @@ import org.flowable.bpmn.model.ValuedDataObject; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.impl.el.DefinitionVariableContainer; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; @@ -325,7 +326,7 @@ protected List resolveActiveExecutions(String processInstanceId .collect(Collectors.toList()); if (executions.isEmpty()) { - throw new FlowableException("Active execution could not be found with activity id " + activityId); + throw new FlowableIllegalArgumentException("Active execution could not be found with activity id " + activityId); } return executions; @@ -447,7 +448,7 @@ protected void prepareMoveExecutionEntityContainer(MoveExecutionEntityContainer protected FlowElement resolveFlowElementFromBpmnModel(BpmnModel bpmnModel, String activityId) { FlowElement flowElement = bpmnModel.getFlowElement(activityId); if (flowElement == null) { - throw new FlowableException("Cannot find activity '" + activityId + "' in process definition with id '" + bpmnModel.getMainProcess().getId() + "'"); + throw new FlowableIllegalArgumentException("Cannot find activity '" + activityId + "' in process definition with id '" + bpmnModel.getMainProcess().getId() + "'"); } return flowElement; } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForCallActivityTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForCallActivityTest.java index d85130bb91a..6d768c97bd6 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForCallActivityTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateForCallActivityTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.history.HistoryLevel; import org.flowable.engine.impl.test.HistoryTestHelper; @@ -355,15 +356,15 @@ public void testSetCurrentActivityInSubProcessInstanceSpecificVersion() { .processInstanceId(processInstance.getId()) .moveActivityIdToSubProcessInstanceActivityId("firstTask", "theTask", "callActivity") .changeState()) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'theTask' in process definition with id 'oneTaskProcess'"); - + //Invalid "unExistent" process definition version assertThatThrownBy(() -> runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getId()) .moveActivityIdToSubProcessInstanceActivityId("firstTask", "theTask", "callActivity", 5) .changeState()) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'theTask' in process definition with id 'oneTaskProcess'"); //Change state specifying the first version diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java index 10f84d39d40..e8b6d4f90b2 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java @@ -27,6 +27,7 @@ import org.assertj.core.api.Condition; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.migration.ActivityMigrationMapping; @@ -274,7 +275,7 @@ public void testValidationOfInvalidActivityInMigrationMappingToCallActivitySubPr "Invalid mapping for 'userTask1Id' to 'wrongActivityId', cannot be found in the process definition with id 'oneTaskProcess'")); assertThatThrownBy(() -> processInstanceMigrationBuilder.migrate(processInstance.getId())) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'wrongActivityId' in process definition with id 'oneTaskProcess'"); completeProcessInstanceTasks(processInstance.getId()); @@ -318,7 +319,7 @@ public void testValidationOfInvalidCallActivityInMigrateMappingToCallActivitySub "There's no call activity element with id 'wrongCallActivity' in the process definition with id 'twoTasksParentProcess'")); assertThatThrownBy(() -> processInstanceMigrationBuilder.migrate(processInstance.getId())) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'wrongCallActivity' in process definition with id 'twoTasksParentProcess'"); completeProcessInstanceTasks(processInstance.getId()); @@ -357,7 +358,7 @@ public void testMigrateMovingActivityIntoCallActivitySubProcessSpecificVersion() .isEqualTo(Collections.singletonList("Invalid mapping for 'theTask' to 'userTask1Id', cannot be found in the process definition with id 'MP'")); assertThatThrownBy(() -> processInstanceMigrationBuilder.migrate(processInstance.getId())) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'userTask1Id' in process definition with id 'MP'"); //Second migration attempt using and invalid "unExistent" version @@ -371,7 +372,7 @@ public void testMigrateMovingActivityIntoCallActivitySubProcessSpecificVersion() .isEqualTo(Collections.singletonList("Invalid mapping for 'theTask' to 'userTask1Id', cannot be found in the process definition with id 'MP'")); assertThatThrownBy(() -> processInstanceMigrationBuilder2.migrate(processInstance.getId())) - .isExactlyInstanceOf(FlowableException.class) + .isExactlyInstanceOf(FlowableIllegalArgumentException.class) .hasMessage("Cannot find activity 'userTask1Id' in process definition with id 'MP'"); //Second migration attempt specifies the version of the call activity subProcess diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionResourceTest.java index dad6cfcaebc..b969531e0dc 100644 --- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionResourceTest.java +++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionResourceTest.java @@ -22,6 +22,7 @@ import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.flowable.engine.runtime.Execution; @@ -308,4 +309,21 @@ public void testIllegalExecutionAction() throws Exception { CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_BAD_REQUEST); closeResponse(response); } + + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/ExecutionResourceTest.process-with-subprocess.bpmn20.xml" }) + public void testChangeExecutionActivityStateWithInvalidStartActivity() throws Exception { + runtimeService.startProcessInstanceByKey("processOne"); + Execution execution = runtimeService.createExecutionQuery().activityId("processTask").singleResult(); + assertThat(execution).isNotNull(); + + ObjectNode requestNode = objectMapper.createObjectNode(); + ArrayNode startActivityArray = requestNode.putArray("startActivityIds"); + startActivityArray.add("doesNotExist"); + + HttpPost httpPost = new HttpPost(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_EXECUTION, execution.getId()) + "/change-state"); + httpPost.setEntity(new StringEntity(requestNode.toString())); + CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_BAD_REQUEST); + closeResponse(response); + } } diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceChangeActivityStateResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceChangeActivityStateResourceTest.java index d01ddaae32d..cb0a8f2d575 100644 --- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceChangeActivityStateResourceTest.java +++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceChangeActivityStateResourceTest.java @@ -143,4 +143,52 @@ public void testChangeActivityStateManyToOneWithMI() throws Exception { assertProcessEnded(processInstance.getId()); } + + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/parallelTask.bpmn20.xml" }) + public void testChangeActivityStateManyToOneWithInvalidStartActivity() throws Exception { + Authentication.setAuthenticatedUserId("testUser"); + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("startParallelProcess") + .start(); + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + taskService.complete(task.getId()); + Authentication.setAuthenticatedUserId(null); + + ObjectNode requestNode = objectMapper.createObjectNode(); + ArrayNode cancelActivityArray = requestNode.putArray("cancelActivityIds"); + cancelActivityArray.add("task1"); + cancelActivityArray.add("task2"); + + ArrayNode startActivityArray = requestNode.putArray("startActivityIds"); + startActivityArray.add("doesNotExist"); + + HttpPost httpPost = new HttpPost(buildUrl(RestUrls.URL_PROCESS_INSTANCE_CHANGE_STATE, processInstance.getId())); + httpPost.setEntity(new StringEntity(requestNode.toString())); + CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_BAD_REQUEST); + closeResponse(response); + } + + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/parallelTask.bpmn20.xml" }) + public void testChangeActivityStateOneToManyWithInvalidCancelActivity() throws Exception { + Authentication.setAuthenticatedUserId("testUser"); + ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("startParallelProcess") + .start(); + Authentication.setAuthenticatedUserId(null); + + ObjectNode requestNode = objectMapper.createObjectNode(); + ArrayNode cancelActivityArray = requestNode.putArray("cancelActivityIds"); + cancelActivityArray.add("doesNotExist"); + + ArrayNode startActivityArray = requestNode.putArray("startActivityIds"); + startActivityArray.add("task1"); + startActivityArray.add("task2"); + + HttpPost httpPost = new HttpPost(buildUrl(RestUrls.URL_PROCESS_INSTANCE_CHANGE_STATE, processInstance.getId())); + httpPost.setEntity(new StringEntity(requestNode.toString())); + CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_BAD_REQUEST); + closeResponse(response); + } }