diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizer.java new file mode 100644 index 00000000..24992700 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/MultiCustomizer.java @@ -0,0 +1,226 @@ +/* + * 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.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface; +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.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; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties; +import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; + + +/******************************************************************************* + ** Implementation of TableCustomizerInterface that runs multiple other customizers + *******************************************************************************/ +public class MultiCustomizer implements InitializableViaCodeReference, TableCustomizerInterface +{ + private static final String KEY_CODE_REFERENCES = "codeReferences"; + + private List 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