From b9ef76da1357d565fb9f2ca6e51be7cd4017a722 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 11 Jul 2022 09:04:41 -0500 Subject: [PATCH] QQQ-21 add safelyGetPage; add stepType --- .../core/actions/RunBackendStepAction.java | 16 ++-- .../processes/QBackendStepMetaData.java | 10 +++ .../processes/QFrontendStepMetaData.java | 10 +++ .../metadata/processes/QStepMetaData.java | 25 ++++++ .../backend/core/utils/CollectionUtils.java | 74 ++++++++++++++++ .../core/utils/CollectionUtilsTest.java | 86 ++++++++++++++++++- 6 files changed, 215 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/RunBackendStepAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/RunBackendStepAction.java index b43e08bb..0ea6cdac 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/RunBackendStepAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/RunBackendStepAction.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.interfaces.BackendStep; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepRequest; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResult; @@ -128,14 +129,18 @@ public class RunBackendStepAction QProcessCallback callback = runBackendStepRequest.getCallback(); if(callback == null) { - throw (new QException("Function is missing values for fields, but no callback was present to request fields from a user")); + throw (new QUserFacingException("Missing values for one or more fields", + new QException("Function is missing values for fields, but no callback was present to request fields from a user"))); } Map fieldValues = callback.getFieldValues(fieldsToGet); - for(Map.Entry entry : fieldValues.entrySet()) + if(fieldValues != null) { - runBackendStepRequest.addValue(entry.getKey(), entry.getValue()); - // todo - check to make sure got values back? + for(Map.Entry entry : fieldValues.entrySet()) + { + runBackendStepRequest.addValue(entry.getKey(), entry.getValue()); + // todo - check to make sure got values back? + } } } } @@ -164,7 +169,8 @@ public class RunBackendStepAction QProcessCallback callback = runBackendStepRequest.getCallback(); if(callback == null) { - throw (new QException("Function is missing input records, but no callback was present to get a query filter from a user")); + throw (new QUserFacingException("Missing input records.", + new QException("Function is missing input records, but no callback was present to request fields from a user"))); } queryRequest.setFilter(callback.getQueryFilter()); diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java index c6fcb64c..6e2fdb40 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java @@ -42,6 +42,16 @@ public class QBackendStepMetaData extends QStepMetaData + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendStepMetaData() + { + setStepType("backend"); + } + + + /******************************************************************************* ** Setter for label ** diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QFrontendStepMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QFrontendStepMetaData.java index 5e5bff1b..fa74f3c3 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QFrontendStepMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QFrontendStepMetaData.java @@ -40,6 +40,16 @@ public class QFrontendStepMetaData extends QStepMetaData + /******************************************************************************* + ** + *******************************************************************************/ + public QFrontendStepMetaData() + { + setStepType("frontend"); + } + + + /******************************************************************************* ** Getter for formFields ** diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java index 67e982b7..42eb90e2 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java @@ -25,17 +25,21 @@ package com.kingsrook.qqq.backend.core.model.metadata.processes; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.serialization.QStepMetaDataDeserializer; /******************************************************************************* ** Meta-Data to define a step in a process in a QQQ instance. ** *******************************************************************************/ +@JsonDeserialize(using = QStepMetaDataDeserializer.class) public class QStepMetaData { private String name; private String label; + private String stepType; @@ -127,4 +131,25 @@ public class QStepMetaData return (new ArrayList<>()); } + + + /******************************************************************************* + ** Getter for stepType + ** + *******************************************************************************/ + public String getStepType() + { + return stepType; + } + + + + /******************************************************************************* + ** Setter for stepType + ** + *******************************************************************************/ + public void setStepType(String stepType) + { + this.stepType = stepType; + } } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java index 53fe7d56..4b762ec8 100755 --- a/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.utils; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; @@ -332,4 +333,77 @@ public class CollectionUtils return (rs.toString()); } + + + /******************************************************************************* + ** Get a sub-list, safely - i.e., w/o worrying if your indexes are too big. + *******************************************************************************/ + public static List safelyGetPage(List list, Integer skip, Integer limit) + { + ////////////////////////////////////////////////////////// + // throw if the user requested a negative skip or limit // + ////////////////////////////////////////////////////////// + if(skip != null && skip < 0) + { + throw (new IllegalArgumentException("Skip may not be negative (was " + skip + ")")); + } + if(limit != null && limit < 0) + { + throw (new IllegalArgumentException("Limit may not be negative (was " + limit + ")")); + } + + /////////////////////////////////// + // null input list = null output // + /////////////////////////////////// + if(list == null) + { + return (null); + } + + int startAt; + if(skip == null) + { + /////////////////////////////////////////// + // treat null skip is the same as 0 skip // + /////////////////////////////////////////// + startAt = 0; + } + else if(skip > list.size()) + { + //////////////////////////////////////// + // if skip is too large, return empty // + //////////////////////////////////////// + return (new ArrayList<>()); + } + else + { + startAt = skip; + } + + int endAt; + if(limit == null) + { + //////////////////////////////////////////////////////// + // treat null limit as request for all after the skip // + //////////////////////////////////////////////////////// + endAt = list.size(); + } + else if(limit == 0) + { + ///////////////////////////////////////////// + // treat limit of zero as request for none // + ///////////////////////////////////////////// + return (new ArrayList<>()); + } + else + { + endAt = startAt + limit; + if (endAt > list.size()) + { + endAt = list.size(); + } + } + + return list.subList(startAt, endAt); + } } diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java index 832384fc..3c758dfa 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java @@ -160,7 +160,7 @@ class CollectionUtilsTest @Test void test_addAllToMap() { - Map to = new HashMap<>(); + Map to = new HashMap<>(); Map from = new HashMap<>(); assertThrows(NullPointerException.class, () -> CollectionUtils.addAllToMap(null, null)); @@ -361,6 +361,8 @@ class CollectionUtilsTest return s -> s.substring(0, 1); } + + /******************************************************************************* ** helper method to get second char of string (unsafely) *******************************************************************************/ @@ -410,4 +412,86 @@ class CollectionUtilsTest assertEquals("?", CollectionUtils.getQuestionMarks(List.of(1))); assertEquals("?,?,?", CollectionUtils.getQuestionMarks(List.of(1, 2, 3))); } + + + + @Test + void test_safelyGetPage() + { + List empty = Collections.emptyList(); + List list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + ///////////////////// + // null list input // + ///////////////////// + assertNull(CollectionUtils.safelyGetPage(null, null, null)); + assertNull(CollectionUtils.safelyGetPage(null, 1, 1)); + assertNull(CollectionUtils.safelyGetPage(null, null, 1)); + assertNull(CollectionUtils.safelyGetPage(null, 1, null)); + + ////////////////////// + // empty list input // + ////////////////////// + assertEquals(empty, CollectionUtils.safelyGetPage(empty, null, null)); + assertEquals(empty, CollectionUtils.safelyGetPage(empty, 1, 1)); + assertEquals(empty, CollectionUtils.safelyGetPage(empty, null, 1)); + assertEquals(empty, CollectionUtils.safelyGetPage(empty, 1, null)); + + //////////////////////////////////////// + // cases that give back the full list // + //////////////////////////////////////// + assertEquals(list, CollectionUtils.safelyGetPage(list, null, null)); + assertEquals(list, CollectionUtils.safelyGetPage(list, 0, null)); + + /////////////////// + // empty outputs // + /////////////////// + assertEquals(empty, CollectionUtils.safelyGetPage(list, 0, 0)); + assertEquals(empty, CollectionUtils.safelyGetPage(list, 10, 1)); + assertEquals(empty, CollectionUtils.safelyGetPage(list, 20, 10)); + + /////////////////////// + // illegal arguments // + /////////////////////// + assertThrows(IllegalArgumentException.class, () -> CollectionUtils.safelyGetPage(list, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> CollectionUtils.safelyGetPage(list, 1, -1)); + assertThrows(IllegalArgumentException.class, () -> CollectionUtils.safelyGetPage(list, -1, -1)); + assertThrows(IllegalArgumentException.class, () -> CollectionUtils.safelyGetPage(null, -1, -1)); + + ///////////////////////////// + // normal kinds of outputs // + ///////////////////////////// + assertEquals(List.of(1), CollectionUtils.safelyGetPage(list, null, 1)); + assertEquals(List.of(1), CollectionUtils.safelyGetPage(list, 0, 1)); + assertEquals(List.of(2), CollectionUtils.safelyGetPage(list, 1, 1)); + assertEquals(List.of(2, 3), CollectionUtils.safelyGetPage(list, 1, 2)); + assertEquals(List.of(2, 3, 4), CollectionUtils.safelyGetPage(list, 1, 3)); + assertEquals(List.of(9), CollectionUtils.safelyGetPage(list, 8, 1)); + assertEquals(List.of(9, 10), CollectionUtils.safelyGetPage(list, 8, 2)); + assertEquals(List.of(9, 10), CollectionUtils.safelyGetPage(list, 8, 10)); + assertEquals(List.of(10), CollectionUtils.safelyGetPage(list, 9, 1)); + assertEquals(List.of(10), CollectionUtils.safelyGetPage(list, 9, 2)); + assertEquals(List.of(10), CollectionUtils.safelyGetPage(list, 9, 10)); + + ///////////////////////////////////////////////////////// + // make sure scrolling through pages works as expected // + ///////////////////////////////////////////////////////// + int skip = 0; + int limit = 3; + int pageCount = 0; + List accumulator = new ArrayList<>(); + while (true) + { + List nextPage = CollectionUtils.safelyGetPage(list, skip, limit); + if (nextPage.isEmpty()) + { + break; + } + accumulator.addAll(nextPage); + skip += limit; + pageCount++; + } + assertEquals(4, pageCount); + assertEquals(list, accumulator); + } }