diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java
deleted file mode 100644
index 4da39af3..00000000
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2022. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.kingsrook.qqq.backend.core.actions.customizers;
-
-
-/*******************************************************************************
- ** Standard place where the names of QQQ Customization points are defined.
- *******************************************************************************/
-public interface Customizers
-{
- String POST_QUERY_RECORD = "postQueryRecord";
-
-}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
index 8e7c0dfc..f1774928 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
@@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
import java.util.Optional;
import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -43,15 +46,14 @@ public class QCodeLoader
/*******************************************************************************
**
*******************************************************************************/
- public static Function, ?> getTableCustomizerFunction(QTableMetaData table, String customizerName)
+ public static Optional> getTableCustomizerFunction(QTableMetaData table, String customizerName)
{
Optional codeReference = table.getCustomizer(customizerName);
if(codeReference.isPresent())
{
- return (QCodeLoader.getFunction(codeReference.get()));
+ return (Optional.ofNullable(QCodeLoader.getFunction(codeReference.get())));
}
-
- return null;
+ return (Optional.empty());
}
@@ -93,4 +95,30 @@ public class QCodeLoader
}
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
+ {
+ try
+ {
+ Class> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
+ Object codeObject = codeClass.getConstructor().newInstance();
+ if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
+ {
+ throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
+ }
+ return (customPossibleValueProvider);
+ }
+ catch(QException qe)
+ {
+ throw (qe);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
+ }
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizer.java
new file mode 100644
index 00000000..9367a0c1
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizer.java
@@ -0,0 +1,61 @@
+package com.kingsrook.qqq.backend.core.actions.customizers;
+
+
+import java.util.function.Consumer;
+
+
+/*******************************************************************************
+ ** Object used by TableCustomizers enum (and similar enums in backend modules)
+ ** to assist with definition and validation of Customizers applied to tables.
+ *******************************************************************************/
+public class TableCustomizer
+{
+ private final String role;
+ private final Class> expectedType;
+ private final Consumer validationFunction;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public TableCustomizer(String role, Class> expectedType, Consumer validationFunction)
+ {
+ this.role = role;
+ this.expectedType = expectedType;
+ this.validationFunction = validationFunction;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for role
+ **
+ *******************************************************************************/
+ public String getRole()
+ {
+ return role;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for expectedType
+ **
+ *******************************************************************************/
+ public Class> getExpectedType()
+ {
+ return expectedType;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for validationFunction
+ **
+ *******************************************************************************/
+ public Consumer getValidationFunction()
+ {
+ return validationFunction;
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizers.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizers.java
new file mode 100644
index 00000000..e5899fad
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizers.java
@@ -0,0 +1,85 @@
+package com.kingsrook.qqq.backend.core.actions.customizers;
+
+
+import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+
+
+/*******************************************************************************
+ ** Enum definition of possible table customizers - "roles" for custom code that
+ ** can be applied to tables.
+ **
+ ** Works with TableCustomizer (singular version of this name) objects, during
+ ** instance validation, to provide validation of the referenced code (and to
+ ** make such validation from sub-backend-modules possible in the future).
+ **
+ ** The idea of the 3rd argument here is to provide a way that we can enforce
+ ** the type-parameters for the custom code. E.g., if it's a Function - how
+ ** can we check at run-time that the type-params are correct? We couldn't find
+ ** how to do this "reflectively", so we can instead try to run the custom code,
+ ** passing it objects of the type that this customizer expects, and a validation
+ ** error will raise upon ClassCastException... This maybe could improve!
+ *******************************************************************************/
+public enum TableCustomizers
+{
+ POST_QUERY_RECORD(new TableCustomizer("postQueryRecord", Function.class, ((Object x) ->
+ {
+ Function function = (Function) x;
+ QRecord output = function.apply(new QRecord());
+ })));
+
+
+ private final TableCustomizer tableCustomizer;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ TableCustomizers(TableCustomizer tableCustomizer)
+ {
+ this.tableCustomizer = tableCustomizer;
+ }
+
+
+
+ /*******************************************************************************
+ ** Get the TableCustomer for a given role (e.g., the role used in meta-data, not
+ ** the enum-constant name).
+ *******************************************************************************/
+ public static TableCustomizers forRole(String name)
+ {
+ for(TableCustomizers value : values())
+ {
+ if(value.tableCustomizer.getRole().equals(name))
+ {
+ return (value);
+ }
+ }
+
+ return (null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tableCustomizer
+ **
+ *******************************************************************************/
+ public TableCustomizer getTableCustomizer()
+ {
+ return tableCustomizer;
+ }
+
+
+
+ /*******************************************************************************
+ ** get the role from the tableCustomizer
+ **
+ *******************************************************************************/
+ public String getRole()
+ {
+ return (tableCustomizer.getRole());
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/RecordPipe.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/RecordPipe.java
index 3703a841..c41e7f08 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/RecordPipe.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/RecordPipe.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import org.apache.logging.log4j.LogManager;
@@ -42,12 +43,31 @@ public class RecordPipe
private ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1_000);
+ private Consumer> postRecordActions = null;
+
+
/*******************************************************************************
** Add a record to the pipe
** Returns true iff the record fit in the pipe; false if the pipe is currently full.
*******************************************************************************/
public void addRecord(QRecord record)
+ {
+ if(postRecordActions != null)
+ {
+ postRecordActions.accept(List.of(record));
+ }
+
+ doAddRecord(record);
+ }
+
+
+
+ /*******************************************************************************
+ ** Private internal version of add record - assumes the postRecordActions have
+ ** already ran.
+ *******************************************************************************/
+ private void doAddRecord(QRecord record)
{
boolean offerResult = queue.offer(record);
@@ -66,7 +86,15 @@ public class RecordPipe
*******************************************************************************/
public void addRecords(List records)
{
- records.forEach(this::addRecord);
+ if(postRecordActions != null)
+ {
+ postRecordActions.accept(records);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ // make sure to go to the private version of doAddRecord - to avoid re-running the post-actions //
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ records.forEach(this::doAddRecord);
}
@@ -101,4 +129,14 @@ public class RecordPipe
return (queue.size());
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void setPostRecordActions(Consumer> postRecordActions)
+ {
+ this.postRecordActions = postRecordActions;
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
index 83ec1dd1..d492dd97 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
@@ -22,12 +22,18 @@
package com.kingsrook.qqq.backend.core.actions.tables;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
+import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@@ -38,6 +44,14 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
*******************************************************************************/
public class QueryAction
{
+ private Optional> postQueryRecordCustomizer;
+
+ private QueryInput queryInput;
+ private QValueFormatter qValueFormatter;
+ private QPossibleValueTranslator qPossibleValueTranslator;
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -45,6 +59,14 @@ public class QueryAction
{
ActionHelper.validateSession(queryInput);
+ postQueryRecordCustomizer = QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
+ this.queryInput = queryInput;
+
+ if(queryInput.getRecordPipe() != null)
+ {
+ queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
+ }
+
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
// todo pre-customization - just get to modify the request?
@@ -53,20 +75,37 @@ public class QueryAction
if(queryInput.getRecordPipe() == null)
{
- if(queryInput.getShouldGenerateDisplayValues())
- {
- QValueFormatter qValueFormatter = new QValueFormatter();
- qValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
- }
-
- if(queryInput.getShouldTranslatePossibleValues())
- {
- QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
- qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
- }
+ postRecordActions(queryOutput.getRecords());
}
return queryOutput;
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void postRecordActions(List records)
+ {
+ this.postQueryRecordCustomizer.ifPresent(qRecordQRecordFunction -> records.replaceAll(qRecordQRecordFunction::apply));
+
+ if(queryInput.getShouldGenerateDisplayValues())
+ {
+ if(qValueFormatter == null)
+ {
+ qValueFormatter = new QValueFormatter();
+ }
+ qValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
+ }
+
+ if(queryInput.getShouldTranslatePossibleValues())
+ {
+ if(qPossibleValueTranslator == null)
+ {
+ qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
+ }
+ qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records);
+ }
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java
index 603951ee..5c50cf2c 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java
@@ -31,8 +31,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
-import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@@ -63,8 +63,10 @@ public class QPossibleValueTranslator
private final QInstance qInstance;
private final QSession session;
- // top-level keys are pvsNames (not table names)
- // 2nd-level keys are pkey values from the PVS table
+ ///////////////////////////////////////////////////////
+ // top-level keys are pvsNames (not table names) //
+ // 2nd-level keys are pkey values from the PVS table //
+ ///////////////////////////////////////////////////////
private Map> possibleValueCache;
@@ -120,9 +122,6 @@ public class QPossibleValueTranslator
return (null);
}
- // todo - memoize!!!
- // todo - bulk!!!
-
String resultValue = null;
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
{
@@ -154,22 +153,14 @@ public class QPossibleValueTranslator
/*******************************************************************************
**
*******************************************************************************/
- private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
+ private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
{
- try
+ for(QPossibleValue> possibleValue : possibleValueSource.getEnumValues())
{
- Class> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
- Object codeObject = codeClass.getConstructor().newInstance();
- if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
+ if(possibleValue.getId().equals(value))
{
- throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
+ return (formatPossibleValue(possibleValueSource, possibleValue));
}
-
- return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
- }
- catch(Exception e)
- {
- LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
}
return (null);
@@ -205,6 +196,26 @@ public class QPossibleValueTranslator
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
+ {
+ try
+ {
+ QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
+ return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
+ }
+
+ return (null);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -256,26 +267,11 @@ public class QPossibleValueTranslator
- /*******************************************************************************
- **
- *******************************************************************************/
- private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
- {
- for(QPossibleValue> possibleValue : possibleValueSource.getEnumValues())
- {
- if(possibleValue.getId().equals(value))
- {
- return (formatPossibleValue(possibleValueSource, possibleValue));
- }
- }
-
- return (null);
- }
-
-
-
/*******************************************************************************
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
+ **
+ ** @param table the table that the records are from
+ ** @param records the records that have the possible value id's (e.g., foreign keys)
*******************************************************************************/
void primePvsCache(QTableMetaData table, List records)
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
index 27ccde27..5ac811a9 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
@@ -25,10 +25,8 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
-import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
@@ -37,7 +35,7 @@ import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Utility to apply display formats to values for records and fields.
- ** Note that this includes handling PossibleValues.
+ **
*******************************************************************************/
public class QValueFormatter
{
@@ -45,15 +43,6 @@ public class QValueFormatter
- /*******************************************************************************
- **
- *******************************************************************************/
- public QValueFormatter()
- {
- }
-
-
-
/*******************************************************************************
**
*******************************************************************************/
@@ -67,16 +56,6 @@ public class QValueFormatter
return (null);
}
- // todo - is this appropriate, with this class and possibleValueTransaltor being decoupled - to still do standard formatting here?
- // alternatively, shold we return null here?
- // ///////////////////////////////////////////////
- // // if the field has a possible value, use it //
- // ///////////////////////////////////////////////
- // if(field.getPossibleValueSourceName() != null)
- // {
- // return (this.possibleValueTranslator.translatePossibleValue(field, value));
- // }
-
////////////////////////////////////////////////////////
// if the field has a display format, try to apply it //
////////////////////////////////////////////////////////
@@ -198,5 +177,4 @@ public class QValueFormatter
}
}
-
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
index 119daa34..19beae30 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java
@@ -229,7 +229,21 @@ public class QInstanceEnricher
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
}
- return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z0-9]+)", " $1").replaceAll("([0-9])([A-Za-z])", "$1 $2"));
+ String suffix = name.substring(1)
+
+ //////////////////////////////////////////////////////////////////////
+ // Put a space before capital letters or numbers embedded in a name //
+ // e.g., omethingElse -> omething Else; umber1 -> umber 1 //
+ //////////////////////////////////////////////////////////////////////
+ .replaceAll("([A-Z0-9]+)", " $1")
+
+ ////////////////////////////////////////////////////////////////
+ // put a space between numbers and words that come after them //
+ // e.g., umber1dad -> number 1 dad //
+ ////////////////////////////////////////////////////////////////
+ .replaceAll("([0-9])([A-Za-z])", "$1 $2");
+
+ return (name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix);
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
index 66959a31..4b7c4c2b 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
@@ -22,13 +22,20 @@
package com.kingsrook.qqq.backend.core.instances;
+import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
+import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
@@ -38,6 +45,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
/*******************************************************************************
@@ -52,6 +61,11 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/
public class QInstanceValidator
{
+ private static final Logger LOG = LogManager.getLogger(QInstanceValidator.class);
+
+ private boolean printWarnings = false;
+
+
/*******************************************************************************
**
@@ -202,12 +216,130 @@ public class QInstanceValidator
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
}
}
+
+ if(table.getCustomizers() != null)
+ {
+ for(Map.Entry entry : table.getCustomizers().entrySet())
+ {
+ validateTableCustomizer(errors, tableName, entry.getKey(), entry.getValue());
+ }
+ }
});
}
}
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void validateTableCustomizer(List errors, String tableName, String customizerName, QCodeReference codeReference)
+ {
+ String prefix = "Table " + tableName + ", customizer " + customizerName + ": ";
+
+ if(!preAssertionsForCodeReference(errors, codeReference, prefix))
+ {
+ return;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // make sure (at this time) that it's a java type, then do some java checks //
+ //////////////////////////////////////////////////////////////////////////////
+ if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
+ {
+ ///////////////////////////////////////
+ // make sure the class can be loaded //
+ ///////////////////////////////////////
+ Class> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
+ if(customizerClass != null)
+ {
+ //////////////////////////////////////////////////
+ // make sure the customizer can be instantiated //
+ //////////////////////////////////////////////////
+ Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
+
+ TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
+ if(tableCustomizer == null)
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+ // todo - in the future, load customizers from backend-modules (e.g., FilesystemTableCustomizers) //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+ warn(prefix + "Unrecognized table customizer name (at least at backend-core level)");
+ }
+ else
+ {
+ ////////////////////////////////////////////////////////////////////////
+ // make sure the customizer instance can be cast to the expected type //
+ ////////////////////////////////////////////////////////////////////////
+ if(customizerInstance != null && tableCustomizer.getTableCustomizer().getExpectedType() != null)
+ {
+ Object castedObject = getCastedObject(errors, prefix, tableCustomizer.getTableCustomizer().getExpectedType(), customizerInstance);
+
+ Consumer validationFunction = tableCustomizer.getTableCustomizer().getValidationFunction();
+ if(castedObject != null && validationFunction != null)
+ {
+ try
+ {
+ validationFunction.accept(castedObject);
+ }
+ catch(ClassCastException e)
+ {
+ errors.add(prefix + "Error validating customizer type parameters: " + e.getMessage());
+ }
+ catch(Exception e)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // mmm, calling customizers w/ random data is expected to often throw, so, this check is iffy at best... //
+ // if we run into more trouble here, we might consider disabling the whole "validation function" check. //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private T getCastedObject(List errors, String prefix, Class expectedType, Object customizerInstance)
+ {
+ T castedObject = null;
+ try
+ {
+ castedObject = expectedType.cast(customizerInstance);
+ }
+ catch(ClassCastException e)
+ {
+ errors.add(prefix + "CodeReference could not be casted to the expected type: " + expectedType);
+ }
+ return castedObject;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private Object getInstanceOfCodeReference(List errors, String prefix, Class> customizerClass)
+ {
+ Object customizerInstance = null;
+ try
+ {
+ customizerInstance = customizerClass.getConstructor().newInstance();
+ }
+ catch(InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e)
+ {
+ errors.add(prefix + "Instance of CodeReference could not be created: " + e);
+ }
+ return customizerInstance;
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -313,7 +445,6 @@ public class QInstanceValidator
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
{
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
- assertCondition(errors, possibleValueSource.getIdType() != null, "Missing an idType for possibleValueSource: " + pvsName);
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
{
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -347,6 +478,7 @@ public class QInstanceValidator
if(assertCondition(errors, possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
{
assertCondition(errors, QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
+ validateCustomPossibleValueSourceCode(errors, pvsName, possibleValueSource.getCustomCodeReference());
}
}
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
@@ -358,6 +490,87 @@ public class QInstanceValidator
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void validateCustomPossibleValueSourceCode(List errors, String pvsName, QCodeReference codeReference)
+ {
+ String prefix = "PossibleValueSource " + pvsName + " custom code reference: ";
+
+ if(!preAssertionsForCodeReference(errors, codeReference, prefix))
+ {
+ return;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // make sure (at this time) that it's a java type, then do some java checks //
+ //////////////////////////////////////////////////////////////////////////////
+ if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
+ {
+ ///////////////////////////////////////
+ // make sure the class can be loaded //
+ ///////////////////////////////////////
+ Class> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
+ if(customizerClass != null)
+ {
+ //////////////////////////////////////////////////
+ // make sure the customizer can be instantiated //
+ //////////////////////////////////////////////////
+ Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
+
+ ////////////////////////////////////////////////////////////////////////
+ // make sure the customizer instance can be cast to the expected type //
+ ////////////////////////////////////////////////////////////////////////
+ if(customizerInstance != null)
+ {
+ getCastedObject(errors, prefix, QCustomPossibleValueProvider.class, customizerInstance);
+ }
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private Class> getClassForCodeReference(List errors, QCodeReference codeReference, String prefix)
+ {
+ Class> customizerClass = null;
+ try
+ {
+ customizerClass = Class.forName(codeReference.getName());
+ }
+ catch(ClassNotFoundException e)
+ {
+ errors.add(prefix + "Class for CodeReference could not be found.");
+ }
+ return customizerClass;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private boolean preAssertionsForCodeReference(List errors, QCodeReference codeReference, String prefix)
+ {
+ boolean okay = true;
+ if(!assertCondition(errors, StringUtils.hasContent(codeReference.getName()), prefix + " is missing a code reference name"))
+ {
+ okay = false;
+ }
+
+ if(!assertCondition(errors, codeReference.getCodeType() != null, prefix + " is missing a code type"))
+ {
+ okay = false;
+ }
+
+ return (okay);
+ }
+
+
+
/*******************************************************************************
** Check if an app's child list can recursively be traversed without finding a
** duplicate, which would indicate a cycle (e.g., an error)
@@ -410,4 +623,16 @@ public class QInstanceValidator
return (condition);
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void warn(String message)
+ {
+ if(printWarnings)
+ {
+ LOG.info("Validation warning: " + message);
+ }
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
index 9412e0d0..a9e19342 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
@@ -24,13 +24,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.List;
-import java.util.function.Function;
-import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
-import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
/*******************************************************************************
@@ -39,12 +34,8 @@ import org.apache.logging.log4j.Logger;
*******************************************************************************/
public class QueryOutput extends AbstractActionOutput implements Serializable
{
- private static final Logger LOG = LogManager.getLogger(QueryOutput.class);
-
private QueryOutputStorageInterface storage;
- private Function postQueryRecordCustomizer;
-
/*******************************************************************************
@@ -61,8 +52,6 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
{
storage = new QueryOutputList();
}
-
- postQueryRecordCustomizer = (Function) QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
}
@@ -76,36 +65,16 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
*******************************************************************************/
public void addRecord(QRecord record)
{
- record = runPostQueryRecordCustomizer(record);
storage.addRecord(record);
}
- /*******************************************************************************
- **
- *******************************************************************************/
- public QRecord runPostQueryRecordCustomizer(QRecord record)
- {
- if(this.postQueryRecordCustomizer != null)
- {
- record = this.postQueryRecordCustomizer.apply(record);
- }
- return record;
- }
-
-
-
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
public void addRecords(List records)
{
- if(this.postQueryRecordCustomizer != null)
- {
- records.replaceAll(t -> this.postQueryRecordCustomizer.apply(t));
- }
-
storage.addRecords(records);
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
index bf51c6fb..77cdd6e8 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
@@ -334,7 +334,7 @@ public class QRecord implements Serializable
*******************************************************************************/
public LocalTime getValueLocalTime(String fieldName)
{
- return ((LocalTime) ValueUtils.getValueAsLocalTime(values.get(fieldName)));
+ return (ValueUtils.getValueAsLocalTime(values.get(fieldName)));
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PVSValueFormatAndFields.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PVSValueFormatAndFields.java
new file mode 100644
index 00000000..f8ea0eb9
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PVSValueFormatAndFields.java
@@ -0,0 +1,55 @@
+package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
+
+
+import java.util.List;
+
+
+/*******************************************************************************
+ ** Define some standard ways to format the value portion of a PossibleValueSource.
+ **
+ ** Can be passed to short-cut {set,with}ValueFormatAndFields methods in QPossibleValueSource
+ ** class, or the format & field properties can be extracted and passed to regular field-level setters.
+ *******************************************************************************/
+public enum PVSValueFormatAndFields
+{
+ LABEL_ONLY("%s", "label"),
+ LABEL_PARENS_ID("%s (%s)", "label", "id"),
+ ID_COLON_LABEL("%s: %s", "id", "label");
+
+
+ private final String format;
+ private final List fields;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ PVSValueFormatAndFields(String format, String... fields)
+ {
+ this.format = format;
+ this.fields = List.of(fields);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for format
+ **
+ *******************************************************************************/
+ public String getFormat()
+ {
+ return format;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for fields
+ **
+ *******************************************************************************/
+ public List getFields()
+ {
+ return fields;
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PossibleValueEnum.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PossibleValueEnum.java
index 24018b3a..fc61b2df 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PossibleValueEnum.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/PossibleValueEnum.java
@@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
/*******************************************************************************
+ ** Interface to be implemented by enums which can be used as a PossibleValueSource.
**
*******************************************************************************/
public interface PossibleValueEnum
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java
index 735a22e4..4b15b193 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java
@@ -25,44 +25,24 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
/*******************************************************************************
- ** Meta-data to represent a single field in a table.
+ ** Meta-data to represent a "Possible value" - e.g., a translation of a foreign
+ ** key and/or a limited set of "possible values" for a field (e.g., from a foreign
+ ** table or an enum).
**
*******************************************************************************/
public class QPossibleValueSource
{
private String name;
private QPossibleValueSourceType type;
- private QFieldType idType = QFieldType.INTEGER;
- private String valueFormat = ValueFormat.DEFAULT;
- private List valueFields = ValueFields.DEFAULT;
+ private String valueFormat = PVSValueFormatAndFields.LABEL_ONLY.getFormat();
+ private List valueFields = PVSValueFormatAndFields.LABEL_ONLY.getFields();
private String valueFormatIfNotFound = null;
private List valueFieldsIfNotFound = null;
-
-
- public interface ValueFormat
- {
- String DEFAULT = "%s";
- String LABEL_ONLY = "%s";
- String LABEL_PARENS_ID = "%s (%s)";
- String ID_COLON_LABEL = "%s: %s";
- }
-
-
-
- public interface ValueFields
- {
- List DEFAULT = List.of("label");
- List LABEL_ONLY = List.of("label");
- List LABEL_PARENS_ID = List.of("label", "id");
- List ID_COLON_LABEL = List.of("id", "label");
- }
-
// todo - optimization hints, such as "table is static, fully cache" or "table is small, so we can pull the whole thing into memory?"
//////////////////////
@@ -154,40 +134,6 @@ public class QPossibleValueSource
- /*******************************************************************************
- ** Getter for idType
- **
- *******************************************************************************/
- public QFieldType getIdType()
- {
- return idType;
- }
-
-
-
- /*******************************************************************************
- ** Setter for idType
- **
- *******************************************************************************/
- public void setIdType(QFieldType idType)
- {
- this.idType = idType;
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for idType
- **
- *******************************************************************************/
- public QPossibleValueSource withIdType(QFieldType idType)
- {
- this.idType = idType;
- return (this);
- }
-
-
-
/*******************************************************************************
** Getter for valueFormat
**
@@ -407,6 +353,9 @@ public class QPossibleValueSource
/*******************************************************************************
+ ** This is the easiest way to add the values from an enum to a PossibleValueSource.
+ ** Make sure the enum implements PossibleValueEnum - then call as:
+ ** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
**
*******************************************************************************/
public > QPossibleValueSource withValuesFromEnum(T[] values)
@@ -453,4 +402,26 @@ public class QPossibleValueSource
return (this);
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void setValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
+ {
+ this.valueFormat = valueFormatAndFields.getFormat();
+ this.valueFields = valueFormatAndFields.getFields();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QPossibleValueSource withValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
+ {
+ setValueFormatAndFields(valueFormatAndFields);
+ return (this);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
index ebb3e36a..0d1cbcef 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
@@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
@@ -85,6 +86,17 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String toString()
+ {
+ return ("QTableMetaData[" + name + "]");
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -451,6 +463,16 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData withCustomizer(TableCustomizer tableCustomizer, QCodeReference customizer)
+ {
+ return (withCustomizer(tableCustomizer.getRole(), customizer));
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java
index f195edcf..90eedd28 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java
@@ -174,10 +174,13 @@ public class MemoryRecordStore
*******************************************************************************/
public Integer count(CountInput input)
{
- Map tableData = getTableData(input.getTable());
- List records = new ArrayList<>(tableData.values());
- // todo - filtering (call query)
- return (records.size());
+ QueryInput queryInput = new QueryInput(input.getInstance());
+ queryInput.setSession(input.getSession());
+ queryInput.setTableName(input.getTableName());
+ queryInput.setFilter(input.getFilter());
+ List queryResult = query(queryInput);
+
+ return (queryResult.size());
}
@@ -192,27 +195,43 @@ public class MemoryRecordStore
return (new ArrayList<>());
}
- QTableMetaData table = input.getTable();
- Map tableData = getTableData(table);
- Integer nextSerial = nextSerials.get(table.getName());
+ QTableMetaData table = input.getTable();
+ Map tableData = getTableData(table);
+
+ ////////////////////////////////////////
+ // grab the next unique serial to use //
+ ////////////////////////////////////////
+ Integer nextSerial = nextSerials.get(table.getName());
if(nextSerial == null)
{
nextSerial = 1;
- while(tableData.containsKey(nextSerial))
- {
- nextSerial++;
- }
+ }
+
+ while(tableData.containsKey(nextSerial))
+ {
+ nextSerial++;
}
List outputRecords = new ArrayList<>();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
for(QRecord record : input.getRecords())
{
+ /////////////////////////////////////////////////
+ // set the next serial in the record if needed //
+ /////////////////////////////////////////////////
if(record.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER))
{
record.setValue(primaryKeyField.getName(), nextSerial++);
}
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
+ // make sure that if the user supplied a serial, greater than the one we had, that we skip ahead //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
+ if(primaryKeyField.getType().equals(QFieldType.INTEGER) && record.getValueInteger(primaryKeyField.getName()) > nextSerial)
+ {
+ nextSerial = record.getValueInteger(primaryKeyField.getName()) + 1;
+ }
+
tableData.put(record.getValue(primaryKeyField.getName()), record);
if(returnInsertedRecords)
{
@@ -220,6 +239,8 @@ public class MemoryRecordStore
}
}
+ nextSerials.put(table.getName(), nextSerial);
+
return (outputRecords);
}
@@ -256,10 +277,6 @@ public class MemoryRecordStore
outputRecords.add(record);
}
}
- else
- {
- outputRecords.add(record);
- }
}
return (outputRecords);
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
index 9850719b..4d422746 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
@@ -423,7 +423,7 @@ public class ValueUtils
/*******************************************************************************
**
*******************************************************************************/
- public static Object getValueAsLocalTime(Serializable value)
+ public static LocalTime getValueAsLocalTime(Serializable value)
{
try
{
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslatorTest.java
index 2d4dbbf6..75d03a38 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslatorTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslatorTest.java
@@ -30,25 +30,39 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
- **
+ ** Unit test for QPossibleValueTranslator
*******************************************************************************/
public class QPossibleValueTranslatorTest
{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ void beforeEach()
+ {
+ MemoryRecordStore.getInstance().reset();
+ MemoryRecordStore.resetStatistics();
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -90,22 +104,20 @@ public class QPossibleValueTranslatorTest
/////////////////////////////////////////////////////////////////
// assert the LABEL_ONLY format (when called out specifically) //
/////////////////////////////////////////////////////////////////
- possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_ONLY);
- possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_ONLY);
+ possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
assertEquals("IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
///////////////////////////////////////
// assert the LABEL_PARAMS_ID format //
///////////////////////////////////////
- possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_PARENS_ID);
- possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_PARENS_ID);
+ possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
assertEquals("IL (1)", possibleValueTranslator.translatePossibleValue(stateField, 1));
//////////////////////////////////////
// assert the ID_COLON_LABEL format //
//////////////////////////////////////
- possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.ID_COLON_LABEL);
- possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.ID_COLON_LABEL);
+ possibleValueSource.setValueFormat(PVSValueFormatAndFields.ID_COLON_LABEL.getFormat());
+ possibleValueSource.setValueFields(PVSValueFormatAndFields.ID_COLON_LABEL.getFields());
assertEquals("1: IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
}
@@ -156,8 +168,7 @@ public class QPossibleValueTranslatorTest
///////////////////////////////////////
// assert the LABEL_PARAMS_ID format //
///////////////////////////////////////
- possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_PARENS_ID);
- possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_PARENS_ID);
+ possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
assertEquals("Circle (3)", possibleValueTranslator.translatePossibleValue(shapeField, 3));
///////////////////////////////////////////////////////////
@@ -195,19 +206,124 @@ public class QPossibleValueTranslatorTest
+ /*******************************************************************************
+ ** Make sure that if we have 2 different PVS's pointed at the same 1 table,
+ ** that we avoid re-doing queries, and that we actually get different (formatted) values.
+ *******************************************************************************/
+ @Test
+ void testPossibleValueTableMultiplePvsForATable() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ QTableMetaData shapeTable = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
+ QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
+
+ ////////////////////////////////////////////////////////////////////
+ // define a second version of the Shape PVS, with a unique format //
+ ////////////////////////////////////////////////////////////////////
+ qInstance.addPossibleValueSource(new QPossibleValueSource()
+ .withName("shapeV2")
+ .withType(QPossibleValueSourceType.TABLE)
+ .withTableName(TestUtils.TABLE_NAME_SHAPE)
+ .withValueFormat("%d: %s")
+ .withValueFields(List.of("id", "label"))
+ );
+
+ //////////////////////////////////////////////////////
+ // use that PVS in a new column on the person table //
+ //////////////////////////////////////////////////////
+ personTable.addField(new QFieldMetaData("currentShapeId", QFieldType.INTEGER)
+ .withPossibleValueSourceName("shapeV2")
+ );
+
+ ///////////////////////////////
+ // insert the list of shapes //
+ ///////////////////////////////
+ List shapeRecords = List.of(
+ new QRecord().withTableName(shapeTable.getName()).withValue("id", 1).withValue("name", "Triangle"),
+ new QRecord().withTableName(shapeTable.getName()).withValue("id", 2).withValue("name", "Square"),
+ new QRecord().withTableName(shapeTable.getName()).withValue("id", 3).withValue("name", "Circle"));
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(new QSession());
+ insertInput.setTableName(shapeTable.getName());
+ insertInput.setRecords(shapeRecords);
+ new InsertAction().execute(insertInput);
+
+ ///////////////////////////////////////////////////////
+ // define a list of persons pointing at those shapes //
+ ///////////////////////////////////////////////////////
+ List personRecords = List.of(
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 2),
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 3),
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3),
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3)
+ );
+
+ /////////////////////////
+ // translate the PVS's //
+ /////////////////////////
+ MemoryRecordStore.setCollectStatistics(true);
+ new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
+
+ /////////////////////////////////
+ // assert only 1 query was ran //
+ /////////////////////////////////
+ assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
+
+ ////////////////////////////////////////
+ // assert expected values and formats //
+ ////////////////////////////////////////
+ assertEquals("Triangle", personRecords.get(0).getDisplayValue("favoriteShapeId"));
+ assertEquals("2: Square", personRecords.get(0).getDisplayValue("currentShapeId"));
+ assertEquals("Triangle", personRecords.get(1).getDisplayValue("favoriteShapeId"));
+ assertEquals("3: Circle", personRecords.get(1).getDisplayValue("currentShapeId"));
+ assertEquals("Square", personRecords.get(2).getDisplayValue("favoriteShapeId"));
+ assertEquals("3: Circle", personRecords.get(2).getDisplayValue("currentShapeId"));
+ }
+
+
+
+ /*******************************************************************************
+ ** Make sure that if we have 2 different PVS's pointed at the same 1 table,
+ ** that we avoid re-doing queries, and that we actually get different (formatted) values.
+ *******************************************************************************/
+ @Test
+ void testCustomPossibleValue() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
+ String fieldName = "customValue";
+
+ //////////////////////////////////////////////////////////////
+ // define a list of persons with values in the custom field //
+ //////////////////////////////////////////////////////////////
+ List personRecords = List.of(
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 1),
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 2),
+ new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, "Buckle my shoe")
+ );
+
+ /////////////////////////
+ // translate the PVS's //
+ /////////////////////////
+ new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
+
+ ////////////////////////////////////////
+ // assert expected values and formats //
+ ////////////////////////////////////////
+ assertEquals("Custom[1]", personRecords.get(0).getDisplayValue(fieldName));
+ assertEquals("Custom[2]", personRecords.get(1).getDisplayValue(fieldName));
+ assertEquals("Custom[Buckle my shoe]", personRecords.get(2).getDisplayValue(fieldName));
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSetDisplayValuesInRecords()
{
- QTableMetaData table = new QTableMetaData()
- .withRecordLabelFormat("%s %s")
- .withRecordLabelFields("firstName", "lastName")
- .withField(new QFieldMetaData("firstName", QFieldType.STRING))
- .withField(new QFieldMetaData("lastName", QFieldType.STRING))
- .withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
- .withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(TestUtils.POSSIBLE_VALUE_SOURCE_STATE));
+ QTableMetaData table = TestUtils.defineTablePerson();
/////////////////////////////////////////////////////////////////
// first, make sure it doesn't crash with null or empty inputs //
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapterTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapterTest.java
index a81e5b53..7dff6bad 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapterTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapterTest.java
@@ -292,6 +292,11 @@ class CsvToQRecordAdapterTest
void testByteOrderMarker()
{
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // note - there's a zero-width non-breaking-space character (0xFEFF or some-such) //
+ // at the start of this string!! You may not be able to see it, depending on where you view this file. //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
List records = csvToQRecordAdapter.buildRecordsFromCsv("""
id,firstName
1,John""", TestUtils.defineTablePerson(), null);
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java
index 2404c915..c86e9fca 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java
@@ -140,6 +140,7 @@ class QInstanceEnricherTest
assertEquals("Field 20", QInstanceEnricher.nameToLabel("field20"));
assertEquals("Something USA", QInstanceEnricher.nameToLabel("somethingUSA"));
assertEquals("Number 1 Dad", QInstanceEnricher.nameToLabel("number1Dad"));
+ assertEquals("Number 417 Dad", QInstanceEnricher.nameToLabel("number417Dad"));
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
index d4c21bd8..ee2464a9 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
@@ -27,9 +27,14 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
+import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
@@ -114,12 +119,13 @@ class QInstanceValidatorTest
@Test
public void test_validateNullTables()
{
- assertValidationFailureReasonsAllowingExtraReasons((qInstance) ->
+ assertValidationFailureReasons((qInstance) ->
{
qInstance.setTables(null);
qInstance.setProcesses(null);
},
- "At least 1 table must be defined");
+ "At least 1 table must be defined",
+ "Unrecognized table shape for possibleValueSource shape");
}
@@ -131,12 +137,13 @@ class QInstanceValidatorTest
@Test
public void test_validateEmptyTables()
{
- assertValidationFailureReasonsAllowingExtraReasons((qInstance) ->
+ assertValidationFailureReasons((qInstance) ->
{
qInstance.setTables(new HashMap<>());
qInstance.setProcesses(new HashMap<>());
},
- "At least 1 table must be defined");
+ "At least 1 table must be defined",
+ "Unrecognized table shape for possibleValueSource shape");
}
@@ -191,7 +198,6 @@ class QInstanceValidatorTest
-
/*******************************************************************************
**
*******************************************************************************/
@@ -265,6 +271,138 @@ class QInstanceValidatorTest
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testTableCustomizers()
+ {
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference()),
+ "missing a code reference name", "missing a code type");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(null, QCodeType.JAVA, null)),
+ "missing a code reference name");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("", QCodeType.JAVA, null)),
+ "missing a code reference name");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", null, null)),
+ "missing a code type");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", QCodeType.JAVA, QCodeUsage.CUSTOMIZER)),
+ "Class for CodeReference could not be found");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerWithNoVoidConstructor.class, QCodeUsage.CUSTOMIZER)),
+ "Instance of CodeReference could not be created");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerThatIsNotAFunction.class, QCodeUsage.CUSTOMIZER)),
+ "CodeReference could not be casted");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameters.class, QCodeUsage.CUSTOMIZER)),
+ "Error validating customizer type parameters");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter1.class, QCodeUsage.CUSTOMIZER)),
+ "Error validating customizer type parameters");
+
+ assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter2.class, QCodeUsage.CUSTOMIZER)),
+ "Error validating customizer type parameters");
+
+ assertValidationSuccess((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerValid.class, QCodeUsage.CUSTOMIZER)));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerWithNoVoidConstructor
+ {
+ public CustomizerWithNoVoidConstructor(boolean b)
+ {
+
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerWithOnlyPrivateConstructor
+ {
+ private CustomizerWithOnlyPrivateConstructor()
+ {
+
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerThatIsNotAFunction
+ {
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerFunctionWithIncorrectTypeParameters implements Function
+ {
+ @Override
+ public String apply(String s)
+ {
+ return null;
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerFunctionWithIncorrectTypeParameter1 implements Function
+ {
+ @Override
+ public QRecord apply(String s)
+ {
+ return null;
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerFunctionWithIncorrectTypeParameter2 implements Function
+ {
+ @Override
+ public String apply(QRecord s)
+ {
+ return "Test";
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class CustomizerValid implements Function
+ {
+ @Override
+ public QRecord apply(QRecord record)
+ {
+ return null;
+ }
+ }
+
+
+
/*******************************************************************************
** Test that if a field specifies a backend that doesn't exist, that it fails.
**
@@ -443,18 +581,6 @@ class QInstanceValidatorTest
- /*******************************************************************************
- **
- *******************************************************************************/
- @Test
- void testPossibleValueSourceMissingIdType()
- {
- assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setIdType(null),
- "Missing an idType for possibleValueSource");
- }
-
-
-
/*******************************************************************************
**
*******************************************************************************/
@@ -516,7 +642,9 @@ class QInstanceValidatorTest
"is missing a customCodeReference");
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()),
- "not a possibleValueProvider");
+ "not a possibleValueProvider",
+ "missing a code reference name",
+ "missing a code type");
}
@@ -561,7 +689,8 @@ class QInstanceValidatorTest
{
if(!allowExtraReasons)
{
- assertEquals(reasons.length, e.getReasons().size(), "Expected number of validation failure reasons\nExpected: " + String.join(",", reasons) + "\nActual: " + e.getReasons());
+ int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
+ assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons) + "\nActual reasons: " + e.getReasons());
}
for(String reason : reasons)
@@ -573,6 +702,25 @@ class QInstanceValidatorTest
+ /*******************************************************************************
+ ** Assert that an instance is valid!
+ *******************************************************************************/
+ private void assertValidationSuccess(Consumer setup)
+ {
+ try
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ setup.accept(qInstance);
+ new QInstanceValidator().validate(qInstance);
+ }
+ catch(QInstanceValidationException e)
+ {
+ fail("Expected no validation errors, but received: " + e.getMessage());
+ }
+ }
+
+
+
/*******************************************************************************
** utility method for asserting that a specific reason string is found within
** the list of reasons in the QInstanceValidationException.
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
index 97ab5288..9bc5081f 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
@@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
import java.util.List;
import java.util.function.Function;
-import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
@@ -36,6 +36,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
@@ -68,6 +71,7 @@ class MemoryBackendModuleTest
void beforeAndAfter()
{
MemoryRecordStore.getInstance().reset();
+ MemoryRecordStore.resetStatistics();
}
@@ -122,8 +126,6 @@ class MemoryBackendModuleTest
assertEquals(3, new CountAction().execute(countInput).getCount());
- // todo - filters in query
-
//////////////////
// do an update //
//////////////////
@@ -152,6 +154,24 @@ class MemoryBackendModuleTest
assertEquals(3, new CountAction().execute(countInput).getCount());
+ /////////////////////////
+ // do a filtered query //
+ /////////////////////////
+ queryInput = new QueryInput(qInstance);
+ queryInput.setSession(session);
+ queryInput.setTableName(table.getName());
+ queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 3))));
+ queryOutput = new QueryAction().execute(queryInput);
+ assertEquals(2, queryOutput.getRecords().size());
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
+
+ /////////////////////////
+ // do a filtered count //
+ /////////////////////////
+ countInput.setFilter(queryInput.getFilter());
+ assertEquals(2, new CountAction().execute(countInput).getCount());
+
/////////////////
// do a delete //
/////////////////
@@ -173,6 +193,57 @@ class MemoryBackendModuleTest
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testSerials() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
+ QSession session = new QSession();
+
+ //////////////////
+ // do an insert //
+ //////////////////
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(session);
+ insertInput.setTableName(table.getName());
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 1")));
+ new InsertAction().execute(insertInput);
+
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 2")));
+ new InsertAction().execute(insertInput);
+
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 3")));
+ new InsertAction().execute(insertInput);
+
+ QueryInput queryInput = new QueryInput(qInstance);
+ queryInput.setSession(new QSession());
+ queryInput.setTableName(table.getName());
+ QueryOutput queryOutput = new QueryAction().execute(queryInput);
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
+
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 4).withValue("name", "Shape 4")));
+ new InsertAction().execute(insertInput);
+ queryOutput = new QueryAction().execute(queryInput);
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(4)));
+
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 6).withValue("name", "Shape 6")));
+ new InsertAction().execute(insertInput);
+ queryOutput = new QueryAction().execute(queryInput);
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(6)));
+
+ insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 7")));
+ new InsertAction().execute(insertInput);
+ queryOutput = new QueryAction().execute(queryInput);
+ assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(7)));
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -215,7 +286,7 @@ class MemoryBackendModuleTest
///////////////////////////////////
// add a customizer to the table //
///////////////////////////////////
- table.withCustomizer(Customizers.POST_QUERY_RECORD, new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
+ table.withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
//////////////////
// do an insert //
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
index 61730733..3da02b62 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
@@ -206,12 +206,10 @@ public abstract class AbstractBaseFilesystemAction
{
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record ->
{
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // since the CSV adapter is the one responsible for putting records into the pipe (rather than the queryOutput), //
- // we must do some of QueryOutput's normal job here - and run the runPostQueryRecordCustomizer //
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Before the records go into the pipe, make sure their backend details are added to them //
+ ////////////////////////////////////////////////////////////////////////////////////////////
addBackendDetailsToRecord(record, file);
- queryOutput.runPostQueryRecordCustomizer(record);
}));
}
else
@@ -308,7 +306,7 @@ public abstract class AbstractBaseFilesystemAction
*******************************************************************************/
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
{
- Optional optionalCustomizer = table.getCustomizer(FilesystemCustomizers.POST_READ_FILE);
+ Optional optionalCustomizer = table.getCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole());
if(optionalCustomizer.isEmpty())
{
return (fileContents);
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java
deleted file mode 100644
index 8e2416e0..00000000
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2022. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.kingsrook.qqq.backend.module.filesystem.base.actions;
-
-
-import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
-
-
-/*******************************************************************************
- ** Standard place where the names of QQQ Customization points for filesystem-based
- ** backends are defined.
- *******************************************************************************/
-public interface FilesystemCustomizers extends Customizers
-{
- String POST_READ_FILE = "postReadFile";
-}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemTableCustomizers.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemTableCustomizers.java
new file mode 100644
index 00000000..06157ceb
--- /dev/null
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemTableCustomizers.java
@@ -0,0 +1,72 @@
+package com.kingsrook.qqq.backend.module.filesystem.base.actions;
+
+
+import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum FilesystemTableCustomizers
+{
+ POST_READ_FILE(new TableCustomizer("postReadFile", Function.class, ((Object x) ->
+ {
+ Function function = (Function) x;
+ String output = function.apply(new String());
+ })));
+
+ private final TableCustomizer tableCustomizer;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ FilesystemTableCustomizers(TableCustomizer tableCustomizer)
+ {
+ this.tableCustomizer = tableCustomizer;
+ }
+
+
+
+ /*******************************************************************************
+ ** Get the FilesystemTableCustomer for a given role (e.g., the role used in meta-data, not
+ ** the enum-constant name).
+ *******************************************************************************/
+ public static FilesystemTableCustomizers forRole(String name)
+ {
+ for(FilesystemTableCustomizers value : values())
+ {
+ if(value.tableCustomizer.getRole().equals(name))
+ {
+ return (value);
+ }
+ }
+
+ return (null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tableCustomizer
+ **
+ *******************************************************************************/
+ public TableCustomizer getTableCustomizer()
+ {
+ return tableCustomizer;
+ }
+
+
+
+ /*******************************************************************************
+ ** get the role from the tableCustomizer
+ **
+ *******************************************************************************/
+ public String getRole()
+ {
+ return (tableCustomizer.getRole());
+ }
+
+}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java
index 8464bb22..1ea36990 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncStep.java
@@ -91,7 +91,7 @@ public class FilesystemSyncStep implements BackendStep
String sourceFileName = sourceEntry.getKey();
if(!archiveFiles.contains(sourceFileName))
{
- LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable.getName() + "] and [" + processingTable.getName() + "]");
+ LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable + "] and [" + processingTable + "]");
InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue());
byte[] bytes = inputStream.readAllBytes();
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/FilesystemModuleJunitExtension.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/FilesystemModuleJunitExtension.java
deleted file mode 100644
index e9bea180..00000000
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/FilesystemModuleJunitExtension.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2022. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.kingsrook.qqq.backend.module.filesystem;
-
-
-/*******************************************************************************
- **
- *******************************************************************************/
-public class FilesystemModuleJunitExtension // implements Extension, BeforeAllCallback, AfterAllCallback
-{
-}
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountActionTest.java
index 2c488900..46cfabe2 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountActionTest.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountActionTest.java
@@ -22,22 +22,16 @@
package com.kingsrook.qqq.backend.module.filesystem.local.actions;
-import java.util.function.Function;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
-import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
-import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
-import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemCustomizers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/*******************************************************************************
- **
+ ** Unit test for FilesystemCountAction
*******************************************************************************/
public class FilesystemCountActionTest extends FilesystemActionTest
{
@@ -55,35 +49,4 @@ public class FilesystemCountActionTest extends FilesystemActionTest
Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
}
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- @Test
- public void testCountWithFileCustomizer() throws QException
- {
- CountInput countInput = new CountInput();
- QInstance instance = TestUtils.defineInstance();
-
- QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
- table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
-
- countInput.setInstance(instance);
- countInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
- CountOutput countOutput = new FilesystemCountAction().execute(countInput);
- Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
- }
-
-
-
- public static class ValueUpshifter implements Function
- {
- @Override
- public String apply(String s)
- {
- return (s.replaceAll("kingsrook.com", "KINGSROOK.COM"));
- }
- }
-
}
\ No newline at end of file
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
index be40e86a..f6646085 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
@@ -32,7 +32,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
-import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemCustomizers;
+import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemTableCustomizers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -71,7 +71,7 @@ public class FilesystemQueryActionTest extends FilesystemActionTest
QInstance instance = TestUtils.defineInstance();
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
- table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
+ table.withCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole(), new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
queryInput.setInstance(instance);
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
index 72e0ee79..85b72727 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
@@ -197,6 +197,12 @@ public class QJavalinImplementation
try
{
QInstance newQInstance = qInstanceHotSwapSupplier.get();
+ if(newQInstance == null)
+ {
+ LOG.warn("Got a null qInstance from hotSwapSupplier. Not hot-swapping.");
+ return;
+ }
+
new QInstanceValidator().validate(newQInstance);
QJavalinImplementation.qInstance = newQInstance;
LOG.info("Swapped qInstance");
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleCli.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleCli.java
index d524c6cd..78884a30 100644
--- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleCli.java
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleCli.java
@@ -22,7 +22,6 @@
package com.kingsrook.sampleapp;
-import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.frontend.picocli.QPicoCliImplementation;
@@ -37,7 +36,8 @@ public class SampleCli
*******************************************************************************/
public static void main(String[] args)
{
- new SampleCli().run(args);
+ int exitCode = new SampleCli().run(args);
+ System.exit(exitCode);
}
@@ -45,31 +45,20 @@ public class SampleCli
/*******************************************************************************
**
*******************************************************************************/
- private void run(String[] args)
+ int run(String[] args)
{
try
{
- int exitCode = runForExitCode(args);
- System.exit(exitCode);
+ QInstance qInstance = SampleMetaDataProvider.defineInstance();
+ QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
+
+ return (qPicoCliImplementation.runCli("my-sample-cli", args));
}
catch(Exception e)
{
e.printStackTrace();
- System.exit(-1);
+ return (-1);
}
}
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- int runForExitCode(String[] args) throws QException
- {
- QInstance qInstance = SampleMetaDataProvider.defineInstance();
- QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
- int exitCode = qPicoCliImplementation.runCli("my-sample-cli", args);
- return exitCode;
- }
-
}
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
index aa5b049b..e88ec6c5 100644
--- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
@@ -22,7 +22,6 @@
package com.kingsrook.sampleapp;
-import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import io.javalin.Javalin;
@@ -71,6 +70,24 @@ public class SampleJavalinServer
config.enableCorsForAllOrigins();
}).start(PORT);
javalinService.routes(qJavalinImplementation.getRoutes());
+
+ /////////////////////////////////////////////////////////////////
+ // set the server to hot-swap the q instance before all routes //
+ /////////////////////////////////////////////////////////////////
+ QJavalinImplementation.setQInstanceHotSwapSupplier(() ->
+ {
+ try
+ {
+ return (SampleMetaDataProvider.defineInstance());
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Error hot-swapping meta data", e);
+ return (null);
+ }
+ });
+ javalinService.before(QJavalinImplementation::hotSwapQInstance);
+
javalinService.after(ctx ->
ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
}
diff --git a/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/SampleCliTest.java b/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/SampleCliTest.java
index ab4546cc..be51bead 100644
--- a/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/SampleCliTest.java
+++ b/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/SampleCliTest.java
@@ -25,6 +25,7 @@ package com.kingsrook.sampleapp;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
/*******************************************************************************
@@ -37,10 +38,21 @@ class SampleCliTest
**
*******************************************************************************/
@Test
- void test() throws QException
+ void testExitSuccess() throws QException
{
- int exitCode = new SampleCli().runForExitCode(new String[] { "--meta-data" });
+ int exitCode = new SampleCli().run(new String[] { "--meta-data" });
assertEquals(0, exitCode);
}
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testNotExitSuccess() throws QException
+ {
+ int exitCode = new SampleCli().run(new String[] { "asdfasdf" });
+ assertNotEquals(0, exitCode);
+ }
+
}
\ No newline at end of file