From 3c0bf9c16df67af8a872d9fae3451292135865be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Tue, 27 Aug 2024 10:25:25 +0200 Subject: [PATCH] Support UUID value type in flowable-rest API (#3856) --- .../runtime/CaseInstanceQueryImplTest.java | 68 +++++++++++++++++++ .../service/api/CmmnRestResponseFactory.java | 2 + .../CaseInstanceVariableResourceTest.java | 58 ++++++++++++++++ .../TaskVariablesCollectionResourceTest.java | 10 ++- .../variable/UUIDRestVariableConverter.java | 55 +++++++++++++++ .../service/api/DmnRestResponseFactory.java | 2 + .../rest/service/api/RestResponseFactory.java | 2 + .../ProcessInstanceVariableResourceTest.java | 60 ++++++++++++++++ 8 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 modules/flowable-common-rest/src/main/java/org/flowable/common/rest/variable/UUIDRestVariableConverter.java diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java index 8cddf467440..89db895889a 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceQueryImplTest.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -1726,6 +1727,73 @@ public void testQueryLocalDateTimeVariable() throws Exception { Assertions.assertThat(caseInstance).isNull(); } + @Test + public void testQueryUUIDVariable() throws Exception { + Map vars = new HashMap<>(); + UUID someUUID = UUID.randomUUID(); + vars.put("uuidVar", someUUID); + + CaseInstance caseInstance1 = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneTaskCase") + .variables(vars) + .start(); + + UUID someUUID2 = UUID.randomUUID(); + vars = new HashMap<>(); + vars.put("uuidVar", someUUID); + vars.put("uuidVar2", someUUID2); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneTaskCase") + .variables(vars) + .start(); + + UUID someUUID3 = UUID.randomUUID(); + vars = new HashMap<>(); + vars.put("uuidVar", someUUID3); + CaseInstance caseInstance3 = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneTaskCase") + .variables(vars) + .start(); + + // Query on single uuid variable, should result in 2 matches + CaseInstanceQuery query = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals("uuidVar", someUUID); + List caseInstances = query.list(); + Assertions.assertThat(caseInstances).hasSize(2); + + // Query on two uuid variables, should result in single value + query = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals("uuidVar", someUUID) + .variableValueEquals("uuidVar2", someUUID2); + CaseInstance caseInstance = query.singleResult(); + Assertions.assertThat(caseInstance).isNotNull(); + Assertions.assertThat(caseInstance.getId()).isEqualTo(caseInstance2.getId()); + + UUID unexistingUUID = UUID.randomUUID(); + // Query with unexisting variable value + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals("uuidVar", unexistingUUID).singleResult(); + Assertions.assertThat(caseInstance).isNull(); + + // Test NOT_EQUALS + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().variableValueNotEquals("uuidVar", someUUID).singleResult(); + Assertions.assertThat(caseInstance).isNotNull(); + Assertions.assertThat(caseInstance.getId()).isEqualTo(caseInstance3.getId()); + + // Test value-only matching + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals(someUUID3).singleResult(); + Assertions.assertThat(caseInstance).isNotNull(); + Assertions.assertThat(caseInstance.getId()).isEqualTo(caseInstance3.getId()); + + caseInstances = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals(someUUID).list(); + Assertions.assertThat(caseInstances) + .extracting(CaseInstance::getId) + .containsExactlyInAnyOrder( + caseInstance1.getId(), + caseInstance2.getId() + ); + + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().variableValueEquals(unexistingUUID).singleResult(); + Assertions.assertThat(caseInstance).isNull(); + } + @Test public void testLocalization() { CaseInstance createdCase = cmmnRuntimeService.createCaseInstanceBuilder() diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestResponseFactory.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestResponseFactory.java index 6693757a594..64b91fa3b17 100644 --- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestResponseFactory.java +++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestResponseFactory.java @@ -68,6 +68,7 @@ import org.flowable.common.rest.variable.RestVariableConverter; import org.flowable.common.rest.variable.ShortRestVariableConverter; import org.flowable.common.rest.variable.StringRestVariableConverter; +import org.flowable.common.rest.variable.UUIDRestVariableConverter; import org.flowable.dmn.api.DmnDecision; import org.flowable.eventsubscription.api.EventSubscription; import org.flowable.form.api.FormDefinition; @@ -1093,6 +1094,7 @@ protected void initializeVariableConverters() { variableConverters.add(new InstantRestVariableConverter()); variableConverters.add(new LocalDateRestVariableConverter()); variableConverters.add(new LocalDateTimeRestVariableConverter()); + variableConverters.add(new UUIDRestVariableConverter()); variableConverters.add(new JsonObjectRestVariableConverter(objectMapper)); } diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java index 5c2c2f806cb..0eac3d757e7 100644 --- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java +++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.http.HttpStatus; @@ -162,6 +163,30 @@ public void testGetCaseInstanceLocalDateTimeVariable() throws Exception { + "}"); } + @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" }) + public void testGetCaseInstanceUUIDVariable() throws Exception { + + CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start(); + UUID someUUID = UUID.fromString("26da81fb-18a6-4c19-b7b4-6877a568bfe1"); + runtimeService.setVariable(caseInstance.getId(), "variable", someUUID); + + CloseableHttpResponse response = executeRequest( + new HttpGet(SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE, + caseInstance.getId(), "variable")), HttpStatus.SC_OK); + + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + + closeResponse(response); + assertThat(responseNode).isNotNull(); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " name: 'variable'," + + " type: 'uuid'," + + " value: '" + someUUID + "'" + + "}"); + } + /** * Test getting a case instance variable data. GET cmmn-runtime/case-instances/{caseInstanceId}/variables/{variableName} */ @@ -427,4 +452,37 @@ public void testUpdateBinaryCaseVariable() throws Exception { assertThat(variableValue).isInstanceOf(byte[].class); assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content"); } + + @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" }) + public void testUpdateUUIDCaseVariable() throws Exception { + + UUID someUUID = UUID.fromString("87b859b2-d0c7-4845-93c2-e96ef69115b5"); + UUID someUUID2 = UUID.fromString("b2233abf-f84f-426f-b978-0d249b90cc45"); + CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase") + .variables(Collections.singletonMap("overlappingVariable", (Object) "caseValue")).start(); + runtimeService.setVariable(caseInstance.getId(), "uuidVariable", someUUID); + + // Update variable + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.put("name", "uuidVariable"); + requestNode.put("value", someUUID2.toString()); + requestNode.put("type", "uuid"); + + HttpPut httpPut = new HttpPut( + SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE, caseInstance.getId(), "uuidVariable")); + httpPut.setEntity(new StringEntity(requestNode.toString())); + CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_OK); + + assertThat(runtimeService.getVariable(caseInstance.getId(), "uuidVariable")).isEqualTo(someUUID2); + + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + closeResponse(response); + assertThat(responseNode).isNotNull(); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " scope: 'global'," + + " value: 'b2233abf-f84f-426f-b978-0d249b90cc45'" + + "}"); + } } diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TaskVariablesCollectionResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TaskVariablesCollectionResourceTest.java index e418d62d1f0..15df0728109 100644 --- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TaskVariablesCollectionResourceTest.java +++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/TaskVariablesCollectionResourceTest.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; @@ -71,6 +72,7 @@ public void testGetTaskVariables() throws Exception { caseVariables.put("dateProcVar", cal.getTime()); caseVariables.put("byteArrayProcVar", "Some raw bytes".getBytes()); caseVariables.put("overlappingVariable", "case-value"); + caseVariables.put("uuidVar", UUID.fromString("a053505c-43c9-479f-ae01-5352ce559786")); CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").variables(caseVariables).start(); // Set local task variables, including one that has the same name as one @@ -86,6 +88,7 @@ public void testGetTaskVariables() throws Exception { taskVariables.put("dateTaskVar", cal.getTime()); taskVariables.put("byteArrayTaskVar", "Some raw bytes".getBytes()); taskVariables.put("overlappingVariable", "task-value"); + taskVariables.put("uuidVar", UUID.fromString("a053505c-43c9-479f-ae01-5352ce559786")); taskService.setVariablesLocal(task.getId(), taskVariables); // Request all variables (no scope provides) which include global an local @@ -97,7 +100,7 @@ public void testGetTaskVariables() throws Exception { closeResponse(response); assertThat(responseNode).isNotNull(); assertThat(responseNode.isArray()).isTrue(); - assertThat(responseNode).hasSize(17); + assertThat(responseNode).hasSize(18); // Overlapping variable should contain task-value AND be defined as "local" assertThatJson(responseNode) @@ -148,6 +151,9 @@ public void testGetTaskVariables() throws Exception { + " }," + " {" + " scope: 'local'" + + " }," + + " {" + + " scope: 'local'" + " }" + "]"); @@ -160,7 +166,7 @@ public void testGetTaskVariables() throws Exception { closeResponse(response); assertThat(responseNode).isNotNull(); assertThat(responseNode.isArray()).isTrue(); - assertThat(responseNode).hasSize(9); + assertThat(responseNode).hasSize(10); assertThatJson(responseNode) .when(Option.IGNORING_EXTRA_FIELDS, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_ARRAY_ITEMS) .isEqualTo("[" diff --git a/modules/flowable-common-rest/src/main/java/org/flowable/common/rest/variable/UUIDRestVariableConverter.java b/modules/flowable-common-rest/src/main/java/org/flowable/common/rest/variable/UUIDRestVariableConverter.java new file mode 100644 index 00000000000..d271944016e --- /dev/null +++ b/modules/flowable-common-rest/src/main/java/org/flowable/common/rest/variable/UUIDRestVariableConverter.java @@ -0,0 +1,55 @@ +/* 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.common.rest.variable; + +import java.util.UUID; + +import org.flowable.common.engine.api.FlowableIllegalArgumentException; + +public class UUIDRestVariableConverter implements RestVariableConverter { + + @Override + public String getRestTypeName() { + return "uuid"; + } + + @Override + public Class getVariableType() { + return UUID.class; + } + + @Override + public Object getVariableValue(EngineRestVariable result) { + if (result.getValue() != null) { + if (!(result.getValue() instanceof String)) { + throw new FlowableIllegalArgumentException("Converter can only convert Strings"); + } + return UUID.fromString((String) result.getValue()); + } + return null; + } + + @Override + public void convertVariableValue(Object variableValue, EngineRestVariable result) { + if (variableValue != null) { + if (!(variableValue instanceof UUID)) { + throw new FlowableIllegalArgumentException("Converter can only convert UUIDs"); + } + result.setValue(((UUID)variableValue).toString()); + } else { + result.setValue(null); + } + } + +} diff --git a/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/DmnRestResponseFactory.java b/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/DmnRestResponseFactory.java index be0fc8a75d1..c8e9177a6db 100644 --- a/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/DmnRestResponseFactory.java +++ b/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/DmnRestResponseFactory.java @@ -29,6 +29,7 @@ import org.flowable.common.rest.variable.RestVariableConverter; import org.flowable.common.rest.variable.ShortRestVariableConverter; import org.flowable.common.rest.variable.StringRestVariableConverter; +import org.flowable.common.rest.variable.UUIDRestVariableConverter; import org.flowable.dmn.api.DecisionExecutionAuditContainer; import org.flowable.dmn.api.DecisionServiceExecutionAuditContainer; import org.flowable.dmn.api.DmnDecision; @@ -302,6 +303,7 @@ protected void initializeVariableConverters() { variableConverters.add(new InstantRestVariableConverter()); variableConverters.add(new LocalDateRestVariableConverter()); variableConverters.add(new LocalDateTimeRestVariableConverter()); + variableConverters.add(new UUIDRestVariableConverter()); } protected DmnRestUrlBuilder createUrlBuilder() { diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestResponseFactory.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestResponseFactory.java index 63f46c051ae..4fb5a84974e 100644 --- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestResponseFactory.java +++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestResponseFactory.java @@ -38,6 +38,7 @@ import org.flowable.common.rest.variable.RestVariableConverter; import org.flowable.common.rest.variable.ShortRestVariableConverter; import org.flowable.common.rest.variable.StringRestVariableConverter; +import org.flowable.common.rest.variable.UUIDRestVariableConverter; import org.flowable.dmn.api.DmnDecision; import org.flowable.engine.form.FormData; import org.flowable.engine.form.FormProperty; @@ -1528,6 +1529,7 @@ protected void initializeVariableConverters() { variableConverters.add(new LocalDateRestVariableConverter()); variableConverters.add(new LocalDateTimeRestVariableConverter()); variableConverters.add(new JsonObjectRestVariableConverter(objectMapper)); + variableConverters.add(new UUIDRestVariableConverter()); } protected String formatUrl(String serverRootUrl, String[] fragments, Object... arguments) { diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java index f4265ec0ca2..e7298b3b11b 100644 --- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java +++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.http.HttpStatus; @@ -178,6 +179,31 @@ public void testGetProcessInstanceLocalDateTimeVariable() throws Exception { + "}"); } + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.testProcess.bpmn20.xml" }) + public void testGetProcessInstanceUUIDVariable() throws Exception { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess"); + UUID someUUID = UUID.fromString("239969dd-3310-4068-b558-e4cbce5650ea"); + runtimeService.setVariable(processInstance.getId(), "variable", someUUID); + + CloseableHttpResponse response = executeRequest( + new HttpGet( + SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE, processInstance.getId(), "variable")), + HttpStatus.SC_OK); + + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + + closeResponse(response); + assertThat(responseNode).isNotNull(); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " name: 'variable'," + + " type: 'uuid'," + + " value: '" + someUUID + "'" + + "}"); + } + /** * Test getting a process instance variable data. GET runtime/process-instances/{processInstanceId}/variables/{variableName} */ @@ -415,6 +441,40 @@ public void testUpdateLocalDateTimeProcessVariable() throws Exception { + "}"); } + @Test + @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.testProcess.bpmn20.xml" }) + public void testUpdateUUIDProcessVariable() throws Exception { + UUID someUUID = UUID.fromString("239969dd-3310-4068-b558-e4cbce5650ea"); + UUID someUUID2 = UUID.fromString("c5b16e77-0c15-4d7b-ac12-15352af76355"); + ProcessInstance processInstance = runtimeService + .startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("overlappingVariable", (Object) "processValue")); + runtimeService.setVariable(processInstance.getId(), "uuidVariable", someUUID); + + // Update variable + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.put("name", "uuidVariable"); + requestNode.put("value", someUUID2.toString()); + requestNode.put("type", "uuid"); + + HttpPut httpPut = new HttpPut( + SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE, processInstance.getId(), "uuidVariable")); + httpPut.setEntity(new StringEntity(requestNode.toString())); + CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_OK); + + assertThat(runtimeService.getVariable(processInstance.getId(), "uuidVariable")) + .isEqualTo(someUUID2); + + JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); + closeResponse(response); + assertThat(responseNode).isNotNull(); + assertThatJson(responseNode) + .when(Option.IGNORING_EXTRA_FIELDS) + .isEqualTo("{" + + " scope: null," + + " value: 'c5b16e77-0c15-4d7b-ac12-15352af76355'" + + "}"); + } + /** * Test updating a single process variable using a binary stream. PUT runtime/process-instances/{processInstanceId}/variables/{variableName} */