From 0069d6a13f20189430405e18e812b2dce4460d8c Mon Sep 17 00:00:00 2001 From: Christopher Welsch <75045836+WelschChristopher@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:21:15 +0200 Subject: [PATCH] =?UTF-8?q?ProcessInstanceCollectionResource=20=20and=20Ca?= =?UTF-8?q?seInstanceCollectionResource=E2=80=A6=20(#3956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ProcessInstanceCollectionResource and CaseInstanceCollectionResource now support sending transientVariables,variables and startFormVariables in one request. * added missing license header * testcase cleanup --------- Co-authored-by: Christopher Welsch --- .../caze/CaseInstanceCollectionResource.java | 44 ++++----- .../CaseInstanceCollectionResourceTest.java | 85 ++++++++++++++++++ .../runtime/TransientVariableServiceTask.java | 26 ++++++ .../caseWithStartFormAndServiceTask.cmmn | 35 ++++++++ .../ProcessInstanceCollectionResource.java | 40 ++++----- ...ProcessInstanceCollectionResourceTest.java | 89 +++++++++++++++++++ .../runtime/TransientVariableServiceTask.java | 27 ++++++ ...cess-with-form-and-service-task.bpmn20.xml | 26 ++++++ 8 files changed, 331 insertions(+), 41 deletions(-) create mode 100644 modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TransientVariableServiceTask.java create mode 100644 modules/flowable-cmmn-rest/src/test/resources/org/flowable/cmmn/rest/service/api/runtime/caseWithStartFormAndServiceTask.cmmn create mode 100644 modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/TransientVariableServiceTask.java create mode 100644 modules/flowable-rest/src/test/resources/org/flowable/rest/service/api/runtime/ProcessInstanceResourceTest.process-with-form-and-service-task.bpmn20.xml diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceCollectionResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceCollectionResource.java index 180fa421ab5..08943536de0 100644 --- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceCollectionResource.java +++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceCollectionResource.java @@ -278,10 +278,12 @@ public DataResponse getCaseInstances(@ApiParam(hidden = tr @ApiOperation(value = "Start a case instance", tags = { "Case Instances" }, notes = "Note that also a *transientVariables* property is accepted as part of this json, that follows the same structure as the *variables* property.\n\n" - + "Only one of *caseDefinitionId* or *caseDefinitionKey* an be used in the request body. \n\n" - + "Parameters *businessKey*, *variables* and *tenantId* are optional.\n\n " - + "If tenantId is omitted, the default tenant will be used. More information about the variable format can be found in the REST variables section.\n\n " - + "Note that the variable-scope that is supplied is ignored, process-variables are always local.\n\n", + + "Only one of *caseDefinitionId* or *caseDefinitionKey* an be used in the request body.\n\n" + + "Parameters *businessKey*, *variables* and *tenantId* are optional.\n\n" + + "If tenantId is omitted, the default tenant will be used.\n\n " + + "It is possible to send variables, transientVariables and startFormVariables in one request.\n\n" + + "More information about the variable format can be found in the REST variables section.\n\n " + + "Note that the variable-scope that is supplied is ignored, case-variables are always local.\n\n", code = 201) @ApiResponses(value = { @ApiResponse(code = 201, message = "Indicates the case instance was created."), @@ -319,26 +321,26 @@ public CaseInstanceResponse createCaseInstance(@RequestBody CaseInstanceCreateRe } startFormVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } - - } else { - if (request.getVariables() != null) { - startVariables = new HashMap<>(); - for (RestVariable variable : request.getVariables()) { - if (variable.getName() == null) { - throw new FlowableIllegalArgumentException("Variable name is required."); - } - startVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); + + } + + if (request.getVariables() != null) { + startVariables = new HashMap<>(); + for (RestVariable variable : request.getVariables()) { + if (variable.getName() == null) { + throw new FlowableIllegalArgumentException("Variable name is required."); } + startVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } - - if (request.getTransientVariables() != null) { - transientVariables = new HashMap<>(); - for (RestVariable variable : request.getTransientVariables()) { - if (variable.getName() == null) { - throw new FlowableIllegalArgumentException("Variable name is required."); - } - transientVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); + } + + if (request.getTransientVariables() != null) { + transientVariables = new HashMap<>(); + for (RestVariable variable : request.getTransientVariables()) { + if (variable.getName() == null) { + throw new FlowableIllegalArgumentException("Variable name is required."); } + transientVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } } diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceCollectionResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceCollectionResourceTest.java index 82488ca7c83..ac354e57711 100644 --- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceCollectionResourceTest.java +++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceCollectionResourceTest.java @@ -16,6 +16,7 @@ import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.when; import java.io.IOException; @@ -951,4 +952,88 @@ public void testQueryByParentScopeId() throws IOException { } + @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/runtime/caseWithStartFormAndServiceTask.cmmn" }) + public void testAllVariablesAreApplied() { + CaseDefinition caseDefinition = repositoryService.createCaseDefinitionQuery().caseDefinitionKey("caseWithStartFormAndServiceTask").singleResult(); + + runUsingMocks(() -> { + Map engineConfigurations = cmmnEngineConfiguration.getEngineConfigurations(); + engineConfigurations.put(EngineConfigurationConstants.KEY_FORM_ENGINE_CONFIG, formEngineConfiguration); + + FormInfo formInfo = new FormInfo(); + formInfo.setId("formDefId"); + formInfo.setKey("formDefKey"); + formInfo.setName("Form Definition Name"); + + when(formEngineConfiguration.getFormRepositoryService()).thenReturn(formRepositoryService); + when(formRepositoryService.getFormModelByKeyAndParentDeploymentId("form1", caseDefinition.getDeploymentId(), caseDefinition.getTenantId(), + cmmnEngineConfiguration.isFallbackToDefaultTenant())) + .thenReturn(formInfo); + + String url = CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_DEFINITION_START_FORM, caseDefinition.getId()); + CloseableHttpResponse response = executeRequest(new HttpGet(SERVER_URL_PREFIX + url), HttpStatus.SC_OK); + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + closeResponse(response); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " id: 'formDefId'," + + " key: 'formDefKey'," + + " name: 'Form Definition Name'," + + " type: 'startForm'," + + " definitionKey: 'caseWithStartFormAndServiceTask'" + + "}"); + + ArrayNode formVariablesNode = objectMapper.createArrayNode(); + + // String variable + ObjectNode stringVarNode = formVariablesNode.addObject(); + stringVarNode.put("name", "user"); + stringVarNode.put("value", "simple string value"); + stringVarNode.put("type", "string"); + + ObjectNode integerVarNode = formVariablesNode.addObject(); + integerVarNode.put("name", "number"); + integerVarNode.put("value", 1234); + integerVarNode.put("type", "integer"); + + ArrayNode variablesNode = objectMapper.createArrayNode(); + stringVarNode = variablesNode.addObject(); + stringVarNode.put("name", "userVariable"); + stringVarNode.put("value", "simple string value"); + stringVarNode.put("type", "string"); + + ArrayNode transientVariablesNode = objectMapper.createArrayNode(); + stringVarNode = transientVariablesNode.addObject(); + stringVarNode.put("name", "userTransient"); + stringVarNode.put("value", "simple transient value"); + stringVarNode.put("type", "string"); + + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.set("startFormVariables", formVariablesNode); + requestNode.set("variables", variablesNode); + requestNode.set("transientVariables", transientVariablesNode); + requestNode.put("caseDefinitionKey", "caseWithStartFormAndServiceTask"); + + when(formRepositoryService.getFormModelByKeyAndParentDeploymentId("form1", caseDefinition.getDeploymentId())) + .thenReturn(formInfo); + when(formEngineConfiguration.getFormService()).thenReturn(formEngineFormService); + when(formEngineFormService.getVariablesFromFormSubmission(null, "planModel", null, caseDefinition.getId(), ScopeTypes.CMMN, formInfo, + Map.of("user", "simple string value", "number", 1234), null)) + .thenReturn(Map.of("user", "simple string value return", "number", 1234L)); + + HttpPost httpPost = new HttpPost(SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_COLLECTION)); + httpPost.setEntity(new StringEntity(requestNode.toString())); + response = executeRequest(httpPost, HttpStatus.SC_CREATED); + responseNode = objectMapper.readTree(response.getEntity().getContent()); + + String instanceId = responseNode.get("id").asText(); + assertThat(runtimeService.getVariables(instanceId).entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue).containsExactlyInAnyOrder( + tuple("user", "simple string value return"), + tuple("number", 1234L), + tuple("userVariable", "simple string value"), + tuple("userTransient", "simple transient value") + ); + }); + } } diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TransientVariableServiceTask.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TransientVariableServiceTask.java new file mode 100644 index 00000000000..c88b0b52694 --- /dev/null +++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TransientVariableServiceTask.java @@ -0,0 +1,26 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.cmmn.rest.service.api.runtime; + +import org.flowable.cmmn.api.delegate.DelegatePlanItemInstance; +import org.flowable.cmmn.api.delegate.PlanItemJavaDelegate; + +/** + * Processes the transient variable and puts the relevant bits in real variables + */ +public class TransientVariableServiceTask implements PlanItemJavaDelegate { + @Override + public void execute(DelegatePlanItemInstance planItemInstance) { + planItemInstance.getTransientVariables().forEach((s, o) -> planItemInstance.setVariable(s, o.toString())); + } +} diff --git a/modules/flowable-cmmn-rest/src/test/resources/org/flowable/cmmn/rest/service/api/runtime/caseWithStartFormAndServiceTask.cmmn b/modules/flowable-cmmn-rest/src/test/resources/org/flowable/cmmn/rest/service/api/runtime/caseWithStartFormAndServiceTask.cmmn new file mode 100644 index 00000000000..6e688909d78 --- /dev/null +++ b/modules/flowable-cmmn-rest/src/test/resources/org/flowable/cmmn/rest/service/api/runtime/caseWithStartFormAndServiceTask.cmmn @@ -0,0 +1,35 @@ + + + + + A human task case + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceCollectionResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceCollectionResource.java index 1cca070431d..a873629e4d4 100644 --- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceCollectionResource.java +++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceCollectionResource.java @@ -287,8 +287,10 @@ public DataResponse getProcessInstances(@ApiParam(hidde @ApiOperation(value = "Start a process instance", tags = { "Process Instances" }, notes = "Note that also a *transientVariables* property is accepted as part of this json, that follows the same structure as the *variables* property.\n\n" + "Only one of *processDefinitionId*, *processDefinitionKey* or *message* can be used in the request body. \n\n" - + "Parameters *businessKey*, *variables* and *tenantId* are optional.\n\n " - + "If tenantId is omitted, the default tenant will be used. More information about the variable format can be found in the REST variables section.\n\n " + + "Parameters *businessKey*, *variables* and *tenantId* are optional.\n\n" + + "If tenantId is omitted, the default tenant will be used.\n\n " + + "It is possible to send variables, transientVariables and startFormVariables in one request.\n\n" + + "More information about the variable format can be found in the REST variables section.\n\n " + "Note that the variable-scope that is supplied is ignored, process-variables are always local.\n\n", code = 201) @ApiResponses(value = { @@ -327,27 +329,25 @@ public ProcessInstanceResponse createProcessInstance(@RequestBody ProcessInstanc } startFormVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } - - } else { - - if (request.getVariables() != null && !request.getVariables().isEmpty()) { - startVariables = new HashMap<>(); - for (RestVariable variable : request.getVariables()) { - if (variable.getName() == null) { - throw new FlowableIllegalArgumentException("Variable name is required."); - } - startVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); + } + + if (request.getVariables() != null && !request.getVariables().isEmpty()) { + startVariables = new HashMap<>(); + for (RestVariable variable : request.getVariables()) { + if (variable.getName() == null) { + throw new FlowableIllegalArgumentException("Variable name is required."); } + startVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } - - if (request.getTransientVariables() != null && !request.getTransientVariables().isEmpty()) { - transientVariables = new HashMap<>(); - for (RestVariable variable : request.getTransientVariables()) { - if (variable.getName() == null) { - throw new FlowableIllegalArgumentException("Variable name is required."); - } - transientVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); + } + + if (request.getTransientVariables() != null && !request.getTransientVariables().isEmpty()) { + transientVariables = new HashMap<>(); + for (RestVariable variable : request.getTransientVariables()) { + if (variable.getName() == null) { + throw new FlowableIllegalArgumentException("Variable name is required."); } + transientVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable)); } } diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceCollectionResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceCollectionResourceTest.java index 83ffb46a828..27e1f0ae3af 100644 --- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceCollectionResourceTest.java +++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceCollectionResourceTest.java @@ -16,6 +16,7 @@ import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.when; import java.io.IOException; @@ -1065,4 +1066,92 @@ public void testQueryByParentScopeId() throws IOException { + " ]" + "}"); } + + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceResourceTest.process-with-form-and-service-task.bpmn20.xml" }) + public void testAllVariablesAreApplied() throws Exception { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("processOne").singleResult(); + + FormInfo formInfo = new FormInfo(); + formInfo.setId("formDefId"); + formInfo.setKey("formDefKey"); + formInfo.setName("Form Definition Name"); + + when(formEngineConfiguration.getFormRepositoryService()).thenReturn(formRepositoryService); + when(formRepositoryService.getFormModelByKeyAndParentDeploymentId("form1", processDefinition.getDeploymentId(), processDefinition.getTenantId(), + processEngineConfiguration.isFallbackToDefaultTenant())) + .thenReturn(formInfo); + + String url = RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_START_FORM, processDefinition.getId()); + CloseableHttpResponse response = executeRequest(new HttpGet(SERVER_URL_PREFIX + url), HttpStatus.SC_OK); + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + closeResponse(response); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS, Option.IGNORING_ARRAY_ORDER) + .isEqualTo("{" + + " id: 'formDefId'," + + " key: 'formDefKey'," + + " name: 'Form Definition Name'," + + " type: 'startForm'," + + " definitionKey: 'processOne'" + + "}"); + + ArrayNode formVariablesNode = objectMapper.createArrayNode(); + + // String variable + ObjectNode stringVarNode = formVariablesNode.addObject(); + stringVarNode.put("name", "user"); + stringVarNode.put("value", "simple string value"); + stringVarNode.put("type", "string"); + + ObjectNode integerVarNode = formVariablesNode.addObject(); + integerVarNode.put("name", "number"); + integerVarNode.put("value", 1234); + integerVarNode.put("type", "integer"); + + ArrayNode variablesNode = objectMapper.createArrayNode(); + stringVarNode = variablesNode.addObject(); + stringVarNode.put("name", "userVariable"); + stringVarNode.put("value", "simple string value"); + stringVarNode.put("type", "string"); + + ArrayNode transientVariablesNode = objectMapper.createArrayNode(); + stringVarNode = transientVariablesNode.addObject(); + stringVarNode.put("name", "userTransient"); + stringVarNode.put("value", "simple transient value"); + stringVarNode.put("type", "string"); + + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.set("startFormVariables", formVariablesNode); + requestNode.set("variables", variablesNode); + requestNode.set("transientVariables", transientVariablesNode); + requestNode.put("processDefinitionKey", "processOne"); + + when(formRepositoryService.getFormModelByKeyAndParentDeploymentId("form1", processDefinition.getDeploymentId())) + .thenReturn(formInfo); + when(formEngineConfiguration.getFormService()).thenReturn(formEngineFormService); + when(formEngineFormService.getVariablesFromFormSubmission("theStart", "startEvent", null, + processDefinition.getId(), ScopeTypes.BPMN, formInfo, Map.of("user", "simple string value", "number", 1234), null)) + .thenReturn(Map.of("user", "simple string value return", "number", 1234L)); + + HttpPost httpPost = new HttpPost(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_COLLECTION)); + httpPost.setEntity(new StringEntity(requestNode.toString())); + response = executeRequest(httpPost, HttpStatus.SC_CREATED); + + responseNode = objectMapper.readTree(response.getEntity().getContent()); + closeResponse(response); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " ended: false" + + "}"); + + String processInstanceId = responseNode.get("id").asText(); + assertThat(runtimeService.getVariables(processInstanceId).entrySet()).extracting(Map.Entry::getKey, Map.Entry::getValue).containsExactlyInAnyOrder( + tuple("user", "simple string value return"), + tuple("number", 1234L), + tuple("userVariable", "simple string value"), + tuple("userTransient", "simple transient value") + ); + } } diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/TransientVariableServiceTask.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/TransientVariableServiceTask.java new file mode 100644 index 00000000000..f25e2721f6b --- /dev/null +++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/TransientVariableServiceTask.java @@ -0,0 +1,27 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.rest.service.api.runtime; + +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; + +/** + * Processes the transient variable and puts the relevant bits in real variables + */ +public class TransientVariableServiceTask implements JavaDelegate { + + @Override + public void execute(DelegateExecution execution) { + execution.getTransientVariables().forEach((s, o) -> execution.setVariable(s, o.toString())); + } +} diff --git a/modules/flowable-rest/src/test/resources/org/flowable/rest/service/api/runtime/ProcessInstanceResourceTest.process-with-form-and-service-task.bpmn20.xml b/modules/flowable-rest/src/test/resources/org/flowable/rest/service/api/runtime/ProcessInstanceResourceTest.process-with-form-and-service-task.bpmn20.xml new file mode 100644 index 00000000000..4b32cd8b474 --- /dev/null +++ b/modules/flowable-rest/src/test/resources/org/flowable/rest/service/api/runtime/ProcessInstanceResourceTest.process-with-form-and-service-task.bpmn20.xml @@ -0,0 +1,26 @@ + + + + One task process description + + + + + + + + + Process task description + + + + + + + + \ No newline at end of file