diff --git a/pom.xml b/pom.xml index 06def7f3..40b33f13 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ com.kingsrook.qqq qqq-backend-core - 0.1.0-20220711.141150-7 + 0.1.0-20220712.140206-8 com.kingsrook.qqq diff --git a/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 56830ab3..437f982f 100644 --- a/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -98,13 +98,14 @@ public class QJavalinImplementation private static final int SESSION_COOKIE_AGE = 60 * 60 * 24; - protected static QInstance qInstance; + static QInstance qInstance; private static int DEFAULT_PORT = 8001; private static Javalin service; + /******************************************************************************* ** *******************************************************************************/ @@ -153,6 +154,7 @@ public class QJavalinImplementation } + /******************************************************************************* ** *******************************************************************************/ @@ -363,6 +365,9 @@ public class QJavalinImplementation setupSession(context, queryRequest); queryRequest.setTableName(tableName); + // todo - validate that the primary key is of the proper type (e.g,. not a string for an id field) + // and throw a 400-series error (tell the user bad-request), rather than, we're doing a 500 (server error) + /////////////////////////////////////////////////////// // setup a filter for the primaryKey = the path-pram // /////////////////////////////////////////////////////// diff --git a/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index f14f7302..aaac1a36 100644 --- a/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -53,8 +53,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; -import com.kingsrook.qqq.backend.core.state.StateType; -import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; @@ -145,29 +143,27 @@ public class QJavalinProcessHandler runProcessRequest.setStartAfterStep(startAfterStep); populateRunProcessRequestWithValuesFromContext(context, runProcessRequest); - try + //////////////////////////////////////// + // run the process as an async action // + //////////////////////////////////////// + Integer timeout = getTimeoutMillis(context); + RunProcessResult runProcessResult = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) -> { - //////////////////////////////////////// - // run the process as an async action // - //////////////////////////////////////// - Integer timeout = getTimeoutMillis(context); - RunProcessResult runProcessResult = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) -> - { - runProcessRequest.setAsyncJobCallback(callback); - return (new RunProcessAction().execute(runProcessRequest)); - }); + runProcessRequest.setAsyncJobCallback(callback); + return (new RunProcessAction().execute(runProcessRequest)); + }); - LOG.info("Process result error? " + runProcessResult.getException()); - for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields()) - { - LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName())); - } - serializeRunProcessResultForCaller(resultForCaller, runProcessResult); - } - catch(JobGoingAsyncException jgae) + LOG.info("Process result error? " + runProcessResult.getException()); + for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields()) { - resultForCaller.put("jobUUID", jgae.getJobUUID()); + LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName())); } + + serializeRunProcessResultForCaller(resultForCaller, runProcessResult); + } + catch(JobGoingAsyncException jgae) + { + resultForCaller.put("jobUUID", jgae.getJobUUID()); } catch(Exception e) { @@ -199,7 +195,7 @@ public class QJavalinProcessHandler serializeRunProcessExceptionForCaller(resultForCaller, runProcessResult.getException().get()); } resultForCaller.put("values", runProcessResult.getValues()); - runProcessResult.getProcessState().getNextStepName().ifPresent(lastStep -> resultForCaller.put("nextStep", lastStep)); + runProcessResult.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep)); } @@ -297,13 +293,13 @@ public class QJavalinProcessHandler { case "recordIds": @SuppressWarnings("ConstantConditions") - Serializable[] idStrings = context.queryParam(recordsParam).split(","); + Serializable[] idStrings = paramValue.split(","); return (new QQueryFilter().withCriteria(new QFilterCriteria() .withFieldName(primaryKeyField) .withOperator(QCriteriaOperator.IN) .withValues(Arrays.stream(idStrings).toList()))); case "filterJSON": - return (JsonUtils.toObject(context.queryParam(recordsParam), QQueryFilter.class)); + return (JsonUtils.toObject(paramValue, QQueryFilter.class)); case "filterId": // return (JsonUtils.toObject(context.queryParam(recordsParam), QQueryFilter.class)); throw (new NotImplementedException("Saved filters are not yet implemented.")); @@ -335,19 +331,20 @@ public class QJavalinProcessHandler *******************************************************************************/ public static void processStatus(Context context) { + Map resultForCaller = new HashMap<>(); + String processUUID = context.pathParam("processUUID"); String jobUUID = context.pathParam("jobUUID"); - LOG.info("Request for status of job " + jobUUID); + LOG.info("Request for status of process " + processUUID + ", job " + jobUUID); Optional optionalJobStatus = new AsyncJobManager().getJobStatus(jobUUID); if(optionalJobStatus.isEmpty()) { - QJavalinImplementation.handleException(context, new RuntimeException("Could not find status of process step job")); + serializeRunProcessExceptionForCaller(resultForCaller, new RuntimeException("Could not find status of process step job")); } else { - Map resultForCaller = new HashMap<>(); - AsyncJobStatus jobStatus = optionalJobStatus.get(); + AsyncJobStatus jobStatus = optionalJobStatus.get(); resultForCaller.put("jobStatus", jobStatus); LOG.info("Job status is " + jobStatus.getState() + " for " + jobUUID); @@ -358,7 +355,7 @@ public class QJavalinProcessHandler // if the job is complete, get the process result from state provider, and return it // // this output should look like it did if the job finished synchronously!! // /////////////////////////////////////////////////////////////////////////////////////// - Optional processState = RunProcessAction.getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS)); + Optional processState = RunProcessAction.getState(processUUID); if(processState.isPresent()) { RunProcessResult runProcessResult = new RunProcessResult(processState.get()); @@ -366,7 +363,7 @@ public class QJavalinProcessHandler } else { - QJavalinImplementation.handleException(context, new RuntimeException("Could not find process results")); + serializeRunProcessExceptionForCaller(resultForCaller, new RuntimeException("Could not find results for process " + processUUID)); } } else if(jobStatus.getState().equals(AsyncJobState.ERROR)) @@ -379,9 +376,9 @@ public class QJavalinProcessHandler serializeRunProcessExceptionForCaller(resultForCaller, jobStatus.getCaughtException()); } } - - context.result(JsonUtils.toJson(resultForCaller)); } + + context.result(JsonUtils.toJson(resultForCaller)); } @@ -397,7 +394,9 @@ public class QJavalinProcessHandler Integer skip = Objects.requireNonNullElse(QJavalinImplementation.integerQueryParam(context, "skip"), 0); Integer limit = Objects.requireNonNullElse(QJavalinImplementation.integerQueryParam(context, "limit"), 20); - Optional optionalProcessState = RunProcessAction.getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS)); + // todo - potential optimization - if a future state provider could take advantage of it, + // we might pass the skip & limit in to a method that fetch just those 'n' rows from state, rather than the whole thing? + Optional optionalProcessState = RunProcessAction.getState(processUUID); if(optionalProcessState.isEmpty()) { throw (new Exception("Could not find process results.")); @@ -411,7 +410,7 @@ public class QJavalinProcessHandler } Map resultForCaller = new HashMap<>(); - List recordPage = CollectionUtils.safelyGetPage(records, skip, limit); + List recordPage = CollectionUtils.safelyGetPage(records, skip, limit); resultForCaller.put("records", recordPage); context.result(JsonUtils.toJson(resultForCaller)); } diff --git a/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java b/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java index 548b4f39..91cc0479 100644 --- a/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java @@ -47,7 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; *******************************************************************************/ class QJavalinProcessHandlerTest extends QJavalinTestBase { - private static final int MORE_THAN_TIMEOUT = 1000; + private static final int MORE_THAN_TIMEOUT = 500; private static final int LESS_THAN_TIMEOUT = 50; @@ -96,8 +96,9 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase assertEquals(200, response.getStatus()); JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertNotNull(jsonObject); + String processUUID = jsonObject.getString("processUUID"); - // todo - once we know how to get records from a process, add that call + getProcessRecords(processUUID, 2); } @@ -120,8 +121,45 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase assertEquals(200, response.getStatus()); JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertNotNull(jsonObject); + String processUUID = jsonObject.getString("processUUID"); - // todo - once we know how to get records from a process, add that call + getProcessRecords(processUUID, 3); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private JSONObject getProcessRecords(String processUUID, int expectedNoOfRecords) + { + return (getProcessRecords(processUUID, expectedNoOfRecords, 0, 20)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private JSONObject getProcessRecords(String processUUID, int expectedNoOfRecords, int skip, int limit) + { + HttpResponse response; + JSONObject jsonObject; + response = Unirest.get(BASE_URL + "/processes/greet/" + processUUID + "/records?skip=" + skip + "&limit=" + limit).asString(); + jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertNotNull(jsonObject); + + if(expectedNoOfRecords == 0) + { + assertFalse(jsonObject.has("records")); + } + else + { + assertTrue(jsonObject.has("records")); + JSONArray records = jsonObject.getJSONArray("records"); + assertEquals(expectedNoOfRecords, records.length()); + } + return (jsonObject); } @@ -133,7 +171,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase @Test public void test_processGreetInitWithQueryValues() { - HttpResponse response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=2,3&greetingPrefix=Hey&greetingSuffix=Jude").asString(); + HttpResponse response = Unirest.post(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=2,3&greetingPrefix=Hey&greetingSuffix=Jude").asString(); assertEquals(200, response.getStatus()); JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertNotNull(jsonObject); @@ -321,7 +359,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase /******************************************************************************* - ** every time a process step (sync or async) has gone async, expect what the + ** every time a process step (or init) has gone async, expect what the ** response should look like *******************************************************************************/ private JSONObject assertProcessStepWentAsyncResponse(HttpResponse response) @@ -412,16 +450,30 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase assertNotNull(jsonObject); String processUUID = jsonObject.getString("processUUID"); - response = Unirest.get(BASE_URL + "/processes/greet/" + processUUID + "/records").asString(); - jsonObject = JsonUtils.toJSONObject(response.getBody()); - assertNotNull(jsonObject); - assertTrue(jsonObject.has("records")); - JSONArray records = jsonObject.getJSONArray("records"); - assertEquals(2, records.length()); + jsonObject = getProcessRecords(processUUID, 2); + JSONArray records = jsonObject.getJSONArray("records"); JSONObject record0 = records.getJSONObject(0); JSONObject values = record0.getJSONObject("values"); assertTrue(values.has("id")); assertTrue(values.has("firstName")); } + + + /******************************************************************************* + ** test getting records back from a process with skip & Limit + ** + *******************************************************************************/ + @Test + public void test_processRecordsSkipAndLimit() + { + HttpResponse response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=1,2,3,4,5").asString(); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + String processUUID = jsonObject.getString("processUUID"); + + getProcessRecords(processUUID, 5); + getProcessRecords(processUUID, 1, 4, 5); + getProcessRecords(processUUID, 0, 5, 5); + } + } \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinTestBase.java b/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinTestBase.java index 54dc47a5..168174c2 100644 --- a/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinTestBase.java +++ b/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinTestBase.java @@ -47,7 +47,7 @@ public class QJavalinTestBase public static void beforeAll() { qJavalinImplementation = new QJavalinImplementation(TestUtils.defineInstance()); - QJavalinProcessHandler.setAsyncStepTimeoutMillis(500); + QJavalinProcessHandler.setAsyncStepTimeoutMillis(250); qJavalinImplementation.startJavalinServer(PORT); }