diff --git a/customer-work/src/main/java/com/customer/work/service/JsonUtils.java b/customer-work/src/main/java/com/customer/work/service/JsonUtils.java index c1b88e3..66319fb 100644 --- a/customer-work/src/main/java/com/customer/work/service/JsonUtils.java +++ b/customer-work/src/main/java/com/customer/work/service/JsonUtils.java @@ -44,7 +44,7 @@ public class JsonUtils { } public Map convertJsonNodeToMap(JsonNode jsonNode) { - return objectMapper.convertValue(jsonNode, new TypeReference>() {}); + return objectMapper.convertValue(jsonNode, new TypeReference<>() {}); } public Map flatten(Object payload) { @@ -57,7 +57,7 @@ public class JsonUtils { public Map unflatten(Map flatVars) { try { - return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), Map.class); + return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), new TypeReference<>() {}); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -73,7 +73,7 @@ public class JsonUtils { } public Map convertObjectNodeToMap(ObjectNode objectNode) { - return objectMapper.convertValue(objectNode, new TypeReference>() {}); + return objectMapper.convertValue(objectNode, new TypeReference<>() {}); } public ObjectNode getEmptyObjectNode() { diff --git a/customer-work/src/test/java/com/customer/work/model/EmailDto.java b/customer-work/src/test/java/com/customer/work/model/EmailDto.java index 9261cb2..05060a5 100644 --- a/customer-work/src/test/java/com/customer/work/model/EmailDto.java +++ b/customer-work/src/test/java/com/customer/work/model/EmailDto.java @@ -2,48 +2,5 @@ package com.customer.work.model; import java.util.List; -public class EmailDto { - private String subject; - private List receiverList; - private String content; - private Object contentRaw; - - public EmailDto(String subject, List receiverList, String content, Object contentRaw) { - this.subject = subject; - this.receiverList = receiverList; - this.content = content; - this.contentRaw = contentRaw; - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public List getReceiverList() { - return receiverList; - } - - public void setReceiverList(List receiverList) { - this.receiverList = receiverList; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public Object getContentRaw() { - return contentRaw; - } - - public void setContentRaw(Object contentRaw) { - this.contentRaw = contentRaw; - } +public record EmailDto(String subject, List receiverList, String content, Object contentRaw) { } diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java b/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java index 256f748..bb242b3 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java @@ -1,6 +1,5 @@ package com.customer.work.model; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -95,25 +94,23 @@ public class FlowableExcelMapper { } protected Object parseCellValue(String type, String cellValue) { - switch (cellValue) { - case "__NULL": return null; - case "__N_A": return "__N_A"; - } - switch (type) { - case "String": - switch (cellValue) { - case "__EMPTY": return ""; - case "__BLANK": return " "; - default: return cellValue; - } - case "Boolean": return Boolean.parseBoolean(cellValue); - case "Integer": return Integer.parseInt(cellValue); - case "Long": return Long.parseLong(cellValue); - case "Double": return Double.parseDouble(cellValue); - case "Date": return parseDate(cellValue); - default: - throw new IllegalArgumentException("Variable type not supported: " + type); - } + return switch (cellValue) { + case "__NULL" -> null; + case "__N_A" -> "__N_A"; + default -> switch (type) { + case "String" -> switch (cellValue) { + case "__EMPTY" -> ""; + case "__BLANK" -> " "; + default -> cellValue; + }; + case "Boolean" -> Boolean.parseBoolean(cellValue); + case "Integer" -> Integer.parseInt(cellValue); + case "Long" -> Long.parseLong(cellValue); + case "Double" -> Double.parseDouble(cellValue); + case "Date" -> parseDate(cellValue); + default -> throw new IllegalArgumentException("Variable type not supported: " + type); + }; + }; } protected Instant parseDate(String cellValue) { diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java b/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java index b101726..f026936 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java @@ -1,6 +1,5 @@ package com.customer.work.model; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -9,16 +8,18 @@ import org.springframework.stereotype.Component; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +/** + * Converts plain Java test data (maps, lists, scalars) into the variable types + * used by Flowable models. Maps and lists become JsonNodes by default; an inner + * map of the form {"__TYPE": "...", "__VALUE": ...} requests an explicit type. + */ @Component public class FlowableJsonParser { - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final JavaTimeModule javaTimeModule = new JavaTimeModule(); - public static final String BOOLEAN = "Boolean"; public static final String STRING = "String"; public static final String INTEGER = "Integer"; @@ -30,28 +31,36 @@ public class FlowableJsonParser { public static final String MAP = "Map"; public static final String ARRAY_LIST = "ArrayList"; + protected final ObjectMapper objectMapper = new ObjectMapper(); public FlowableJsonParser() { - objectMapper.registerModule(javaTimeModule); + // Enable ObjectMapper for handling Instant as string + objectMapper.registerModule(new JavaTimeModule()); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); } - public Map parseMap(Object map) throws JsonProcessingException, ClassCastException { - return (Map) parseObject(map); + public Map parseMap(Object map) { + Object parsed = parseObject(map); + if (!(parsed instanceof Map)) { + throw new IllegalArgumentException("Not a map: " + map); + } + @SuppressWarnings("unchecked") + Map result = (Map) parsed; + return result; } - public Object parseObject(Object object) throws JsonProcessingException, ClassCastException { - if (object instanceof Map) { - Map resultMap = new LinkedHashMap<>(Map.of()); - for (Map.Entry mapEntry : ((Map) object).entrySet()) { - String key = mapEntry.getKey(); + public Object parseObject(Object object) { + if (object instanceof Map map) { + Map resultMap = new LinkedHashMap<>(); + for (Map.Entry mapEntry : map.entrySet()) { + String key = String.valueOf(mapEntry.getKey()); resultMap.put(key, parseMapEntry(key, mapEntry.getValue())); } return resultMap; } - if (object instanceof ArrayList) { - ArrayList resultList = new ArrayList<>(); - for (Object arrayEntry : (ArrayList) object) { + if (object instanceof List list) { + List resultList = new ArrayList<>(); + for (Object arrayEntry : list) { resultList.add(parseArrayEntry(arrayEntry)); } return resultList; @@ -59,52 +68,51 @@ public class FlowableJsonParser { return null; } - private Object parseMapEntry(String key, Object value) throws JsonProcessingException { - if (value instanceof Map) { - Map valueMap = (Map) value; - if (valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE") && valueMap.size() == 2) { + private Object parseMapEntry(String key, Object value) { + if (value instanceof Map valueMap) { + if (valueMap.size() == 2 && valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE")) { // if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE) - return convertObjectExplicit(valueMap.get("__VALUE"), (String) valueMap.get("__TYPE")); + return convertObjectExplicit(valueMap.get("__VALUE"), String.valueOf(valueMap.get("__TYPE"))); } - if (Arrays.asList("__IN", "__OUT").contains(key)) { + if (FlowableModelTestUtils.IN_PARAM.equals(key) || FlowableModelTestUtils.OUT_PARAM.equals(key)) { // convert __IN and __OUT maps as Map return parseObject(value); } // convert Map to JsonObjectNode (default in models) return convertObjectExplicit(value, JSON_OBJECT_NODE); } - if (value instanceof ArrayList) { - // convert ArrayList to JsonArrayNode (default in models) + if (value instanceof List) { + // convert List to JsonArrayNode (default in models) return convertObjectExplicit(value, JSON_ARRAY_NODE); } // take json supported type (Boolean, String, Integer, Double) return value; } - private Object parseArrayEntry(Object value) throws JsonProcessingException { + private Object parseArrayEntry(Object value) { if (value instanceof Map) { // convert Map to JsonObjectNode (default in models) return convertObjectExplicit(value, JSON_OBJECT_NODE); } - if (value instanceof ArrayList) { - // convert ArrayList to JsonArrayNode (default in models) + if (value instanceof List) { + // convert List to JsonArrayNode (default in models) return convertObjectExplicit(value, JSON_ARRAY_NODE); } // take json supported type (Boolean, String, Integer, Double) return value; } - private Object convertObjectExplicit(Object value, String explicitType) throws JsonProcessingException { - switch (explicitType) { - case BOOLEAN: return Boolean.parseBoolean(value.toString()); - case STRING: return value.toString(); - case INTEGER: return Integer.parseInt(value.toString()); - case DOUBLE: return Double.parseDouble(value.toString()); - case LONG: return Long.parseLong(value.toString()); - case INSTANT: return Instant.parse(value.toString()); - case MAP: case ARRAY_LIST: return parseObject(value); - case JSON_OBJECT_NODE: case JSON_ARRAY_NODE: return objectMapper.convertValue(parseObject(value), JsonNode.class); - default: return value; - } + private Object convertObjectExplicit(Object value, String explicitType) { + return switch (explicitType) { + case BOOLEAN -> Boolean.parseBoolean(value.toString()); + case STRING -> value.toString(); + case INTEGER -> Integer.parseInt(value.toString()); + case DOUBLE -> Double.parseDouble(value.toString()); + case LONG -> Long.parseLong(value.toString()); + case INSTANT -> Instant.parse(value.toString()); + case MAP, ARRAY_LIST -> parseObject(value); + case JSON_OBJECT_NODE, JSON_ARRAY_NODE -> objectMapper.convertValue(parseObject(value), JsonNode.class); + default -> value; + }; } } diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java index 8f90ef2..c395ad1 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java @@ -122,8 +122,8 @@ public class FlowableModelTestUtils { Assertions.assertThat(emails.size()).as("%s: invalid email number %s", hint, emailNumber).isGreaterThanOrEqualTo(emailNumber); EmailDto email = emails.get(emailNumber - 1); - Assertions.assertThat(email.getSubject()).as("%s: invalid subject %s", hint, email.getSubject()).endsWith(subject); - Assertions.assertThat(email.getReceiverList()).as("%s: invalid email receivers", hint) + Assertions.assertThat(email.subject()).as("%s: invalid subject %s", hint, email.subject()).endsWith(subject); + Assertions.assertThat(email.receiverList()).as("%s: invalid email receivers", hint) .containsExactlyInAnyOrder(receivers.split("[,\\s]+")); } @@ -227,14 +227,11 @@ public class FlowableModelTestUtils { return true; } // If test is an object node, check if all fields in test exist in root - if (test.isObject()){ - for (Iterator iterator = test.fieldNames(); iterator.hasNext(); ) { - String fieldName = iterator.next(); - JsonNode testValue = test.get(fieldName); - JsonNode rootValue = null; - if (root != null) { - rootValue = root.get(fieldName); - } + if (test.isObject()) { + for (Map.Entry testField : test.properties()) { + String fieldName = testField.getKey(); + JsonNode testValue = testField.getValue(); + JsonNode rootValue = root != null ? root.get(fieldName) : null; if (testValue.isTextual() && "__N_A".equals(testValue.asText())) { if (root != null && root.has(fieldName)) { return false; @@ -276,12 +273,8 @@ public class FlowableModelTestUtils { ObjectNode vars = jsonUtils.getEmptyObjectNode(); JsonNode rootParam = row.get("root"); if (rootParam != null) { - Iterator> rootVars = rootParam.fields(); - while (rootVars.hasNext()) { - Map.Entry rootVar = rootVars.next(); - String key = rootVar.getKey(); - JsonNode value = rootVar.getValue(); - vars.set(key, value); + for (Map.Entry rootVar : rootParam.properties()) { + vars.set(rootVar.getKey(), rootVar.getValue()); } } JsonNode inParam = row.get("in"); @@ -335,20 +328,20 @@ public class FlowableModelTestUtils { public Map testObjExcelRow(String path, Map row) { Map vars = new LinkedHashMap<>(); - Map rootParam = (Map) row.get("root"); + Map rootParam = getMapParam(row, "root", path); if (rootParam != null) { vars.putAll(rootParam); } - Map inParam = (Map) row.get("in"); + Map inParam = getMapParam(row, "in", path); if (inParam != null) { vars.put(IN_PARAM, inParam); } - Map outParam = (Map) row.get("out"); + Map outParam = getMapParam(row, "out", path); if (outParam != null) { vars.put(OUT_PARAM, outParam); } String idParam = (String) row.get("id"); - Map test =(Map) row.get("test"); + Map test = getMapParam(row, "test", path); int timerCount = 0; Object timerNode = row.get("timer"); if (timerNode != null) timerCount = (Integer) timerNode; @@ -388,6 +381,16 @@ public class FlowableModelTestUtils { return result; } + @SuppressWarnings("unchecked") + protected Map getMapParam(Map row, String key, String path) { + Object value = row.get(key); + if (value == null) { + return null; + } + Assertions.assertThat(value).as("Parameter '%s' in %s must be an object", key, path).isInstanceOf(Map.class); + return (Map) value; + } + public void exportApp(String rootUrl, String workspaceKey, String appModelKey, String username, String password) { RestClient restClient = RestClient.builder() .baseUrl(rootUrl) @@ -416,7 +419,9 @@ public class FlowableModelTestUtils { public ObjectNode loadObjectNodeFromResources(String path) { if (path == null || path.isEmpty()) return jsonUtils.getEmptyObjectNode(); try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) { - Assertions.assertThat(inputStream).as("Resource not found: %s", path).isNotNull(); + if (inputStream == null) { + throw new IllegalArgumentException("Resource not found: " + path); + } JsonNode jsonNode = jsonUtils.convertJsonStringToJsonNode(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); Assertions.assertThat(jsonNode).as("Resource %s is not a JSON object", path).isInstanceOf(ObjectNode.class); return (ObjectNode) jsonNode; @@ -433,27 +438,21 @@ public class FlowableModelTestUtils { public Map loadTestVarsFromResources(String path) { ObjectNode node = loadObjectNodeFromResources(path); Map vars = new LinkedHashMap<>(); - for (Iterator> fields = node.fields(); fields.hasNext(); ) { - Map.Entry field = fields.next(); + for (Map.Entry field : node.properties()) { String key = field.getKey(); switch (key) { - case ROOT_PARAM: + case ROOT_PARAM -> { Assertions.assertThat(field.getValue().isObject()) .as("%s in %s must be a JSON object", ROOT_PARAM, path).isTrue(); - for (Iterator rootKeys = field.getValue().fieldNames(); rootKeys.hasNext(); ) { - String rootKey = rootKeys.next(); - Assertions.assertThat(rootKey.startsWith("__")) - .as("Root parameter '%s' in %s must not start with '__'", rootKey, path).isFalse(); + for (Map.Entry rootVar : field.getValue().properties()) { + Assertions.assertThat(rootVar.getKey().startsWith("__")) + .as("Root parameter '%s' in %s must not start with '__'", rootVar.getKey(), path).isFalse(); } vars.putAll(jsonUtils.convertJsonNodeToMap(field.getValue())); - break; - case IN_PARAM: - case OUT_PARAM: - vars.put(key, jsonUtils.convertJsonNodeToMap(field.getValue())); - break; - default: - throw new IllegalArgumentException("Implicit parameter '" + key + "' in " + path - + ": parameters must be declared explicitly inside " + ROOT_PARAM + ", " + IN_PARAM + " or " + OUT_PARAM); + } + case IN_PARAM, OUT_PARAM -> vars.put(key, jsonUtils.convertJsonNodeToMap(field.getValue())); + default -> throw new IllegalArgumentException("Implicit parameter '" + key + "' in " + path + + ": parameters must be declared explicitly inside " + ROOT_PARAM + ", " + IN_PARAM + " or " + OUT_PARAM); } } return vars; @@ -505,8 +504,8 @@ public class FlowableModelTestUtils { public void completeTask(String taskId, Map completionVariables, String outcome) { CompleteFormRepresentation form = new CompleteFormRepresentation(); - for (String key : completionVariables.keySet()) { - form.setValues(key, completionVariables.get(key)); + for (Map.Entry entry : completionVariables.entrySet()) { + form.setValues(entry.getKey(), entry.getValue()); } form.setOutcome(outcome); platformTaskService.completeTaskForm(taskId, form); @@ -526,8 +525,8 @@ public class FlowableModelTestUtils { return convertHistVariableListToMap(historicVariableInstanceList); } - private HashMap convertHistVariableListToMap(List historicVariableInstanceList) { - HashMap variableMap = new HashMap<>(); + private Map convertHistVariableListToMap(List historicVariableInstanceList) { + Map variableMap = new HashMap<>(); for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) { variableMap.put(historicVariableInstance.getVariableName(), historicVariableInstance.getValue()); } @@ -584,11 +583,9 @@ public class FlowableModelTestUtils { CallActivity callActivity = new CallActivity(); callActivity.setId(testKey); callActivity.setCalledElement(testKey); - JsonNode inNode = variables.get(IN_PARAM); - if (inNode instanceof ObjectNode) { - Map inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode); - ArrayList inParameters = new ArrayList<>(); - for (Map.Entry entry : inMap.entrySet()) { + if (variables.get(IN_PARAM) instanceof ObjectNode inNode) { + List inParameters = new ArrayList<>(); + for (Map.Entry entry : inNode.properties()) { IOParameter ioParameter = new IOParameter(); ioParameter.setSourceExpression("${" + IN_PARAM + "." + entry.getKey() + "}"); ioParameter.setTarget(entry.getKey()); @@ -596,14 +593,12 @@ public class FlowableModelTestUtils { } callActivity.setInParameters(inParameters); } - JsonNode outNode = variables.get(OUT_PARAM); - if (outNode instanceof ObjectNode) { - Map outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get(OUT_PARAM); - ArrayList outParameters = new ArrayList<>(); - for (Map.Entry entry : outMap.entrySet()) { + if (variables.get(OUT_PARAM) instanceof ObjectNode outNode) { + List outParameters = new ArrayList<>(); + for (Map.Entry entry : outNode.properties()) { IOParameter ioParameter = new IOParameter(); ioParameter.setSource(entry.getKey()); - ioParameter.setTarget(entry.getValue()); + ioParameter.setTarget(entry.getValue().asText()); outParameters.add(ioParameter); } callActivity.setOutParameters(outParameters); diff --git a/customer-work/src/test/resources/application.properties b/customer-work/src/test/resources/application.properties index 9fd4faf..50bda6c 100644 --- a/customer-work/src/test/resources/application.properties +++ b/customer-work/src/test/resources/application.properties @@ -7,7 +7,7 @@ flowable.async-executor-activate=false flowable.async-history-executor-activate=false flowable.indexing.enabled=false management.health.elasticsearch.enabled=false -management.metrics.export.elastic.enabled=false +management.elastic.metrics.export.enabled=false # Set debug level in tests logging.level.com.flowable=INFO diff --git a/customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip b/customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip new file mode 100644 index 0000000..21cb2ac Binary files /dev/null and b/customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip differ