Initial checkin

This commit is contained in:
2025-06-18 15:37:15 -05:00
parent cb6101d0ed
commit 4fd68f9195
2 changed files with 367 additions and 0 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TableCustomizerInterface> 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:
* <pre>
* table.withCustomizer(TableCustomizers.POST_INSERT_RECORD,
* MultiCustomizer.of(QCodeReference(x), QCodeReference(y)));
* </pre>
*
* @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<QCodeReference> 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<QCodeReference> list = (ArrayList<QCodeReference>) 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<QCodeReference>.");
}
}
if(customizers.isEmpty())
{
LOG.info("No TableCustomizers were specified for MultiCustomizer.");
}
}
/***************************************************************************
* run postQuery method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postQuery(queryInput, records);
}
return records;
}
/***************************************************************************
* run preInsert method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> 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<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postInsert(insertInput, records);
}
return records;
}
/***************************************************************************
* run preUpdate method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> 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<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> 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<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> 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<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postDelete(deleteInput, records);
}
return records;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
events.add("CustomizerA.preInsert");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class CustomizerB implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
events.add("CustomizerB.preInsert");
return (records);
}
}
}