Refactor table customizers to follow a common interface (point being, so you can have 1 class instead of many for a table's closely-related actions)

This commit is contained in:
2024-03-14 11:14:27 -05:00
parent 3265d6d842
commit 5f72978528
19 changed files with 523 additions and 76 deletions

View File

@ -47,12 +47,24 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
** records that the delete action marked in error - the user might want to do
** something special with them (idk, try some other way to delete them?)
*******************************************************************************/
public abstract class AbstractPostDeleteCustomizer
public abstract class AbstractPostDeleteCustomizer implements TableCustomizerInterface
{
protected DeleteInput deleteInput;
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
this.deleteInput = deleteInput;
return apply(records);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -42,12 +42,24 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
**
** Note that the full insertInput is available as a field in this class.
*******************************************************************************/
public abstract class AbstractPostInsertCustomizer
public abstract class AbstractPostInsertCustomizer implements TableCustomizerInterface
{
protected InsertInput insertInput;
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
this.insertInput = insertInput;
return (apply(records));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -23,16 +23,29 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
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.data.QRecord;
/*******************************************************************************
**
*******************************************************************************/
public abstract class AbstractPostQueryCustomizer
public abstract class AbstractPostQueryCustomizer implements TableCustomizerInterface
{
protected AbstractTableActionInput input;
protected QueryOrGetInputInterface input;
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
{
input = queryInput;
return apply(records);
}
@ -47,7 +60,7 @@ public abstract class AbstractPostQueryCustomizer
** Getter for input
**
*******************************************************************************/
public AbstractTableActionInput getInput()
public QueryOrGetInputInterface getInput()
{
return (input);
}
@ -58,7 +71,7 @@ public abstract class AbstractPostQueryCustomizer
** Setter for input
**
*******************************************************************************/
public void setInput(AbstractTableActionInput input)
public void setInput(QueryOrGetInputInterface input)
{
this.input = input;
}

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.HashMap;
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.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -48,7 +49,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
** available (if the backend supports it) - both as a list (`getOldRecordList`)
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
*******************************************************************************/
public abstract class AbstractPostUpdateCustomizer
public abstract class AbstractPostUpdateCustomizer implements TableCustomizerInterface
{
protected UpdateInput updateInput;
protected List<QRecord> oldRecordList;
@ -57,6 +58,19 @@ public abstract class AbstractPostUpdateCustomizer
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
this.updateInput = updateInput;
this.oldRecordList = oldRecordList.orElse(null);
return apply(records);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -50,7 +50,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
** Note that the full deleteInput is available as a field in this class.
**
*******************************************************************************/
public abstract class AbstractPreDeleteCustomizer
public abstract class AbstractPreDeleteCustomizer implements TableCustomizerInterface
{
protected DeleteInput deleteInput;
@ -58,6 +58,19 @@ public abstract class AbstractPreDeleteCustomizer
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
{
this.deleteInput = deleteInput;
this.isPreview = isPreview;
return apply(records);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -47,7 +47,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
**
** Note that the full insertInput is available as a field in this class.
*******************************************************************************/
public abstract class AbstractPreInsertCustomizer
public abstract class AbstractPreInsertCustomizer implements TableCustomizerInterface
{
protected InsertInput insertInput;
@ -70,6 +70,30 @@ public abstract class AbstractPreInsertCustomizer
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
this.insertInput = insertInput;
this.isPreview = isPreview;
return (apply(records));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public WhenToRun whenToRunPreInsert(InsertInput insertInput, boolean isPreview)
{
return getWhenToRun();
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.HashMap;
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.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -53,7 +54,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
** available (if the backend supports it) - both as a list (`getOldRecordList`)
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
*******************************************************************************/
public abstract class AbstractPreUpdateCustomizer
public abstract class AbstractPreUpdateCustomizer implements TableCustomizerInterface
{
protected UpdateInput updateInput;
protected List<QRecord> oldRecordList;
@ -63,6 +64,20 @@ public abstract class AbstractPreUpdateCustomizer
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
this.updateInput = updateInput;
this.isPreview = isPreview;
this.oldRecordList = oldRecordList.orElse(null);
return apply(records);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -64,12 +64,12 @@ public class QCodeLoader
/*******************************************************************************
**
*******************************************************************************/
public static <T> Optional<T> getTableCustomizer(Class<T> expectedClass, QTableMetaData table, String customizerName)
public static Optional<TableCustomizerInterface> getTableCustomizer(QTableMetaData table, String customizerName)
{
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
if(codeReference.isPresent())
{
return (Optional.ofNullable(QCodeLoader.getAdHoc(expectedClass, codeReference.get())));
return (Optional.ofNullable(QCodeLoader.getAdHoc(TableCustomizerInterface.class, codeReference.get())));
}
return (Optional.empty());
}

View File

@ -0,0 +1,202 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.List;
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.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 static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Common interface used by all (core) TableCustomizer types (e.g., post-query,
** and {pre,post}-{insert,update,delete}.
**
** Note that the abstract-base classes for each action still exist, though have
** been back-ported to be implementors of this interface. The action classes
** will now expect this type, and call this type's methods.
**
*******************************************************************************/
public interface TableCustomizerInterface
{
QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class);
/*******************************************************************************
** custom actions to run after a query (or get!) takes place.
**
*******************************************************************************/
default List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
{
LOG.info("A default implementation of postQuery is running... Probably not expected!", logPair("tableName", queryInput.getTableName()));
return (records);
}
/*******************************************************************************
** custom actions before an insert takes place.
**
** It's important for implementations to be aware of the isPreview field, which
** is set to true when the code is running to give users advice, e.g., on a review
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
** things like storing data, you don't want to do that if isPreview is true!!
**
** General implementation would be, to iterate over the records (the inputs to
** the insert action), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
** - possibly manipulating values (`setValue`)
** - possibly throwing an exception - if you really don't want the insert operation to continue.
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
*******************************************************************************/
default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
/*******************************************************************************
**
*******************************************************************************/
default AbstractPreInsertCustomizer.WhenToRun whenToRunPreInsert(InsertInput insertInput, boolean isPreview)
{
return (AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
}
/*******************************************************************************
** custom actions after an insert takes place.
**
** General implementation would be, to iterate over the records (the outputs of
** the insert action), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
** - possibly throwing an exception - though doing so won't stop the update, and instead
** will just set a warning on all of the updated records...
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) that you want to go back to the caller.
*******************************************************************************/
default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
/*******************************************************************************
** custom actions before an update takes place.
**
** It's important for implementations to be aware of the isPreview field, which
** is set to true when the code is running to give users advice, e.g., on a review
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
** things like storing data, you don't want to do that if isPreview is true!!
**
** General implementation would be, to iterate over the records (the inputs to
** the update action), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
** - possibly manipulating values (`setValue`)
** - possibly throwing an exception - if you really don't want the update operation to continue.
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
**
** Note, "old records" (e.g., with values freshly fetched from the backend) will be
** available (if the backend supports it)
*******************************************************************************/
default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
/*******************************************************************************
** custom actions after an update takes place.
**
** General implementation would be, to iterate over the records (the outputs of
** the update action), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
** - possibly throwing an exception - though doing so won't stop the update, and instead
** will just set a warning on all of the updated records...
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) that you want to go back to the caller.
**
** Note, "old records" (e.g., with values freshly fetched from the backend) will be
** available (if the backend supports it).
*******************************************************************************/
default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
/*******************************************************************************
** Custom actions before a delete takes place.
**
** It's important for implementations to be aware of the isPreview param, which
** is set to true when the code is running to give users advice, e.g., on a review
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
** things like storing data, you don't want to do that if isPreview is true!!
**
** General implementation would be, to iterate over the records (which the DeleteAction
** would look up based on the inputs to the delete action), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
** - possibly throwing an exception - if you really don't want the delete operation to continue.
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) - this is how errors
** and warnings are propagated to the DeleteAction. Note that any records with
** an error will NOT proceed to the backend's delete interface - but those with
** warnings will.
*******************************************************************************/
default List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
{
LOG.info("A default implementation of preDelete is running... Probably not expected!", logPair("tableName", deleteInput.getTableName()));
return (records);
}
/*******************************************************************************
** Custom actions after a delete takes place.
**
** General implementation would be, to iterate over the records (ones which didn't
** have a delete error), and look at their values:
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
** - possibly throwing an exception - though doing so won't stop the delete, and instead
** will just set a warning on all of the deleted records...
** - doing "whatever else" you may want to do.
** - returning the list of records (can be the input list) that you want to go back
** to the caller - this is how errors and warnings are propagated .
*******************************************************************************/
default List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
LOG.info("A default implementation of postDelete is running... Probably not expected!", logPair("tableName", deleteInput.getTableName()));
return (records);
}
}

View File

@ -29,13 +29,13 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
*******************************************************************************/
public enum TableCustomizers
{
POST_QUERY_RECORD("postQueryRecord", AbstractPostQueryCustomizer.class),
PRE_INSERT_RECORD("preInsertRecord", AbstractPreInsertCustomizer.class),
POST_INSERT_RECORD("postInsertRecord", AbstractPostInsertCustomizer.class),
PRE_UPDATE_RECORD("preUpdateRecord", AbstractPreUpdateCustomizer.class),
POST_UPDATE_RECORD("postUpdateRecord", AbstractPostUpdateCustomizer.class),
PRE_DELETE_RECORD("preDeleteRecord", AbstractPreDeleteCustomizer.class),
POST_DELETE_RECORD("postDeleteRecord", AbstractPostDeleteCustomizer.class);
POST_QUERY_RECORD("postQueryRecord", TableCustomizerInterface.class),
PRE_INSERT_RECORD("preInsertRecord", TableCustomizerInterface.class),
POST_INSERT_RECORD("postInsertRecord", TableCustomizerInterface.class),
PRE_UPDATE_RECORD("preUpdateRecord", TableCustomizerInterface.class),
POST_UPDATE_RECORD("postUpdateRecord", TableCustomizerInterface.class),
PRE_DELETE_RECORD("preDeleteRecord", TableCustomizerInterface.class),
POST_DELETE_RECORD("postDeleteRecord", TableCustomizerInterface.class);
private final String role;

View File

@ -35,9 +35,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostDeleteCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreDeleteCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
@ -250,7 +249,7 @@ public class DeleteAction
//////////////////////////////////////////////////////////////
// finally, run the post-delete customizer, if there is one //
//////////////////////////////////////////////////////////////
Optional<AbstractPostDeleteCustomizer> postDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPostDeleteCustomizer.class, table, TableCustomizers.POST_DELETE_RECORD.getRole());
Optional<TableCustomizerInterface> postDeleteCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_DELETE_RECORD.getRole());
if(postDeleteCustomizer.isPresent() && oldRecordList.isPresent())
{
////////////////////////////////////////////////////////////////////////////
@ -260,8 +259,7 @@ public class DeleteAction
try
{
postDeleteCustomizer.get().setDeleteInput(deleteInput);
List<QRecord> postCustomizerResult = postDeleteCustomizer.get().apply(recordsForCustomizer);
List<QRecord> postCustomizerResult = postDeleteCustomizer.get().postDelete(deleteInput, recordsForCustomizer);
///////////////////////////////////////////////////////
// check if any records got errors in the customizer //
@ -327,13 +325,11 @@ public class DeleteAction
///////////////////////////////////////////////////////////////////////////
// after all validations, run the pre-delete customizer, if there is one //
///////////////////////////////////////////////////////////////////////////
Optional<AbstractPreDeleteCustomizer> preDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPreDeleteCustomizer.class, table, TableCustomizers.PRE_DELETE_RECORD.getRole());
Optional<TableCustomizerInterface> preDeleteCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_DELETE_RECORD.getRole());
List<QRecord> customizerResult = oldRecordList.get();
if(preDeleteCustomizer.isPresent())
{
preDeleteCustomizer.get().setDeleteInput(deleteInput);
preDeleteCustomizer.get().setIsPreview(isPreview);
customizerResult = preDeleteCustomizer.get().apply(oldRecordList.get());
customizerResult = preDeleteCustomizer.get().preDelete(deleteInput, oldRecordList.get(), isPreview);
}
/////////////////////////////////////////////////////////////////////////

View File

@ -27,8 +27,8 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.interfaces.GetInterface;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.GetActionCacheHelper;
@ -58,7 +58,7 @@ import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
*******************************************************************************/
public class GetAction
{
private Optional<AbstractPostQueryCustomizer> postGetRecordCustomizer;
private Optional<TableCustomizerInterface> postGetRecordCustomizer;
private GetInput getInput;
private QPossibleValueTranslator qPossibleValueTranslator;
@ -88,7 +88,7 @@ public class GetAction
throw (new QException("Requested to Get a record from an unrecognized table: " + getInput.getTableName()));
}
postGetRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
postGetRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
this.getInput = getInput;
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
@ -126,10 +126,10 @@ public class GetAction
new GetActionCacheHelper().handleCaching(getInput, getOutput);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the record is found, perform post-actions on it //
// unless the defaultGetInteface was used - as it just does a query, and the query will do the post-actions. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// unless the defaultGetInterface was used - as it just does a query, and the query will do the post-actions. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(getOutput.getRecord() != null && !usingDefaultGetInterface)
{
getOutput.setRecord(postRecordActions(getOutput.getRecord()));
@ -220,12 +220,12 @@ public class GetAction
** Run the necessary actions on a record. This may include setting display values,
** translating possible values, and running post-record customizations.
*******************************************************************************/
public QRecord postRecordActions(QRecord record)
public QRecord postRecordActions(QRecord record) throws QException
{
QRecord returnRecord = record;
if(this.postGetRecordCustomizer.isPresent())
{
returnRecord = postGetRecordCustomizer.get().apply(List.of(record)).get(0);
returnRecord = postGetRecordCustomizer.get().postQuery(getInput, List.of(record)).get(0);
}
if(getInput.getShouldTranslatePossibleValues())

View File

@ -38,9 +38,9 @@ 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;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
@ -169,13 +169,12 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
//////////////////////////////////////////////////////////////
// finally, run the post-insert customizer, if there is one //
//////////////////////////////////////////////////////////////
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
Optional<TableCustomizerInterface> postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole());
if(postInsertCustomizer.isPresent())
{
try
{
postInsertCustomizer.get().setInsertInput(insertInput);
insertOutput.setRecords(postInsertCustomizer.get().apply(insertOutput.getRecords()));
insertOutput.setRecords(postInsertCustomizer.get().postInsert(insertInput, insertOutput.getRecords()));
}
catch(Exception e)
{
@ -233,31 +232,29 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
// load the pre-insert customizer and set it up, if there is one //
// then we'll run it based on its WhenToRun value //
///////////////////////////////////////////////////////////////////
Optional<AbstractPreInsertCustomizer> preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
if(preInsertCustomizer.isPresent())
{
preInsertCustomizer.get().setInsertInput(insertInput);
preInsertCustomizer.get().setIsPreview(isPreview);
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
}
setDefaultValuesInRecords(table, insertInput.getRecords());
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, insertInput.getInstance(), table, insertInput.getRecords(), null);
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
setErrorsIfUniqueKeyErrors(insertInput, table);
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_REQUIRED_FIELD_CHECKS);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_REQUIRED_FIELD_CHECKS);
if(insertInput.getInputSource().shouldValidateRequiredFields())
{
validateRequiredFields(insertInput);
}
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
}
@ -291,13 +288,13 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/*******************************************************************************
**
*******************************************************************************/
private void runPreInsertCustomizerIfItIsTime(InsertInput insertInput, Optional<AbstractPreInsertCustomizer> preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun whenToRun) throws QException
private void runPreInsertCustomizerIfItIsTime(InsertInput insertInput, boolean isPreview, Optional<TableCustomizerInterface> preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun whenToRun) throws QException
{
if(preInsertCustomizer.isPresent())
{
if(whenToRun.equals(preInsertCustomizer.get().getWhenToRun()))
if(whenToRun.equals(preInsertCustomizer.get().whenToRunPreInsert(insertInput, isPreview)))
{
insertInput.setRecords(preInsertCustomizer.get().apply(insertInput.getRecords()));
insertInput.setRecords(preInsertCustomizer.get().preInsert(insertInput, insertInput.getRecords(), isPreview));
}
}
}

View File

@ -31,8 +31,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
@ -73,7 +73,7 @@ public class QueryAction
{
private static final QLogger LOG = QLogger.getLogger(QueryAction.class);
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
private Optional<TableCustomizerInterface> postQueryRecordCustomizer;
private QueryInput queryInput;
private QueryInterface queryInterface;
@ -100,7 +100,7 @@ public class QueryAction
}
QBackendMetaData backend = queryInput.getBackend();
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
this.queryInput = queryInput;
if(queryInput.getRecordPipe() != null)
@ -264,7 +264,7 @@ public class QueryAction
{
if(this.postQueryRecordCustomizer.isPresent())
{
records = postQueryRecordCustomizer.get().apply(records);
records = postQueryRecordCustomizer.get().postQuery(queryInput, records);
}
if(queryInput.getShouldTranslatePossibleValues())

View File

@ -34,9 +34,8 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
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;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostUpdateCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.interfaces.UpdateInterface;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
@ -192,14 +191,12 @@ public class UpdateAction
//////////////////////////////////////////////////////////////
// finally, run the post-update customizer, if there is one //
//////////////////////////////////////////////////////////////
Optional<AbstractPostUpdateCustomizer> postUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPostUpdateCustomizer.class, table, TableCustomizers.POST_UPDATE_RECORD.getRole());
Optional<TableCustomizerInterface> postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole());
if(postUpdateCustomizer.isPresent())
{
try
{
postUpdateCustomizer.get().setUpdateInput(updateInput);
oldRecordList.ifPresent(l -> postUpdateCustomizer.get().setOldRecordList(l));
updateOutput.setRecords(postUpdateCustomizer.get().apply(updateOutput.getRecords()));
updateOutput.setRecords(postUpdateCustomizer.get().postUpdate(updateInput, updateOutput.getRecords(), oldRecordList));
}
catch(Exception e)
{
@ -273,13 +270,10 @@ public class UpdateAction
///////////////////////////////////////////////////////////////////////////
// after all validations, run the pre-update customizer, if there is one //
///////////////////////////////////////////////////////////////////////////
Optional<AbstractPreUpdateCustomizer> preUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPreUpdateCustomizer.class, table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
if(preUpdateCustomizer.isPresent())
{
preUpdateCustomizer.get().setUpdateInput(updateInput);
preUpdateCustomizer.get().setIsPreview(isPreview);
oldRecordList.ifPresent(l -> preUpdateCustomizer.get().setOldRecordList(l));
updateInput.setRecords(preUpdateCustomizer.get().apply(updateInput.getRecords()));
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
}
}

View File

@ -60,6 +60,11 @@ public interface QueryOrGetInputInterface
QBackendTransaction getTransaction();
/*******************************************************************************
**
*******************************************************************************/
String getTableName();
/*******************************************************************************
** Setter for transaction
*******************************************************************************/

View File

@ -34,6 +34,7 @@ import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer.WhenToRun;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
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.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
@ -137,15 +138,13 @@ public class BulkInsertTransformStep extends AbstractTransformStep
// we do this, in case it needs to, for example, adjust values that //
// are part of a unique key //
//////////////////////////////////////////////////////////////////////
Optional<AbstractPreInsertCustomizer> preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
if(preInsertCustomizer.isPresent())
{
preInsertCustomizer.get().setInsertInput(insertInput);
preInsertCustomizer.get().setIsPreview(true);
AbstractPreInsertCustomizer.WhenToRun whenToRun = preInsertCustomizer.get().getWhenToRun();
AbstractPreInsertCustomizer.WhenToRun whenToRun = preInsertCustomizer.get().whenToRunPreInsert(insertInput, true);
if(WhenToRun.BEFORE_ALL_VALIDATIONS.equals(whenToRun) || WhenToRun.BEFORE_UNIQUE_KEY_CHECKS.equals(whenToRun))
{
List<QRecord> recordsAfterCustomizer = preInsertCustomizer.get().apply(runBackendStepInput.getRecords());
List<QRecord> recordsAfterCustomizer = preInsertCustomizer.get().preInsert(insertInput, runBackendStepInput.getRecords(), true);
runBackendStepInput.setRecords(recordsAfterCustomizer);
///////////////////////////////////////////////////////////////////////////////////////

View File

@ -26,15 +26,21 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
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.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
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.actions.tables.query.QQueryFilter;
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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
@ -777,4 +783,142 @@ class InsertActionTest extends BaseTest
assertEquals(2, records.get(1).getValueInteger("noOfShoes"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testCustomizers() throws QException
{
String tableName = TestUtils.TABLE_NAME_PERSON_MEMORY;
{
QContext.getQInstance().getTable(tableName).withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(TestPreInsertCustomizer.class));
List<QRecord> records = new InsertAction().execute(new InsertInput(tableName)
.withRecord(new QRecord().withValue("firstName", "Darin").withValue("lastName", "Kelkhoff")))
.getRecords();
assertEquals(1701, records.get(0).getValueInteger("noOfShoes"));
///////////////////////////////////////////////////////////////////////////////////////////////////
// because this was a pre-action, the value should actually be inserted - so re-query and get it //
///////////////////////////////////////////////////////////////////////////////////////////////////
assertEquals(1701, new GetAction().executeForRecord(new GetInput(tableName).withPrimaryKey(1)).getValueInteger("noOfShoes"));
QContext.getQInstance().getTable(tableName).withCustomizers(new HashMap<>());
}
{
QContext.getQInstance().getTable(tableName).withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(TestPostInsertCustomizer.class));
List<QRecord> records = new InsertAction().execute(new InsertInput(tableName)
.withRecord(new QRecord().withValue("firstName", "Thom").withValue("lastName", "Chutterloin")))
.getRecords();
assertEquals(47, records.get(0).getValueInteger("homeStateId"));
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// because this was a post-action, the value should NOT actually be inserted - so re-query and confirm null //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
assertNull(new GetAction().executeForRecord(new GetInput(tableName).withPrimaryKey(2)).getValueInteger("homeStateId"));
QContext.getQInstance().getTable(tableName).withCustomizers(new HashMap<>());
}
{
QContext.getQInstance().getTable(tableName).withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(TestTableCustomizer.class));
QContext.getQInstance().getTable(tableName).withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(TestTableCustomizer.class));
List<QRecord> records = new InsertAction().execute(new InsertInput(tableName)
.withRecord(new QRecord().withValue("firstName", "Thom").withValue("lastName", "Chutterloin")))
.getRecords();
assertEquals(1701, records.get(0).getValueInteger("noOfShoes"));
assertEquals(47, records.get(0).getValueInteger("homeStateId"));
//////////////////////////////////////////////////////////////////////
// merger of the two above - one pre, one post, so one set, one not //
//////////////////////////////////////////////////////////////////////
QRecord fetchedRecord = new GetAction().executeForRecord(new GetInput(tableName).withPrimaryKey(2));
assertEquals(1701, records.get(0).getValueInteger("noOfShoes"));
assertNull(fetchedRecord.getValueInteger("homeStateId"));
QContext.getQInstance().getTable(tableName).withCustomizers(new HashMap<>());
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class TestPreInsertCustomizer extends AbstractPreInsertCustomizer
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> apply(List<QRecord> records) throws QException
{
List<QRecord> rs = new ArrayList<>();
records.forEach(r -> rs.add(new QRecord(r).withValue("noOfShoes", 1701)));
return rs;
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class TestPostInsertCustomizer extends AbstractPostInsertCustomizer
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> apply(List<QRecord> records) throws QException
{
/////////////////////////////////////////////////////////////////////////////////////////////
// grr, memory backend let's make sure to return a clone (so we don't edit what's stored!) //
/////////////////////////////////////////////////////////////////////////////////////////////
List<QRecord> rs = new ArrayList<>();
records.forEach(r -> rs.add(new QRecord(r).withValue("homeStateId", 47)));
return rs;
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class TestTableCustomizer implements TableCustomizerInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
List<QRecord> rs = new ArrayList<>();
records.forEach(r -> rs.add(new QRecord(r).withValue("noOfShoes", 1701)));
return rs;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
/////////////////////////////////////////////////////////////////////////////////////////////
// grr, memory backend let's make sure to return a clone (so we don't edit what's stored!) //
/////////////////////////////////////////////////////////////////////////////////////////////
List<QRecord> rs = new ArrayList<>();
records.forEach(r -> rs.add(new QRecord(r).withValue("homeStateId", 47)));
return rs;
}
}
}

View File

@ -43,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -368,13 +369,19 @@ public abstract class AbstractBaseFilesystemAction<FILE>
{
try
{
Optional<AbstractPostReadFileCustomizer> tableCustomizer = QCodeLoader.getTableCustomizer(AbstractPostReadFileCustomizer.class, table, FilesystemTableCustomizers.POST_READ_FILE.getRole());
if(tableCustomizer.isEmpty())
Optional<QCodeReference> codeReference = table.getCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole());
if(codeReference.isEmpty())
{
return (fileContents);
}
return tableCustomizer.get().customizeFileContents(fileContents);
AbstractPostReadFileCustomizer tableCustomizer = QCodeLoader.getAdHoc(AbstractPostReadFileCustomizer.class, codeReference.get());
if(tableCustomizer == null)
{
return (fileContents);
}
return tableCustomizer.customizeFileContents(fileContents);
}
catch(Exception e)
{