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 5ce2ce38..29266e13 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 @@ -54,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; 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.joins.JoinOn; @@ -174,9 +175,21 @@ public class InsertAction extends AbstractQActionFunction postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole()); if(postInsertCustomizer.isPresent()) { @@ -193,7 +206,25 @@ public class InsertAction extends AbstractQActionFunction tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_INSERT_RECORD); + for(QCodeReference tableCustomizerCode : tableCustomizerCodes) + { + try + { + TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode); + insertOutput.setRecords(tableCustomizer.postInsert(insertInput, insertOutput.getRecords())); + } + catch(Exception e) + { + for(QRecord record : insertOutput.getRecords()) + { + record.addWarning(new QWarningMessage("An error occurred after the insert: " + e.getMessage())); + } + } + } } @@ -308,6 +339,19 @@ public class InsertAction extends AbstractQActionFunction tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_INSERT_RECORD); + for(QCodeReference tableCustomizerCode : tableCustomizerCodes) + { + TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode); + if(whenToRun.equals(tableCustomizer.whenToRunPreInsert(insertInput, isPreview))) + { + insertInput.setRecords(tableCustomizer.preInsert(insertInput, insertInput.getRecords(), isPreview)); + } + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java index 30fcde0b..8730ae14 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java @@ -57,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; @@ -199,6 +200,18 @@ public class UpdateAction ////////////////////////////////////////////////////////////// // finally, run the post-update customizer, if there is one // ////////////////////////////////////////////////////////////// + runPostUpdateCustomizers(updateInput, table, updateOutput, oldRecordList); + + return updateOutput; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static void runPostUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, UpdateOutput updateOutput, Optional> oldRecordList) + { Optional postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole()); if(postUpdateCustomizer.isPresent()) { @@ -215,7 +228,49 @@ public class UpdateAction } } - return updateOutput; + /////////////////////////////////////////////// + // run all of the instance-level customizers // + /////////////////////////////////////////////// + List tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_UPDATE_RECORD); + for(QCodeReference tableCustomizerCode : tableCustomizerCodes) + { + try + { + TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode); + updateOutput.setRecords(tableCustomizer.postUpdate(updateInput, updateOutput.getRecords(), oldRecordList)); + } + catch(Exception e) + { + for(QRecord record : updateOutput.getRecords()) + { + record.addWarning(new QWarningMessage("An error occurred after the update: " + e.getMessage())); + } + } + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static void runPreUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, Optional> oldRecordList, boolean isPreview) throws QException + { + Optional preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole()); + if(preUpdateCustomizer.isPresent()) + { + updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList)); + } + + /////////////////////////////////////////////// + // run all of the instance-level customizers // + /////////////////////////////////////////////// + List tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_UPDATE_RECORD); + for(QCodeReference tableCustomizerCode : tableCustomizerCodes) + { + TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode); + updateInput.setRecords(tableCustomizer.preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList)); + } } @@ -278,11 +333,7 @@ public class UpdateAction /////////////////////////////////////////////////////////////////////////// // after all validations, run the pre-update customizer, if there is one // /////////////////////////////////////////////////////////////////////////// - Optional preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole()); - if(preUpdateCustomizer.isPresent()) - { - updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList)); - } + runPreUpdateCustomizers(updateInput, table, oldRecordList, isPreview); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 8f7793ef..89af643f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -42,6 +42,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface; import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph; @@ -252,6 +253,17 @@ public class QInstanceValidator { validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class); } + + if(qInstance.getTableCustomizers() != null) + { + for(Map.Entry> entry : qInstance.getTableCustomizers().entrySet()) + { + for(QCodeReference codeReference : CollectionUtils.nonNullList(entry.getValue())) + { + validateSimpleCodeReference("Instance tableCustomizer of type " + entry.getKey() + ": ", codeReference, TableCustomizerInterface.class); + } + } + } } @@ -301,6 +313,17 @@ public class QInstanceValidator validatorPlugins.clear(); } + + + /******************************************************************************* + ** Getter for validatorPlugins + ** + *******************************************************************************/ + public static ListingHash, QInstanceValidatorPluginInterface> getValidatorPlugins() + { + return validatorPlugins; + } + /******************************************************************************* @@ -2238,8 +2261,7 @@ public class QInstanceValidator /******************************************************************************* ** *******************************************************************************/ - @SafeVarargs - private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class... anyOfExpectedClasses) + public void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class... anyOfExpectedClasses) { if(!preAssertionsForCodeReference(codeReference, prefix)) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java index 7b1fe014..49e64d17 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph; import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction; import com.kingsrook.qqq.backend.core.exceptions.QException; @@ -65,6 +67,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.StringUtils; import io.github.cdimascio.dotenv.Dotenv; import io.github.cdimascio.dotenv.DotenvEntry; @@ -116,6 +119,8 @@ public class QInstance private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance(); private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone(); + private ListingHash tableCustomizers; + @Deprecated(since = "migrated to metaDataCustomizer") private QCodeReference metaDataFilter = null; @@ -1623,4 +1628,76 @@ public class QInstance return (this); } + + + /******************************************************************************* + ** Getter for tableCustomizers + *******************************************************************************/ + public ListingHash getTableCustomizers() + { + return (this.tableCustomizers); + } + + + + /******************************************************************************* + ** Setter for tableCustomizers + *******************************************************************************/ + public void setTableCustomizers(ListingHash tableCustomizers) + { + this.tableCustomizers = tableCustomizers; + } + + + + /******************************************************************************* + ** Fluent setter for tableCustomizers + *******************************************************************************/ + public QInstance withTableCustomizers(ListingHash tableCustomizers) + { + this.tableCustomizers = tableCustomizers; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QInstance withTableCustomizer(String role, QCodeReference customizer) + { + if(this.tableCustomizers == null) + { + this.tableCustomizers = new ListingHash<>(); + } + + this.tableCustomizers.add(role, customizer); + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QInstance withTableCustomizer(TableCustomizers tableCustomizer, QCodeReference customizer) + { + return (withTableCustomizer(tableCustomizer.getRole(), customizer)); + } + + + + /******************************************************************************* + ** Getter for tableCustomizers + *******************************************************************************/ + public List getTableCustomizers(TableCustomizers tableCustomizer) + { + if(this.tableCustomizers == null) + { + return (Collections.emptyList()); + } + + return (this.tableCustomizers.getOrDefault(tableCustomizer.getRole(), Collections.emptyList())); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/InsertActionInstanceLevelTableCustomizersTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/InsertActionInstanceLevelTableCustomizersTest.java new file mode 100644 index 00000000..b911c7da --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/InsertActionInstanceLevelTableCustomizersTest.java @@ -0,0 +1,149 @@ +/* + * 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.tables; + + +import java.util.List; +import java.util.Optional; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; +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.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage; +import com.kingsrook.qqq.backend.core.utils.ListingHash; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class InsertActionInstanceLevelTableCustomizersTest +{ + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInstanceLevelCustomizers() throws QException + { + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(BreaksEverythingCustomizer.class)); + QRecord record = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("name", "octogon"))).getRecords().get(0); + assertEquals("Everything is broken", record.getErrorsAsString()); + assertNull(record.getValueInteger("id")); + + QContext.getQInstance().setTableCustomizers(new ListingHash<>()); + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(SetsFirstName.class)); + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(SetsLastName.class)); + QContext.getQInstance().withTableCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(DoesNothing.class)); + DoesNothing.callCount = 0; + record = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("name", "octogon"))).getRecords().get(0); + assertEquals("Jeff", record.getValueString("firstName")); + assertEquals("Smith", record.getValueString("lastName")); + assertNotNull(record.getValueInteger("id")); + assertEquals(1, DoesNothing.callCount); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class BreaksEverythingCustomizer implements TableCustomizerInterface + { + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List preInsertOrUpdate(AbstractActionInput input, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + records.forEach(r -> r.addError(new SystemErrorStatusMessage("Everything is broken"))); + return records; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class SetsFirstName implements TableCustomizerInterface + { + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List preInsertOrUpdate(AbstractActionInput input, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + records.forEach(r -> r.setValue("firstName", "Jeff")); + return records; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class SetsLastName implements TableCustomizerInterface + { + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List preInsertOrUpdate(AbstractActionInput input, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + records.forEach(r -> r.setValue("lastName", "Smith")); + return records; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class DoesNothing implements TableCustomizerInterface + { + static int callCount = 0; + + + + /*************************************************************************** + * + ***************************************************************************/ + @Override + public List postInsertOrUpdate(AbstractActionInput input, List records, Optional> oldRecordList) throws QException + { + callCount++; + return records; + } + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateActionInstanceLevelTableCustomizersTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateActionInstanceLevelTableCustomizersTest.java new file mode 100644 index 00000000..2f86e8eb --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateActionInstanceLevelTableCustomizersTest.java @@ -0,0 +1,70 @@ +/* + * 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.tables; + + +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; +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.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.QCodeReference; +import com.kingsrook.qqq.backend.core.utils.ListingHash; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class UpdateActionInstanceLevelTableCustomizersTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInstanceLevelCustomizers() throws QException + { + QRecord record = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("name", "octogon"))).getRecords().get(0); + + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(InsertActionInstanceLevelTableCustomizersTest.BreaksEverythingCustomizer.class)); + record = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", record.getValue("id")).withValue("name", "octogon"))).getRecords().get(0); + assertEquals("Everything is broken", record.getErrorsAsString()); + + QContext.getQInstance().setTableCustomizers(new ListingHash<>()); + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(InsertActionInstanceLevelTableCustomizersTest.SetsFirstName.class)); + QContext.getQInstance().withTableCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(InsertActionInstanceLevelTableCustomizersTest.SetsLastName.class)); + QContext.getQInstance().withTableCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(InsertActionInstanceLevelTableCustomizersTest.DoesNothing.class)); + InsertActionInstanceLevelTableCustomizersTest.DoesNothing.callCount = 0; + record = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", record.getValue("id")).withValue("name", "octogon"))).getRecords().get(0); + assertEquals("Jeff", record.getValueString("firstName")); + assertEquals("Smith", record.getValueString("lastName")); + assertNotNull(record.getValueInteger("id")); + assertEquals(1, InsertActionInstanceLevelTableCustomizersTest.DoesNothing.callCount); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index d790ca59..7f651074 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -176,6 +176,30 @@ public class QInstanceValidatorTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInstanceLevelTableCustomizers() + { + assertValidationFailureReasons((qInstance) -> qInstance.withTableCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(QInstanceValidator.class)), + "Instance tableCustomizer of type preInsertRecord: CodeReference is not of the expected type"); + + assertValidationFailureReasons((qInstance) -> + { + qInstance.withTableCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(QInstanceValidator.class)); + qInstance.withTableCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(QInstanceValidator.class)); + qInstance.withTableCustomizer(TableCustomizers.PRE_DELETE_RECORD, new QCodeReference(QInstanceValidator.class)); + }, + "Instance tableCustomizer of type postUpdateRecord: CodeReference is not of the expected type", + "Instance tableCustomizer of type postUpdateRecord: CodeReference is not of the expected type", + "Instance tableCustomizer of type preDeleteRecord: CodeReference is not of the expected type"); + + assertValidationSuccess((qInstance) -> qInstance.withTableCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(CustomizerValid.class))); + } + + + /******************************************************************************* ** Test an instance with null backends - should throw. **