From 5434721c8e83f9b9993453d9ea001a218c859a27 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 11 Dec 2024 14:40:06 -0600 Subject: [PATCH 1/4] Add NullKeyToEmptyStringSerializer - to allow jackson serialization of a map with a null key --- .../qqq/backend/core/utils/JsonUtils.java | 45 +++++++++++++++++++ .../qqq/backend/core/utils/JsonUtilsTest.java | 30 ++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java index 7c000e3e..325e842f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java @@ -30,11 +30,14 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.data.QRecord; @@ -54,6 +57,11 @@ public class JsonUtils { private static final QLogger LOG = QLogger.getLogger(JsonUtils.class); + ////////////////////////////////////////////////////////////////////// + // see https://www.baeldung.com/jackson-map-null-values-or-null-key // + ////////////////////////////////////////////////////////////////////// + public static NullKeyToEmptyStringSerializer nullKeyToEmptyStringSerializer = new NullKeyToEmptyStringSerializer(); + /******************************************************************************* @@ -396,4 +404,41 @@ public class JsonUtils return (record); } + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class NullKeyToEmptyStringSerializer extends StdSerializer + { + /*************************************************************************** + ** + ***************************************************************************/ + public NullKeyToEmptyStringSerializer() + { + this(null); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public NullKeyToEmptyStringSerializer(Class t) + { + super(t); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused) throws IOException + { + jsonGenerator.writeFieldName(""); + } + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java index 8e88739a..bbb4c88a 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java @@ -35,11 +35,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -81,7 +83,7 @@ class JsonUtilsTest extends BaseTest { objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); }); - + assertThat(json).contains(""" "values":{"foo":"Foo","bar":3.14159,"baz":null}"""); } @@ -318,4 +320,30 @@ class JsonUtilsTest extends BaseTest assertEquals("age", qQueryFilter.getOrderBys().get(0).getFieldName()); assertTrue(qQueryFilter.getOrderBys().get(0).getIsAscending()); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNullKeyInMap() + { + Map mapWithNullKey = MapBuilder.of(null, "foo"); + + ////////////////////////////////////////////////////// + // assert default behavior throws with null map key // + ////////////////////////////////////////////////////// + assertThatThrownBy(() -> JsonUtils.toJson(mapWithNullKey)).rootCause().hasMessageContaining("Null key for a Map not allowed in JSON"); + + //////////////////////////////////////////////////////////////////////// + // assert that the nullKeyToEmptyStringSerializer does what we expect // + //////////////////////////////////////////////////////////////////////// + assertEquals(""" + {"":"foo"}""", JsonUtils.toJson(mapWithNullKey, mapper -> + { + mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer); + })); + } + } From 63a48eeafaf8df2a155575d7047e7953c9c4afdc Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 11 Dec 2024 14:59:08 -0600 Subject: [PATCH 2/4] Avoid exceptions from jackson serialization of processValues that contain a map with a null key --- .../javalin/QJavalinProcessHandler.java | 29 +++++++++++++--- .../ProcessInitOrStepOrStatusResponseV1.java | 2 ++ .../specs/v1/utils/ProcessSpecUtilsV1.java | 34 +++++++++++++++++-- .../javalin/QJavalinProcessHandlerTest.java | 24 +++++++++++++ .../qqq/backend/javalin/TestUtils.java | 25 ++++++++++++++ .../specs/v1/ProcessInitSpecV1Test.java | 22 ++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index bb00bfab..1ed7e826 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -352,6 +352,8 @@ public class QJavalinProcessHandler Map resultForCaller = new HashMap<>(); Exception returningException = null; + String processName = context.pathParam("processName"); + try { if(processUUID == null) @@ -360,7 +362,6 @@ public class QJavalinProcessHandler } resultForCaller.put("processUUID", processUUID); - String processName = context.pathParam("processName"); LOG.info(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]" : "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]"); @@ -441,10 +442,30 @@ public class QJavalinProcessHandler // negative side-effects - but be aware. // // One could imagine that we'd need this to be configurable in the future? // /////////////////////////////////////////////////////////////////////////////////// - context.result(JsonUtils.toJson(resultForCaller, mapper -> + try { - mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); - })); + String json = JsonUtils.toJson(resultForCaller, mapper -> + { + mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + // use this custom serializer to convert null map-keys to empty-strings (rather than having an exception!) // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer); + }); + context.result(json); + } + catch(Exception e) + { + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // related to the change above - we've seen at least one new error that can come from the // + // Include.ALWAYS change (a null key in a map -> jackson exception). So, try-catch around // + // the above serialization, and if it does throw, log, but continue trying a default serialization // + // as that is probably preferable to an exception for the caller... // + ///////////////////////////////////////////////////////////////////////////////////////////////////// + LOG.warn("Error deserializing process results with serializationInclusion:ALWAYS - will retry with default settings", e, logPair("processName", processName), logPair("processUUID", processUUID)); + context.result(JsonUtils.toJson(resultForCaller)); + } } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java index 5723c3c2..cc81bacb 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/responses/ProcessInitOrStepOrStatusResponseV1.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.kingsrook.qqq.middleware.javalin.executors.io.ProcessInitOrStepOrStatusOutputInterface; import com.kingsrook.qqq.middleware.javalin.schemabuilder.SchemaBuilder; import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema; @@ -117,6 +118,7 @@ public class ProcessInitOrStepOrStatusResponseV1 implements ProcessInitOrStepOrS ** Getter for values ** *******************************************************************************/ + @JsonIgnore // we are doing custom serialization of the values map, so mark as ignore. public Map getValues() { return values; diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java index 9b1ffca7..761baa5d 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java @@ -183,6 +183,14 @@ public class ProcessSpecUtilsV1 String name = valueEntry.getKey(); Serializable value = valueEntry.getValue(); + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // follow the strategy that we use for JsonUtils.nullKeyToEmptyStringSerializer in this rare case... // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + if(name == null) + { + name = ""; + } + Serializable valueToMakeIntoJson = value; if(value instanceof String s) { @@ -213,11 +221,31 @@ public class ProcessSpecUtilsV1 valueToMakeIntoJson = new WidgetBlock(abstractBlockWidgetData); } - String valueAsJsonString = JsonUtils.toJson(valueToMakeIntoJson, mapper -> + /////////////////////////////////////////////// + // ok now, make the value into a JSON string // + /////////////////////////////////////////////// + String valueAsJsonString; + try { - mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); - }); + valueAsJsonString = JsonUtils.toJson(valueToMakeIntoJson, mapper -> + { + mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + // use this custom serializer to convert null map-keys to empty-strings (rather than having an exception!) // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer); + }); + } + catch(Exception e) + { + LOG.warn("Error deserializing process results with serializationInclusion:ALWAYS - will retry with default settings", e); + valueAsJsonString = JsonUtils.toJson(valueToMakeIntoJson); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + // THEN - make it back into a JSONObject or JSONArray, and add it to the valuesAsJsonObject JSONObject // + ///////////////////////////////////////////////////////////////////////////////////////////////////////// if(valueAsJsonString.startsWith("[")) { valuesAsJsonObject.put(name, new JSONArray(valueAsJsonString)); diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java index 563ad04a..6c5298ed 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java @@ -655,4 +655,28 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase assertEquals(200, response.getStatus()); } + + + /******************************************************************************* + ** test running a process who has a value with a null key. + * + ** This was a regression - that threw an exception from jackson at one point in time. + ** + ** Note: ported to v1 + *******************************************************************************/ + @Test + public void test_processPutsNullKeyInMap() + { + HttpResponse response = Unirest.get(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_PUTS_NULL_KEY_IN_MAP + "/init").asString(); + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertNotNull(jsonObject); + JSONObject values = jsonObject.getJSONObject("values"); + JSONObject mapWithNullKey = values.getJSONObject("mapWithNullKey"); + assertTrue(mapWithNullKey.has("")); // null key currently set to become empty-string key... + assertEquals("hadNullKey", mapWithNullKey.getString("")); + assertTrue(mapWithNullKey.has("one")); + assertEquals("1", mapWithNullKey.getString("one")); + } + } diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java index b1e41f11..32b6ec2d 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java @@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.javalin; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.sql.Connection; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -53,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; @@ -112,6 +114,8 @@ public class TestUtils public static final String PROCESS_NAME_SIMPLE_THROW = "simpleThrow"; public static final String PROCESS_NAME_SLEEP_INTERACTIVE = "sleepInteractive"; + public static final String PROCESS_NAME_PUTS_NULL_KEY_IN_MAP = "putsNullKeyInMap"; + public static final String STEP_NAME_SLEEPER = "sleeper"; public static final String STEP_NAME_THROWER = "thrower"; @@ -177,6 +181,7 @@ public class TestUtils qInstance.addProcess(defineProcessGreetPeopleInteractive()); qInstance.addProcess(defineProcessSimpleSleep()); qInstance.addProcess(defineProcessScreenThenSleep()); + qInstance.addProcess(defineProcessPutsNullKeyInMap()); qInstance.addProcess(defineProcessSimpleThrow()); qInstance.addReport(definePersonsReport()); qInstance.addPossibleValueSource(definePossibleValueSourcePerson()); @@ -554,6 +559,26 @@ public class TestUtils + + /******************************************************************************* + ** Define an interactive version of the 'greet people' process + *******************************************************************************/ + private static QProcessMetaData defineProcessPutsNullKeyInMap() + { + return new QProcessMetaData() + .withName(PROCESS_NAME_PUTS_NULL_KEY_IN_MAP) + .withTableName(TABLE_NAME_PERSON) + .withStep(new QBackendStepMetaData().withName("step") + .withCode(new QCodeReferenceLambda((runBackendStepInput, runBackendStepOutput) -> + { + HashMap mapWithNullKey = new HashMap<>(); + mapWithNullKey.put(null, "hadNullKey"); + mapWithNullKey.put("one", "1"); + runBackendStepOutput.addValue("mapWithNullKey", mapWithNullKey); + }))); + } + + /******************************************************************************* ** Define a process with just one step that sleeps and then throws *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java index 36d2fa8c..ee9fd1af 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/specs/v1/ProcessInitSpecV1Test.java @@ -216,4 +216,26 @@ class ProcessInitSpecV1Test extends SpecTestBase // todo - in a higher-level test, resume test_processInitGoingAsync at the // request job status before sleep is done // line } + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testProcessPutsNullKeyInMap() + { + HttpResponse response = Unirest.post(getBaseUrlAndPath() + "/processes/" + TestUtils.PROCESS_NAME_PUTS_NULL_KEY_IN_MAP + "/init") + .multiPartContent() + .asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + + JSONObject values = jsonObject.getJSONObject("values"); + JSONObject mapWithNullKey = values.getJSONObject("mapWithNullKey"); + assertTrue(mapWithNullKey.has("")); // null key currently set to become empty-string key... + assertEquals("hadNullKey", mapWithNullKey.getString("")); + assertTrue(mapWithNullKey.has("one")); + assertEquals("1", mapWithNullKey.getString("one")); + } + } \ No newline at end of file From e84fe7eb18a9c6ba2f89eee1a0e09a0214c68375 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 11 Dec 2024 15:05:47 -0600 Subject: [PATCH 3/4] Checkstyle! --- .../com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java index bbb4c88a..88db07e3 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java @@ -340,10 +340,7 @@ class JsonUtilsTest extends BaseTest // assert that the nullKeyToEmptyStringSerializer does what we expect // //////////////////////////////////////////////////////////////////////// assertEquals(""" - {"":"foo"}""", JsonUtils.toJson(mapWithNullKey, mapper -> - { - mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer); - })); + {"":"foo"}""", JsonUtils.toJson(mapWithNullKey, mapper -> mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer))); } } From abc6331131ab8b43d63d14eb2f3c93da7047b286 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 11 Dec 2024 15:27:33 -0600 Subject: [PATCH 4/4] Fixed process responses in openapi.yaml -- they were a layer too low, w/ a wrapped "typedResponse" above them (and since they were being serialized directly by jackson, were missing the 'values' now that they were marked to be ignored by it... so going through our conversion method in here - this suggests some refactoring that should apply a change like this to all specs, in case they have overrides of handleOutput as well... --- .../specs/v1/utils/ProcessSpecUtilsV1.java | 36 ++- .../main/resources/openapi/v1/openapi.yaml | 297 +++++++++--------- 2 files changed, 166 insertions(+), 167 deletions(-) diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java index 761baa5d..e339d957 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/specs/v1/utils/ProcessSpecUtilsV1.java @@ -28,6 +28,7 @@ import java.time.LocalDate; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import com.fasterxml.jackson.annotation.JsonInclude; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; @@ -141,12 +142,14 @@ public class ProcessSpecUtilsV1 errorResponse.setError("Illegal Argument Exception: NaN"); errorResponse.setUserFacingError("The process could not be completed due to invalid input."); + Function responseToExample = response -> new Example().withValue(convertResponseToJSONObject(response).toMap()); + return MapBuilder.of(() -> new LinkedHashMap()) - .with("COMPLETE", new Example().withValue(completeResponse)) - .with("COMPLETE with metaDataAdjustment", new Example().withValue(completeResponseWithMetaDataAdjustment)) - .with("JOB_STARTED", new Example().withValue(jobStartedResponse)) - .with("RUNNING", new Example().withValue(runningResponse)) - .with("ERROR", new Example().withValue(errorResponse)) + .with("COMPLETE", responseToExample.apply(completeResponse)) + .with("COMPLETE with metaDataAdjustment", responseToExample.apply(completeResponseWithMetaDataAdjustment)) + .with("JOB_STARTED", responseToExample.apply(jobStartedResponse)) + .with("RUNNING", responseToExample.apply(runningResponse)) + .with("ERROR", responseToExample.apply(errorResponse)) .build(); } @@ -155,7 +158,21 @@ public class ProcessSpecUtilsV1 /*************************************************************************** ** ***************************************************************************/ - public static void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 output) + public static void handleOutput(Context context, ProcessInitOrStepOrStatusResponseV1 response) + { + JSONObject outputJsonObject = convertResponseToJSONObject(response); + + String json = outputJsonObject.toString(3); + System.out.println(json); + context.result(json); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static JSONObject convertResponseToJSONObject(ProcessInitOrStepOrStatusResponseV1 response) { //////////////////////////////////////////////////////////////////////////////// // normally, we like the JsonUtils behavior of excluding null/empty elements. // @@ -163,7 +180,7 @@ public class ProcessSpecUtilsV1 // so, go through a loop of object → JSON String → JSONObject → String... // // also - work with the TypedResponse sub-object within this response class // //////////////////////////////////////////////////////////////////////////////// - ProcessInitOrStepOrStatusResponseV1.TypedResponse typedOutput = output.getTypedResponse(); + ProcessInitOrStepOrStatusResponseV1.TypedResponse typedOutput = response.getTypedResponse(); String outputJson = JsonUtils.toJson(typedOutput); JSONObject outputJsonObject = new JSONObject(outputJson); @@ -280,10 +297,7 @@ public class ProcessSpecUtilsV1 outputJsonObject.put("values", valuesAsJsonObject); } } - - String json = outputJsonObject.toString(3); - System.out.println(json); - context.result(json); + return outputJsonObject; } diff --git a/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml index f89ba784..e6772436 100644 --- a/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml +++ b/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml @@ -1652,66 +1652,61 @@ paths: examples: COMPLETE: value: - typedResponse: - nextStep: "reviewScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "reviewScreen" + type: "COMPLETE" COMPLETE with metaDataAdjustment: value: - typedResponse: - nextStep: "inputScreen" - processMetaDataAdjustment: - updatedFields: - someField: - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: true - name: "someField" - type: "STRING" - updatedFrontendStepList: - - components: - - type: "EDIT_FORM" - formFields: - - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: false - name: "someField" - type: "STRING" - name: "inputScreen" - - components: - - type: "PROCESS_SUMMARY_RESULTS" - name: "resultScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "inputScreen" + processMetaDataAdjustment: + updatedFields: + someField: + isRequired: true + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + updatedFrontendStepList: + - components: + - type: "EDIT_FORM" + name: "inputScreen" + formFields: + - isRequired: false + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + - components: + - type: "PROCESS_SUMMARY_RESULTS" + name: "resultScreen" + type: "COMPLETE" JOB_STARTED: value: - typedResponse: - jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "JOB_STARTED" + jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "JOB_STARTED" RUNNING: value: - typedResponse: - current: 47 - message: "Processing person records" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - total: 1701 - type: "RUNNING" + current: 47 + total: 1701 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" + message: "Processing person records" ERROR: value: - typedResponse: - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "RUNNING" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" schema: $ref: "#/components/schemas/ProcessStepResponseV1" description: "State of the initialization of the job, with different fields\ @@ -1788,66 +1783,61 @@ paths: examples: COMPLETE: value: - typedResponse: - nextStep: "reviewScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "reviewScreen" + type: "COMPLETE" COMPLETE with metaDataAdjustment: value: - typedResponse: - nextStep: "inputScreen" - processMetaDataAdjustment: - updatedFields: - someField: - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: true - name: "someField" - type: "STRING" - updatedFrontendStepList: - - components: - - type: "EDIT_FORM" - formFields: - - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: false - name: "someField" - type: "STRING" - name: "inputScreen" - - components: - - type: "PROCESS_SUMMARY_RESULTS" - name: "resultScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "inputScreen" + processMetaDataAdjustment: + updatedFields: + someField: + isRequired: true + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + updatedFrontendStepList: + - components: + - type: "EDIT_FORM" + name: "inputScreen" + formFields: + - isRequired: false + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + - components: + - type: "PROCESS_SUMMARY_RESULTS" + name: "resultScreen" + type: "COMPLETE" JOB_STARTED: value: - typedResponse: - jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "JOB_STARTED" + jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "JOB_STARTED" RUNNING: value: - typedResponse: - current: 47 - message: "Processing person records" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - total: 1701 - type: "RUNNING" + current: 47 + total: 1701 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" + message: "Processing person records" ERROR: value: - typedResponse: - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "RUNNING" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" schema: $ref: "#/components/schemas/ProcessStepResponseV1" description: "State of the backend's running of the next step(s) of the\ @@ -1895,66 +1885,61 @@ paths: examples: COMPLETE: value: - typedResponse: - nextStep: "reviewScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "reviewScreen" + type: "COMPLETE" COMPLETE with metaDataAdjustment: value: - typedResponse: - nextStep: "inputScreen" - processMetaDataAdjustment: - updatedFields: - someField: - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: true - name: "someField" - type: "STRING" - updatedFrontendStepList: - - components: - - type: "EDIT_FORM" - formFields: - - displayFormat: "%s" - isEditable: true - isHeavy: false - isHidden: false - isRequired: false - name: "someField" - type: "STRING" - name: "inputScreen" - - components: - - type: "PROCESS_SUMMARY_RESULTS" - name: "resultScreen" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "COMPLETE" - values: - totalAge: 32768 - firstLastName: "Aabramson" + values: + firstLastName: "Aabramson" + totalAge: 32768 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + nextStep: "inputScreen" + processMetaDataAdjustment: + updatedFields: + someField: + isRequired: true + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + updatedFrontendStepList: + - components: + - type: "EDIT_FORM" + name: "inputScreen" + formFields: + - isRequired: false + isEditable: true + name: "someField" + displayFormat: "%s" + type: "STRING" + isHeavy: false + isHidden: false + - components: + - type: "PROCESS_SUMMARY_RESULTS" + name: "resultScreen" + type: "COMPLETE" JOB_STARTED: value: - typedResponse: - jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "JOB_STARTED" + jobUUID: "98765432-10FE-DCBA-9876-543210FEDCBA" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "JOB_STARTED" RUNNING: value: - typedResponse: - current: 47 - message: "Processing person records" - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - total: 1701 - type: "RUNNING" + current: 47 + total: 1701 + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" + message: "Processing person records" ERROR: value: - typedResponse: - processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" - type: "RUNNING" + processUUID: "01234567-89AB-CDEF-0123-456789ABCDEF" + type: "RUNNING" schema: $ref: "#/components/schemas/ProcessStepResponseV1" description: "State of the backend's running of the specified job, with\