customizers = new ArrayList<>();
+
+
+ /***************************************************************************
+ * Factory method that builds a {@link QCodeReferenceWithProperties} that will
+ * allow this multi-customizer to be assigned to a table, and to track
+ * in that code ref's properties, the "sub" QCodeReferences to be used.
+ *
+ * Added to a table as in:
+ *
+ * table.withCustomizer(TableCustomizers.POST_INSERT_RECORD,
+ * MultiCustomizer.of(QCodeReference(x), QCodeReference(y)));
+ *
+ *
+ * @param codeReferences
+ * one or more {@link QCodeReference objects} to run when this customizer
+ * runs. note that they will run in the order provided in this list.
+ ***************************************************************************/
+ public static QCodeReferenceWithProperties of(QCodeReference... codeReferences)
+ {
+ ArrayList list = new ArrayList<>(Arrays.stream(codeReferences).toList());
+ return (new QCodeReferenceWithProperties(MultiCustomizer.class, MapBuilder.of(KEY_CODE_REFERENCES, list)));
+ }
+
+
+ /***************************************************************************
+ * Add an additional table customizer code reference to an existing
+ * codeReference, e.g., constructed by the `of` factory method.
+ *
+ * @see #of(QCodeReference...)
+ ***************************************************************************/
+ public static void addTableCustomizer(QCodeReferenceWithProperties existingMultiCustomizerCodeReference, QCodeReference codeReference)
+ {
+ ArrayList list = (ArrayList) existingMultiCustomizerCodeReference.getProperties().computeIfAbsent(KEY_CODE_REFERENCES, key -> new ArrayList<>());
+ list.add(codeReference);
+ }
+
+
+
+ /***************************************************************************
+ * When this class is instantiated by the QCodeLoader, initialize the
+ * sub-customizer objects.
+ ***************************************************************************/
+ @Override
+ public void initialize(QCodeReference codeReference)
+ {
+ if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
+ {
+ Serializable codeReferencesPropertyValue = codeReferenceWithProperties.getProperties().get(KEY_CODE_REFERENCES);
+ if(codeReferencesPropertyValue instanceof List> list)
+ {
+ for(Object o : list)
+ {
+ if(o instanceof QCodeReference reference)
+ {
+ TableCustomizerInterface customizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, reference);
+ customizers.add(customizer);
+ }
+ }
+ }
+ else
+ {
+ LOG.warn("Property KEY_CODE_REFERENCES [" + KEY_CODE_REFERENCES + "] must be a List.");
+ }
+ }
+
+ if(customizers.isEmpty())
+ {
+ LOG.info("No TableCustomizers were specified for MultiCustomizer.");
+ }
+ }
+
+
+
+ /***************************************************************************
+ * run postQuery method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List postQuery(QueryOrGetInputInterface queryInput, List records) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.postQuery(queryInput, records);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run preInsert method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List preInsert(InsertInput insertInput, List records, boolean isPreview) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.preInsert(insertInput, records, isPreview);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run postInsert method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List postInsert(InsertInput insertInput, List records) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.postInsert(insertInput, records);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run preUpdate method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List preUpdate(UpdateInput updateInput, List records, boolean isPreview, Optional> oldRecordList) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.preUpdate(updateInput, records, isPreview, oldRecordList);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run postUpdate method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List postUpdate(UpdateInput updateInput, List records, Optional> oldRecordList) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.postUpdate(updateInput, records, oldRecordList);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run preDelete method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List preDelete(DeleteInput deleteInput, List records, boolean isPreview) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.preDelete(deleteInput, records, isPreview);
+ }
+ return records;
+ }
+
+
+
+ /***************************************************************************
+ * run postDelete method over all sub-customizers
+ ***************************************************************************/
+ @Override
+ public List postDelete(DeleteInput deleteInput, List records) throws QException
+ {
+ for(TableCustomizerInterface customizer : customizers)
+ {
+ records = customizer.postDelete(deleteInput, records);
+ }
+ return records;
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/DeleteAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/DeleteAction.java
index a5028027..17a96091 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/DeleteAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/DeleteAction.java
@@ -401,6 +401,7 @@ public class DeleteAction
if(CollectionUtils.nullSafeHasContents(associatedKeys))
{
DeleteInput nextLevelDeleteInput = new DeleteInput();
+ nextLevelDeleteInput.setFlags(deleteInput.getFlags());
nextLevelDeleteInput.setTransaction(deleteInput.getTransaction());
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
index 29266e13..e56da2b7 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
@@ -34,7 +34,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
-import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
@@ -158,7 +157,7 @@ public class InsertAction extends AbstractQActionFunction insertedRecords, QBackendTransaction transaction) throws QException
+ private void manageAssociations(QTableMetaData table, List insertedRecords, InsertInput insertInput) throws QException
{
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
@@ -419,7 +418,8 @@ public class InsertAction extends AbstractQActionFunction r.getValue(associatedTable.getPrimaryKeyField())).collect(Collectors.toList()));
@@ -617,6 +618,7 @@ public class UpdateAction
LOG.debug("Updating associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
UpdateInput nextLevelUpdateInput = new UpdateInput();
nextLevelUpdateInput.setTransaction(updateInput.getTransaction());
+ nextLevelUpdateInput.setFlags(updateInput.getFlags());
nextLevelUpdateInput.setTableName(association.getAssociatedTableName());
nextLevelUpdateInput.setRecords(nextLevelUpdates);
UpdateOutput nextLevelUpdateOutput = new UpdateAction().execute(nextLevelUpdateInput);
@@ -627,6 +629,7 @@ public class UpdateAction
LOG.debug("Inserting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
InsertInput nextLevelInsertInput = new InsertInput();
nextLevelInsertInput.setTransaction(updateInput.getTransaction());
+ nextLevelInsertInput.setFlags(updateInput.getFlags());
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
nextLevelInsertInput.setRecords(nextLevelInserts);
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java
index da1421bc..fe6faa12 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java
@@ -47,12 +47,12 @@ public abstract class BasicCustomPossibleValueProvider getAllSourceObjects();
+ protected abstract List getAllSourceObjects() throws QException;
@@ -60,7 +60,7 @@ public abstract class BasicCustomPossibleValueProvider getPossibleValue(Serializable idValue)
+ public QPossibleValue getPossibleValue(Serializable idValue) throws QException
{
S sourceObject = getSourceObject(idValue);
if(sourceObject == null)
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java
index d32e4e79..b27097a3 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java
@@ -45,7 +45,7 @@ public interface QCustomPossibleValueProvider
/*******************************************************************************
**
*******************************************************************************/
- QPossibleValue getPossibleValue(Serializable idValue);
+ QPossibleValue getPossibleValue(Serializable idValue) throws QException;
/*******************************************************************************
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/ActionFlag.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/ActionFlag.java
new file mode 100644
index 00000000..eacb5923
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/ActionFlag.java
@@ -0,0 +1,35 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. 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.model.actions.tables;
+
+
+import java.io.Serializable;
+
+
+/*******************************************************************************
+ ** interface to mark enums (presumably classes too, but the original intent is
+ ** enums) that can be added to insert/update/delete action inputs to flag behaviors
+ *******************************************************************************/
+public interface ActionFlag extends Serializable
+{
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/delete/DeleteInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/delete/DeleteInput.java
index c7a92518..9c555f53 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/delete/DeleteInput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/delete/DeleteInput.java
@@ -24,9 +24,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@@ -47,6 +50,8 @@ public class DeleteInput extends AbstractTableActionInput
private boolean omitDmlAudit = false;
private String auditContext = null;
+ private Set flags;
+
/*******************************************************************************
@@ -295,4 +300,65 @@ public class DeleteInput extends AbstractTableActionInput
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for flags
+ *******************************************************************************/
+ public Set getFlags()
+ {
+ return (this.flags);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for flags
+ *******************************************************************************/
+ public void setFlags(Set flags)
+ {
+ this.flags = flags;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for flags
+ *******************************************************************************/
+ public DeleteInput withFlags(Set flags)
+ {
+ this.flags = flags;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public DeleteInput withFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ this.flags = new HashSet<>();
+ }
+ this.flags.add(flag);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean hasFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ return (false);
+ }
+
+ return (this.flags.contains(flag));
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/insert/InsertInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/insert/InsertInput.java
index 1753f61f..0bf2d301 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/insert/InsertInput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/insert/InsertInput.java
@@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@@ -48,6 +51,8 @@ public class InsertInput extends AbstractTableActionInput
private boolean omitDmlAudit = false;
private String auditContext = null;
+ private Set flags;
+
/*******************************************************************************
@@ -316,4 +321,65 @@ public class InsertInput extends AbstractTableActionInput
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for flags
+ *******************************************************************************/
+ public Set getFlags()
+ {
+ return (this.flags);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for flags
+ *******************************************************************************/
+ public void setFlags(Set flags)
+ {
+ this.flags = flags;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for flags
+ *******************************************************************************/
+ public InsertInput withFlags(Set flags)
+ {
+ this.flags = flags;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public InsertInput withFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ this.flags = new HashSet<>();
+ }
+ this.flags.add(flag);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean hasFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ return (false);
+ }
+
+ return (this.flags.contains(flag));
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/replace/ReplaceInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/replace/ReplaceInput.java
index e518dec1..542c8045 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/replace/ReplaceInput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/replace/ReplaceInput.java
@@ -22,9 +22,12 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
@@ -39,12 +42,14 @@ public class ReplaceInput extends AbstractTableActionInput
private UniqueKey key;
private List records;
private QQueryFilter filter;
- private boolean performDeletes = true;
- private boolean allowNullKeyValuesToEqual = false;
- private boolean setPrimaryKeyInInsertedRecords = false;
+ private boolean performDeletes = true;
+ private boolean allowNullKeyValuesToEqual = false;
+ private boolean setPrimaryKeyInInsertedRecords = false;
private boolean omitDmlAudit = false;
+ private Set flags;
+
/*******************************************************************************
@@ -303,4 +308,65 @@ public class ReplaceInput extends AbstractTableActionInput
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for flags
+ *******************************************************************************/
+ public Set getFlags()
+ {
+ return (this.flags);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for flags
+ *******************************************************************************/
+ public void setFlags(Set flags)
+ {
+ this.flags = flags;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for flags
+ *******************************************************************************/
+ public ReplaceInput withFlags(Set flags)
+ {
+ this.flags = flags;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public ReplaceInput withFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ this.flags = new HashSet<>();
+ }
+ this.flags.add(flag);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean hasFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ return (false);
+ }
+
+ return (this.flags.contains(flag));
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/update/UpdateInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/update/UpdateInput.java
index 767b9ee5..d3645d37 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/update/UpdateInput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/update/UpdateInput.java
@@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.update;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@@ -56,6 +59,8 @@ public class UpdateInput extends AbstractTableActionInput
private boolean omitModifyDateUpdate = false;
private String auditContext = null;
+ private Set flags;
+
/*******************************************************************************
@@ -385,4 +390,65 @@ public class UpdateInput extends AbstractTableActionInput
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for flags
+ *******************************************************************************/
+ public Set getFlags()
+ {
+ return (this.flags);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for flags
+ *******************************************************************************/
+ public void setFlags(Set flags)
+ {
+ this.flags = flags;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for flags
+ *******************************************************************************/
+ public UpdateInput withFlags(Set flags)
+ {
+ this.flags = flags;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public UpdateInput withFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ this.flags = new HashSet<>();
+ }
+ this.flags.add(flag);
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public boolean hasFlag(ActionFlag flag)
+ {
+ if(this.flags == null)
+ {
+ return (false);
+ }
+
+ return (this.flags.contains(flag));
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java
new file mode 100644
index 00000000..b6ac044b
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java
@@ -0,0 +1,123 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. 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.model.tables;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
+import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
+import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
+import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
+import com.kingsrook.qqq.backend.core.actions.values.BasicCustomPossibleValueProvider;
+import com.kingsrook.qqq.backend.core.context.QContext;
+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.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+
+
+/*******************************************************************************
+ ** possible-value source provider for the `QQQ Table` PVS - a list of all tables
+ ** in an application/qInstance (that you have permission to see)
+ *******************************************************************************/
+public class QQQTableCustomPossibleValueProvider extends BasicCustomPossibleValueProvider
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected QPossibleValue makePossibleValue(QRecord sourceObject)
+ {
+ return (new QPossibleValue<>(sourceObject.getValueInteger("id"), sourceObject.getValueString("label")));
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected QRecord getSourceObject(Serializable id) throws QException
+ {
+ QRecord qqqTableRecord = GetAction.execute(QQQTable.TABLE_NAME, id);
+ if(qqqTableRecord == null)
+ {
+ return (null);
+ }
+
+ QTableMetaData table = QContext.getQInstance().getTable(qqqTableRecord.getValueString("name"));
+ return isTableAllowed(table) ? qqqTableRecord : null;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ protected List getAllSourceObjects() throws QException
+ {
+ List records = QueryAction.execute(QQQTable.TABLE_NAME, null);
+ ArrayList rs = new ArrayList<>();
+ for(QRecord record : records)
+ {
+ QTableMetaData table = QContext.getQInstance().getTable(record.getValueString("name"));
+ if(isTableAllowed(table))
+ {
+ rs.add(record);
+ }
+ }
+
+ return rs;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private boolean isTableAllowed(QTableMetaData table)
+ {
+ if(table == null)
+ {
+ return (false);
+ }
+
+ if(table.getIsHidden())
+ {
+ return (false);
+ }
+
+ PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table);
+ if(!PermissionCheckResult.ALLOW.equals(permissionCheckResult))
+ {
+ return (false);
+ }
+
+ return (true);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java
index e880a230..91712ab9 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java
@@ -27,6 +27,9 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+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.Capability;
@@ -125,10 +128,11 @@ public class QQQTablesMetaDataProvider
public QPossibleValueSource defineQQQTablePossibleValueSource()
{
return (new QPossibleValueSource()
- .withType(QPossibleValueSourceType.TABLE)
.withName(QQQTable.TABLE_NAME)
- .withTableName(QQQTable.TABLE_NAME))
- .withOrderByField("label");
+ .withType(QPossibleValueSourceType.CUSTOM)
+ .withIdType(QFieldType.INTEGER)
+ .withCustomCodeReference(new QCodeReference(QQQTableCustomPossibleValueProvider.class))
+ .withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY));
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/AbstractRecordSyncToScheduledJobProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/AbstractRecordSyncToScheduledJobProcess.java
index 65ed317c..90df487b 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/AbstractRecordSyncToScheduledJobProcess.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/AbstractRecordSyncToScheduledJobProcess.java
@@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
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;
@@ -52,10 +53,10 @@ import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
/*******************************************************************************
- ** Base class to manage creating scheduled jobs based on records in another table
- **
- ** Expected to be used via BaseSyncToScheduledJobTableCustomizer - see its javadoc.
- **
+ * Base class to manage creating scheduled jobs based on records in another table
+ *
+ * Expected to be used via BaseSyncToScheduledJobTableCustomizer - see its javadoc.
+ * @see BaseSyncToScheduledJobTableCustomizer
*******************************************************************************/
public abstract class AbstractRecordSyncToScheduledJobProcess extends AbstractTableSyncTransformStep implements MetaDataProducerInterface
{
@@ -65,6 +66,20 @@ public abstract class AbstractRecordSyncToScheduledJobProcess extends AbstractTa
+ /***************************************************************************
+ * action flags that can be put in an insert/update/delete input to control
+ * behavior of this process.
+ ***************************************************************************/
+ public enum ActionFlags implements ActionFlag
+ {
+ /***************************************************************************
+ * tell this process not to run upon such an action taken on the source table.
+ ***************************************************************************/
+ DO_NOT_SYNC
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -89,7 +104,6 @@ public abstract class AbstractRecordSyncToScheduledJobProcess extends AbstractTa
-
/*******************************************************************************
**
*******************************************************************************/
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/BaseSyncToScheduledJobTableCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/BaseSyncToScheduledJobTableCustomizer.java
index aebd82c1..ce36a096 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/BaseSyncToScheduledJobTableCustomizer.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/processes/BaseSyncToScheduledJobTableCustomizer.java
@@ -40,9 +40,11 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
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.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
@@ -84,16 +86,44 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
/***************************************************************************
- **
+ * Create a {@link QCodeReferenceWithProperties} that can be used to add this
+ * class to a table.
+ *
+ * If this is the only customizer for the post insert/update/delete events
+ * on your table, you can instead call setTableCustomizers. But if you want,
+ * for example, a sync-scheduled-job (what this customizer does) plus some other
+ * customizers, then you can call this method to get a code reference that you
+ * can add, for example, to {@link com.kingsrook.qqq.backend.core.actions.customizers.MultiCustomizer}
+ *
+ * @param tableMetaData the table that the customizer will be used on.
+ * @param syncProcess instance of the subclass of AbstractRecordSyncToScheduledJobProcess
+ * that should run in the table's post insert/update/delete
+ * events.
+ * @see #setTableCustomizers(QTableMetaData, AbstractRecordSyncToScheduledJobProcess)
***************************************************************************/
- public static void setTableCustomizers(QTableMetaData tableMetaData, AbstractRecordSyncToScheduledJobProcess syncProcess)
+ public static QCodeReferenceWithProperties makeCodeReference(QTableMetaData tableMetaData, AbstractRecordSyncToScheduledJobProcess syncProcess)
{
- QCodeReference codeReference = new QCodeReferenceWithProperties(BaseSyncToScheduledJobTableCustomizer.class, Map.of(
+ return new QCodeReferenceWithProperties(BaseSyncToScheduledJobTableCustomizer.class, Map.of(
KEY_TABLE_NAME, tableMetaData.getName(),
KEY_SYNC_PROCESS_NAME, syncProcess.getClass().getSimpleName(),
KEY_SCHEDULED_JOB_FOREIGN_KEY_TYPE, syncProcess.getScheduledJobForeignKeyType()
));
+ }
+
+
+ /***************************************************************************
+ * Add post insert/update/delete customizers to a table, that will run a
+ * sync-scheduled-job process.
+ *
+ * @param tableMetaData the table that the customizer will be used on.
+ * @param syncProcess instance of the subclass of AbstractRecordSyncToScheduledJobProcess
+ * that should run in the table's post insert/update/delete
+ * events.
+ ***************************************************************************/
+ public static void setTableCustomizers(QTableMetaData tableMetaData, AbstractRecordSyncToScheduledJobProcess syncProcess)
+ {
+ QCodeReference codeReference = makeCodeReference(tableMetaData, syncProcess);
tableMetaData.withCustomizer(TableCustomizers.POST_INSERT_RECORD, codeReference);
tableMetaData.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, codeReference);
tableMetaData.withCustomizer(TableCustomizers.POST_DELETE_RECORD, codeReference);
@@ -138,6 +168,16 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
@Override
public List postInsertOrUpdate(AbstractActionInput input, List records, Optional> oldRecordList) throws QException
{
+ if(input instanceof UpdateInput updateInput && updateInput.hasFlag(AbstractRecordSyncToScheduledJobProcess.ActionFlags.DO_NOT_SYNC))
+ {
+ return records;
+ }
+
+ if(input instanceof InsertInput insertInput && insertInput.hasFlag(AbstractRecordSyncToScheduledJobProcess.ActionFlags.DO_NOT_SYNC))
+ {
+ return records;
+ }
+
runSyncProcessForRecordList(records, syncProcessName);
return records;
}
@@ -157,7 +197,17 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
/***************************************************************************
- **
+ * Run the named process over a set of records (e.g., that were inserted or
+ * updated).
+ *
+ * This method is normally called from within this class, in postInsertOrUpdate.
+ *
+ * Note that if the {@link ScheduledJob} table isn't defined in the QInstance,
+ * that the process will not be called.
+ *
+ * @param records list of records to use as source records in the table-sync
+ * to the scheduledJob table.
+ * @param processName name of the sync-process to run.
***************************************************************************/
public void runSyncProcessForRecordList(List records, String processName)
{
@@ -199,7 +249,15 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
/***************************************************************************
- **
+ * Delete scheduled job records for source-table records that have been deleted.
+ *
+ * This method is normally called from within this class, in postDelete.
+ *
+ * Note that if the {@link ScheduledJob} table isn't defined in the QInstance,
+ * that the process will not be called.
+ *
+ * @param records list of records to use as foreign-key sources to identify
+ * scheduledJob records to delete
***************************************************************************/
public void deleteScheduledJobsForRecordList(List records)
{
@@ -296,15 +354,6 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
}
- /*******************************************************************************
- ** Getter for KEY_SCHEDULED_JOB_FOREIGN_KEY_TYPE
- *******************************************************************************/
- public String getKEY_SCHEDULED_JOB_FOREIGN_KEY_TYPE()
- {
- return (BaseSyncToScheduledJobTableCustomizer.KEY_SCHEDULED_JOB_FOREIGN_KEY_TYPE);
- }
-
-
/*******************************************************************************
** Getter for scheduledJobForeignKeyType
@@ -335,5 +384,4 @@ public class BaseSyncToScheduledJobTableCustomizer implements TableCustomizerInt
return (this);
}
-
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizerTest.java
new file mode 100644
index 00000000..64508617
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizerTest.java
@@ -0,0 +1,141 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. 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.ArrayList;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.BaseTest;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+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.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/*******************************************************************************
+ ** Unit test for MultiCustomizer
+ *******************************************************************************/
+class MultiCustomizerTest extends BaseTest
+{
+ private static List events = new ArrayList<>();
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ @AfterEach
+ void beforeAndAfterEach()
+ {
+ events.clear();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
+ .withCustomizer(TableCustomizers.PRE_INSERT_RECORD, MultiCustomizer.of(
+ new QCodeReference(CustomizerA.class),
+ new QCodeReference(CustomizerB.class)
+ ));
+ reInitInstanceInContext(qInstance);
+
+ new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
+ assertThat(events).hasSize(2)
+ .contains("CustomizerA.preInsert")
+ .contains("CustomizerB.preInsert");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testAddingMore() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+
+ QCodeReferenceWithProperties multiCustomizer = MultiCustomizer.of(new QCodeReference(CustomizerA.class));
+ MultiCustomizer.addTableCustomizer(multiCustomizer, new QCodeReference(CustomizerB.class));
+
+ qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).withCustomizer(TableCustomizers.PRE_INSERT_RECORD, multiCustomizer);
+ reInitInstanceInContext(qInstance);
+
+ new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
+ assertThat(events).hasSize(2)
+ .contains("CustomizerA.preInsert")
+ .contains("CustomizerB.preInsert");
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static class CustomizerA implements TableCustomizerInterface
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List preInsert(InsertInput insertInput, List records, boolean isPreview) throws QException
+ {
+ events.add("CustomizerA.preInsert");
+ return (records);
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static class CustomizerB implements TableCustomizerInterface
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public List preInsert(InsertInput insertInput, List records, boolean isPreview) throws QException
+ {
+ events.add("CustomizerB.preInsert");
+ return (records);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProviderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProviderTest.java
index 4bdbbc06..5bf5217b 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProviderTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProviderTest.java
@@ -80,7 +80,7 @@ class TablesCustomPossibleValueProviderTest extends BaseTest
**
*******************************************************************************/
@Test
- void testGetPossibleValue()
+ void testGetPossibleValue() throws QException
{
TablesCustomPossibleValueProvider provider = new TablesCustomPossibleValueProvider();
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java
new file mode 100644
index 00000000..6412df35
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java
@@ -0,0 +1,174 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. 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.model.tables;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.BaseTest;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
+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.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+
+/*******************************************************************************
+ ** Unit test for QQQTableCustomPossibleValueProvider
+ *******************************************************************************/
+class QQQTableCustomPossibleValueProviderTest extends BaseTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ void beforeEach() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+
+ qInstance.addTable(new QTableMetaData()
+ .withName("hidden")
+ .withIsHidden(true)
+ .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
+ .withPrimaryKeyField("id")
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER)));
+
+ qInstance.addTable(new QTableMetaData()
+ .withName("restricted")
+ .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION))
+ .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
+ .withPrimaryKeyField("id")
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER)));
+
+ new QQQTablesMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
+
+ QContext.init(qInstance, newSession());
+
+ for(String tableName : qInstance.getTables().keySet())
+ {
+ QQQTableTableManager.getQQQTableId(qInstance, tableName);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetPossibleValue() throws QException
+ {
+ Integer personTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), TestUtils.TABLE_NAME_PERSON);
+ QQQTableCustomPossibleValueProvider provider = new QQQTableCustomPossibleValueProvider();
+
+ QPossibleValue possibleValue = provider.getPossibleValue(personTableId);
+ assertEquals(personTableId, possibleValue.getId());
+ assertEquals("Person", possibleValue.getLabel());
+
+ assertNull(provider.getPossibleValue(-1));
+
+ Integer hiddenTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "hidden");
+ assertNull(provider.getPossibleValue(hiddenTableId));
+
+ Integer restrictedTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "restricted");
+ assertNull(provider.getPossibleValue(restrictedTableId));
+
+ QContext.getQSession().withPermission("restricted.hasAccess");
+ assertNotNull(provider.getPossibleValue(restrictedTableId));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testSearchPossibleValue() throws QException
+ {
+ Integer personTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), TestUtils.TABLE_NAME_PERSON);
+ Integer shapeTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), TestUtils.TABLE_NAME_SHAPE);
+ Integer hiddenTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "hidden");
+ Integer restrictedTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "restricted");
+
+ QQQTableCustomPossibleValueProvider provider = new QQQTableCustomPossibleValueProvider();
+
+ List> list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME));
+ assertThat(list).anyMatch(p -> p.getId().equals(personTableId));
+ assertThat(list).noneMatch(p -> p.getId().equals(-1));
+ assertThat(list).noneMatch(p -> p.getId().equals(hiddenTableId));
+ assertThat(list).noneMatch(p -> p.getId().equals(restrictedTableId));
+ assertNull(provider.getPossibleValue("restricted"));
+
+ list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME)
+ .withIdList(List.of(personTableId, shapeTableId, hiddenTableId)));
+ assertEquals(2, list.size());
+ assertThat(list).anyMatch(p -> p.getId().equals(personTableId));
+ assertThat(list).anyMatch(p -> p.getId().equals(shapeTableId));
+ assertThat(list).noneMatch(p -> p.getId().equals(hiddenTableId));
+
+ list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME)
+ .withLabelList(List.of("Person", "Shape", "Restricted")));
+ assertEquals(2, list.size());
+ assertThat(list).anyMatch(p -> p.getId().equals(personTableId));
+ assertThat(list).anyMatch(p -> p.getId().equals(shapeTableId));
+ assertThat(list).noneMatch(p -> p.getId().equals(restrictedTableId));
+
+ list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME)
+ .withSearchTerm("restricted"));
+ assertEquals(0, list.size());
+
+ /////////////////////////////////////////
+ // add permission for restricted table //
+ /////////////////////////////////////////
+ QContext.getQSession().withPermission("restricted.hasAccess");
+ list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME)
+ .withSearchTerm("restricted"));
+ assertEquals(1, list.size());
+
+ list = provider.search(new SearchPossibleValueSourceInput()
+ .withPossibleValueSourceName(QQQTable.TABLE_NAME)
+ .withLabelList(List.of("Person", "Shape", "Restricted")));
+ assertEquals(3, list.size());
+ assertThat(list).anyMatch(p -> p.getId().equals(personTableId));
+ assertThat(list).anyMatch(p -> p.getId().equals(shapeTableId));
+ assertThat(list).anyMatch(p -> p.getId().equals(restrictedTableId));
+
+ }
+
+}
\ No newline at end of file