diff --git a/pom.xml b/pom.xml
index 098a13fe..ea1003a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
true
true
0.80
+ 0.95
@@ -211,6 +212,11 @@
COVEREDRATIO
${coverage.instructionCoveredRatioMinimum}
+
+ CLASS
+ COVEREDRATIO
+ ${coverage.classCoveredRatioMinimum}
+
@@ -255,7 +261,7 @@ echo "------------------------------------------------------------"
which xpath > /dev/null 2>&1
if [ "$?" == "0" ]; then
echo "Element\nInstructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
- xpath -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
+ xpath -n -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
paste /tmp/$$.headers /tmp/$$.values | tail +2 | awk -v FS='\t' '{printf("%-20s %s\n",$1,$2)}'
rm /tmp/$$.headers /tmp/$$.values
else
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
new file mode 100644
index 00000000..f1774928
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+
+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;
+
+
+/*******************************************************************************
+ ** Utility to load code for running QQQ customizers.
+ *******************************************************************************/
+public class QCodeLoader
+{
+ private static final Logger LOG = LogManager.getLogger(QCodeLoader.class);
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Optional> getTableCustomizerFunction(QTableMetaData table, String customizerName)
+ {
+ Optional codeReference = table.getCustomizer(customizerName);
+ if(codeReference.isPresent())
+ {
+ return (Optional.ofNullable(QCodeLoader.getFunction(codeReference.get())));
+ }
+ return (Optional.empty());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public static Function getFunction(QCodeReference codeReference)
+ {
+ if(codeReference == null)
+ {
+ return (null);
+ }
+
+ if(!codeReference.getCodeType().equals(QCodeType.JAVA))
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
+ ///////////////////////////////////////////////////////////////////////////////////////
+ throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
+ }
+
+ try
+ {
+ Class> customizerClass = Class.forName(codeReference.getName());
+ return ((Function) customizerClass.getConstructor().newInstance());
+ }
+ catch(Exception e)
+ {
+ LOG.error("Error initializing customizer: " + codeReference);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // return null here - under the assumption that during normal run-time operations, we'll never hit here //
+ // as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
+ // if it finds an invalid code reference //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ return (null);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ 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