From 40b4b55bf43125358d8b2e1d4bb48f1c5cb62be2 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 31 Jan 2025 14:32:26 -0600 Subject: [PATCH] Add preInsertOrUpdate, postInsertOrUpdate, and oldRecordListToMap --- .../customizers/TableCustomizerInterface.java | 104 +++++- .../TableCustomizerInterfaceTest.java | 325 ++++++++++++++++++ 2 files changed, 420 insertions(+), 9 deletions(-) create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterfaceTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterface.java index 3a7cfa41..284d2df0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterface.java @@ -22,15 +22,19 @@ package com.kingsrook.qqq.backend.core.actions.customizers; +import java.io.Serializable; import java.util.List; +import java.util.Map; import java.util.Optional; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; 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.utils.CollectionUtils; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -47,7 +51,6 @@ public interface TableCustomizerInterface { QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class); - /******************************************************************************* ** custom actions to run after a query (or get!) takes place. ** @@ -77,8 +80,15 @@ public interface TableCustomizerInterface *******************************************************************************/ default List preInsert(InsertInput insertInput, List records, boolean isPreview) throws QException { - LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); - return (records); + try + { + return (preInsertOrUpdate(insertInput, records, isPreview, Optional.empty())); + } + catch(NotImplementedHereException e) + { + LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); + return (records); + } } @@ -104,8 +114,15 @@ public interface TableCustomizerInterface *******************************************************************************/ default List postInsert(InsertInput insertInput, List records) throws QException { - LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); - return (records); + try + { + return (postInsertOrUpdate(insertInput, records, Optional.empty())); + } + catch(NotImplementedHereException e) + { + LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); + return (records); + } } @@ -130,8 +147,15 @@ public interface TableCustomizerInterface *******************************************************************************/ default List preUpdate(UpdateInput updateInput, List records, boolean isPreview, Optional> oldRecordList) throws QException { - LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); - return (records); + try + { + return (preInsertOrUpdate(updateInput, records, isPreview, oldRecordList)); + } + catch(NotImplementedHereException e) + { + LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); + return (records); + } } @@ -151,8 +175,15 @@ public interface TableCustomizerInterface *******************************************************************************/ default List postUpdate(UpdateInput updateInput, List records, Optional> oldRecordList) throws QException { - LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); - return (records); + try + { + return (postInsertOrUpdate(updateInput, records, oldRecordList)); + } + catch(NotImplementedHereException e) + { + LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); + return (records); + } } @@ -199,4 +230,59 @@ public interface TableCustomizerInterface return (records); } + + /*************************************************************************** + ** Optional method to override in a customizer that does the same thing for + ** both preInsert & preUpdate. + ***************************************************************************/ + default List preInsertOrUpdate(AbstractActionInput input, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + throw NotImplementedHereException.instance; + } + + + /*************************************************************************** + ** Optional method to override in a customizer that does the same thing for + ** both postInsert & postUpdate. + ***************************************************************************/ + default List postInsertOrUpdate(AbstractActionInput input, List records, Optional> oldRecordList) throws QException + { + throw NotImplementedHereException.instance; + } + + + /*************************************************************************** + ** + ***************************************************************************/ + default Optional> oldRecordListToMap(String primaryKeyField, Optional> oldRecordList) + { + if(oldRecordList.isPresent()) + { + return (Optional.of(CollectionUtils.listToMap(oldRecordList.get(), r -> r.getValue(primaryKeyField)))); + } + else + { + return (Optional.empty()); + } + } +} + + + +/*************************************************************************** + ** + ***************************************************************************/ +class NotImplementedHereException extends QException +{ + static NotImplementedHereException instance = new NotImplementedHereException(); + + + + /*************************************************************************** + ** + ***************************************************************************/ + public NotImplementedHereException() + { + super("Not implemented here"); + } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterfaceTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterfaceTest.java new file mode 100644 index 00000000..c4129707 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/TableCustomizerInterfaceTest.java @@ -0,0 +1,325 @@ +/* + * 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 java.util.Optional; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QCollectingLogger; +import com.kingsrook.qqq.backend.core.logging.QLogger; +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.actions.tables.update.UpdateInput; +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.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 TableCustomizerInterface + *******************************************************************************/ +class TableCustomizerInterfaceTest extends BaseTest +{ + private static List events = new ArrayList<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + @AfterEach + void beforeAndAfterEach() + { + events.clear(); + QLogger.deactivateCollectingLoggerForClass(TableCustomizerInterface.class); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPreInsertOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreInsertOnly.class)) + .withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreInsertOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PreInsertOnly.preInsert()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1); + assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of preUpdate is running"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPreUpdateOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreUpdateOnly.class)) + .withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreUpdateOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PreUpdateOnly.preUpdate()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1); + assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of preInsert is running"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPreInsertOrUpdateOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreInsertOrUpdateOnly.class)) + .withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreInsertOrUpdateOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PreInsertOrUpdateOnly.preInsertOrUpdate()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(2).allMatch(s -> s.contains("PreInsertOrUpdateOnly.preInsertOrUpdate()")); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPostInsertOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostInsertOnly.class)) + .withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostInsertOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PostInsertOnly.postInsert()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1); + assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of postUpdate is running"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPostUpdateOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostUpdateOnly.class)) + .withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostUpdateOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PostUpdateOnly.postUpdate()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1); + assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of postInsert is running"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPostInsertOrUpdateOnly() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostInsertOrUpdateOnly.class)) + .withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostInsertOrUpdateOnly.class)); + reInitInstanceInContext(qInstance); + + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class); + + new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(1).contains("PostInsertOrUpdateOnly.postInsertOrUpdate()"); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertThat(events).hasSize(2).allMatch(s -> s.contains("PostInsertOrUpdateOnly.postInsertOrUpdate()")); + assertThat(collectingLogger.getCollectedMessages()).hasSize(0); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PreInsertOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List preInsert(InsertInput insertInput, List records, boolean isPreview) throws QException + { + events.add("PreInsertOnly.preInsert()"); + return (records); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PreUpdateOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List preUpdate(UpdateInput updateInput, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + events.add("PreUpdateOnly.preUpdate()"); + return (records); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PreInsertOrUpdateOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List preInsertOrUpdate(AbstractActionInput input, List records, boolean isPreview, Optional> oldRecordList) throws QException + { + events.add("PreInsertOrUpdateOnly.preInsertOrUpdate()"); + return (records); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PostInsertOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List postInsert(InsertInput insertInput, List records) throws QException + { + events.add("PostInsertOnly.postInsert()"); + return (records); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PostUpdateOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List postUpdate(UpdateInput updateInput, List records, Optional> oldRecordList) throws QException + { + events.add("PostUpdateOnly.postUpdate()"); + return (records); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class PostInsertOrUpdateOnly implements TableCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List postInsertOrUpdate(AbstractActionInput input, List records, Optional> oldRecordList) throws QException + { + events.add("PostInsertOrUpdateOnly.postInsertOrUpdate()"); + return (records); + } + } + +} \ No newline at end of file