mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
QQQ-40 Initial working POC
This commit is contained in:
@ -0,0 +1,78 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** enum of possible values for a record's Automation Status.
|
||||
*******************************************************************************/
|
||||
public enum AutomationStatus implements PossibleValueEnum<Integer>
|
||||
{
|
||||
PENDING_INSERT_AUTOMATIONS(1, "Pending Insert Automations"),
|
||||
RUNNING_INSERT_AUTOMATIONS(2, "Running Insert Automations"),
|
||||
FAILED_INSERT_AUTOMATIONS(3, "Failed Insert Automations"),
|
||||
PENDING_UPDATE_AUTOMATIONS(4, "Pending Update Automations"),
|
||||
RUNNING_UPDATE_AUTOMATIONS(5, "Running Update Automations"),
|
||||
FAILED_UPDATE_AUTOMATIONS(6, "Failed Update Automations"),
|
||||
OK(7, "OK");
|
||||
|
||||
|
||||
private final Integer id;
|
||||
private final String label;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
AutomationStatus(int id, String label)
|
||||
{
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return (label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Integer getPossibleValueId()
|
||||
{
|
||||
return (getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getPossibleValueLabel()
|
||||
{
|
||||
return (getLabel());
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base class for custom-codes to run as an automation action
|
||||
*******************************************************************************/
|
||||
public abstract class RecordAutomationHandler
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract void execute(RecordAutomationInput recordAutomationInput) throws QException;
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for updating the automation status data for records
|
||||
*******************************************************************************/
|
||||
public class RecordAutomationStatusUpdater
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(RecordAutomationStatusUpdater.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** for a list of records from a table, set their automation status - based on
|
||||
** how the table is configured.
|
||||
*******************************************************************************/
|
||||
public static boolean setAutomationStatusInRecords(QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus)
|
||||
{
|
||||
if(table == null || table.getAutomationDetails() == null || CollectionUtils.nullSafeIsEmpty(records))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
if(automationStatus.equals(AutomationStatus.PENDING_INSERT_AUTOMATIONS) || automationStatus.equals(AutomationStatus.PENDING_UPDATE_AUTOMATIONS))
|
||||
{
|
||||
Exception e = new Exception();
|
||||
for(StackTraceElement stackTraceElement : e.getStackTrace())
|
||||
{
|
||||
String className = stackTraceElement.getClassName();
|
||||
if(className.contains("com.kingsrook.qqq.backend.core.actions.automation") && !className.equals(RecordAutomationStatusUpdater.class.getName()) && !className.endsWith("Test"))
|
||||
{
|
||||
LOG.info("Avoiding re-setting automation status to PENDING while running an automation");
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails.getStatusTracking() != null && AutomationStatusTrackingType.FIELD_IN_TABLE.equals(automationDetails.getStatusTracking().getType()))
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
record.setValue(automationDetails.getStatusTracking().getFieldName(), automationStatus.getId());
|
||||
// todo - another field - for the automation timestamp??
|
||||
}
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** for a list of records, update their automation status and actually Update the
|
||||
** backend as well.
|
||||
*******************************************************************************/
|
||||
public static void setAutomationStatusInRecordsAndUpdate(QInstance instance, QSession session, QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails != null && AutomationStatusTrackingType.FIELD_IN_TABLE.equals(automationDetails.getStatusTracking().getType()))
|
||||
{
|
||||
boolean didSetStatusField = setAutomationStatusInRecords(table, records, automationStatus);
|
||||
if(didSetStatusField)
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput(instance);
|
||||
updateInput.setSession(session);
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(records);
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo - verify if this is valid as other types are built
|
||||
throw (new NotImplementedException("Updating record automation status is not implemented for table [" + table + "], tracking type: "
|
||||
+ (automationDetails == null ? "null" : automationDetails.getStatusTracking().getType())));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation.polling;
|
||||
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton that runs a Polling Automation Provider. Call its 'start' method
|
||||
** to make it go. Likely you need to set a sessionSupplier before you start -
|
||||
** so that threads that do work will have a valid session.
|
||||
*******************************************************************************/
|
||||
public class PollingAutomationExecutor
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(PollingAutomationExecutor.class);
|
||||
|
||||
private static PollingAutomationExecutor pollingAutomationExecutor = null;
|
||||
|
||||
private Integer initialDelayMillis = 3000;
|
||||
private Integer delayMillis = 1000;
|
||||
|
||||
private Supplier<QSession> sessionSupplier;
|
||||
|
||||
private RunningState runningState = RunningState.STOPPED;
|
||||
private ScheduledExecutorService service;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private PollingAutomationExecutor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static PollingAutomationExecutor getInstance()
|
||||
{
|
||||
if(pollingAutomationExecutor == null)
|
||||
{
|
||||
pollingAutomationExecutor = new PollingAutomationExecutor();
|
||||
}
|
||||
return (pollingAutomationExecutor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** @return true iff the schedule was started
|
||||
*******************************************************************************/
|
||||
public boolean start(QInstance instance, String providerName)
|
||||
{
|
||||
if(!runningState.equals(RunningState.STOPPED))
|
||||
{
|
||||
LOG.info("Request to start from an invalid running state [" + runningState + "]. Must be STOPPED.");
|
||||
return (false);
|
||||
}
|
||||
|
||||
LOG.info("Starting PollingAutomationExecutor");
|
||||
service = Executors.newSingleThreadScheduledExecutor();
|
||||
service.scheduleWithFixedDelay(new PollingAutomationRunner(instance, providerName, sessionSupplier), initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);
|
||||
runningState = RunningState.RUNNING;
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Stop, and don't wait to check if it worked or anything
|
||||
*******************************************************************************/
|
||||
public void stopAsync()
|
||||
{
|
||||
Runnable stopper = this::stop;
|
||||
stopper.run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Issue a stop, and wait (a while) for it to succeed.
|
||||
**
|
||||
** @return true iff we see that the service fully stopped.
|
||||
*******************************************************************************/
|
||||
public boolean stop()
|
||||
{
|
||||
if(!runningState.equals(RunningState.RUNNING))
|
||||
{
|
||||
LOG.info("Request to stop from an invalid running state [" + runningState + "]. Must be RUNNING.");
|
||||
return (false);
|
||||
}
|
||||
|
||||
LOG.info("Stopping PollingAutomationExecutor");
|
||||
runningState = RunningState.STOPPING;
|
||||
service.shutdown();
|
||||
|
||||
try
|
||||
{
|
||||
if(service.awaitTermination(300, TimeUnit.SECONDS))
|
||||
{
|
||||
LOG.info("Successfully stopped PollingAutomationExecutor");
|
||||
runningState = RunningState.STOPPED;
|
||||
return (true);
|
||||
}
|
||||
|
||||
LOG.info("Timed out waiting for service to fully terminate. Will be left in STOPPING state.");
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
///////////////////////////////
|
||||
// what does this ever mean? //
|
||||
///////////////////////////////
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for initialDelayMillis
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getInitialDelayMillis()
|
||||
{
|
||||
return initialDelayMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for initialDelayMillis
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setInitialDelayMillis(Integer initialDelayMillis)
|
||||
{
|
||||
this.initialDelayMillis = initialDelayMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for delayMillis
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getDelayMillis()
|
||||
{
|
||||
return delayMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for delayMillis
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setDelayMillis(Integer delayMillis)
|
||||
{
|
||||
this.delayMillis = delayMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionSupplier
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSessionSupplier(Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for runningState
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunningState getRunningState()
|
||||
{
|
||||
return runningState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum RunningState
|
||||
{
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
STOPPING,
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation.polling;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Runnable for the Polling Automation Provider, that looks for records that
|
||||
** need automations, and executes them.
|
||||
*******************************************************************************/
|
||||
class PollingAutomationRunner implements Runnable
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(PollingAutomationRunner.class);
|
||||
|
||||
private QInstance instance;
|
||||
private String providerName;
|
||||
private Supplier<QSession> sessionSupplier;
|
||||
|
||||
private List<QTableMetaData> managedTables = new ArrayList<>();
|
||||
|
||||
private Map<String, List<TableAutomationAction>> tableInsertActions = new HashMap<>();
|
||||
private Map<String, List<TableAutomationAction>> tableUpdateActions = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public PollingAutomationRunner(QInstance instance, String providerName, Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.providerName = providerName;
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// todo - share logic like this among any automation implementation //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
for(QTableMetaData table : instance.getTables().values())
|
||||
{
|
||||
if(table.getAutomationDetails() != null && this.providerName.equals(table.getAutomationDetails().getProviderName()))
|
||||
{
|
||||
managedTables.add(table);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// organize the table's actions by type //
|
||||
// todo - in future, need user-defined actions here too (and refreshed!) //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
for(TableAutomationAction action : table.getAutomationDetails().getActions())
|
||||
{
|
||||
if(TriggerEvent.POST_INSERT.equals(action.getTriggerEvent()))
|
||||
{
|
||||
tableInsertActions.putIfAbsent(table.getName(), new ArrayList<>());
|
||||
tableInsertActions.get(table.getName()).add(action);
|
||||
}
|
||||
else if(TriggerEvent.POST_UPDATE.equals(action.getTriggerEvent()))
|
||||
{
|
||||
tableUpdateActions.putIfAbsent(table.getName(), new ArrayList<>());
|
||||
tableUpdateActions.get(table.getName()).add(action);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// sort actions by priority //
|
||||
//////////////////////////////
|
||||
if(tableInsertActions.containsKey(table.getName()))
|
||||
{
|
||||
tableInsertActions.get(table.getName()).sort(Comparator.comparing(TableAutomationAction::getPriority));
|
||||
}
|
||||
|
||||
if(tableUpdateActions.containsKey(table.getName()))
|
||||
{
|
||||
tableUpdateActions.get(table.getName()).sort(Comparator.comparing(TableAutomationAction::getPriority));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Thread.currentThread().setName(getClass().getSimpleName() + ">" + providerName);
|
||||
LOG.info("Running " + this.getClass().getSimpleName() + "[providerName=" + providerName + "]");
|
||||
|
||||
for(QTableMetaData table : managedTables)
|
||||
{
|
||||
try
|
||||
{
|
||||
processTable(table);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error processing automations on table: " + table, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void processTable(QTableMetaData table) throws QException
|
||||
{
|
||||
QSession session = sessionSupplier != null ? sessionSupplier.get() : new QSession();
|
||||
processTableInsertOrUpdate(table, session, true);
|
||||
processTableInsertOrUpdate(table, session, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void processTableInsertOrUpdate(QTableMetaData table, QSession session, boolean isInsert) throws QException
|
||||
{
|
||||
AutomationStatus automationStatus = isInsert ? AutomationStatus.PENDING_INSERT_AUTOMATIONS : AutomationStatus.PENDING_UPDATE_AUTOMATIONS;
|
||||
List<TableAutomationAction> actions = (isInsert ? tableInsertActions : tableUpdateActions).get(table.getName());
|
||||
if(CollectionUtils.nullSafeIsEmpty(actions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info(" Query for records " + automationStatus + " in " + table);
|
||||
|
||||
QueryInput queryInput = new QueryInput(instance);
|
||||
queryInput.setSession(session); // todo - where the heck can we get this from??
|
||||
queryInput.setTableName(table.getName());
|
||||
|
||||
for(TableAutomationAction action : actions)
|
||||
{
|
||||
QQueryFilter filter = action.getFilter();
|
||||
if(filter == null)
|
||||
{
|
||||
filter = new QQueryFilter();
|
||||
}
|
||||
|
||||
filter.addCriteria(new QFilterCriteria(table.getAutomationDetails().getStatusTracking().getFieldName(), QCriteriaOperator.IN, List.of(automationStatus.getId())));
|
||||
queryInput.setFilter(filter);
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
// todo - pipe this query!!
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
LOG.info(" Processing " + queryOutput.getRecords().size() + " records in " + table + " for action " + action);
|
||||
processRecords(table, actions, queryOutput.getRecords(), session, isInsert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void processRecords(QTableMetaData table, List<TableAutomationAction> actions, List<QRecord> records, QSession session, boolean isInsert) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
updateRecordAutomationStatus(table, session, records, isInsert ? AutomationStatus.RUNNING_INSERT_AUTOMATIONS : AutomationStatus.RUNNING_UPDATE_AUTOMATIONS);
|
||||
|
||||
for(TableAutomationAction action : actions)
|
||||
{
|
||||
////////////////////////////////////
|
||||
// todo - what, re-query them? :( //
|
||||
////////////////////////////////////
|
||||
if(StringUtils.hasContent(action.getProcessName()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - uh, how to make these records the input, where an extract step might be involved? //
|
||||
// should extract step ... see record list and just use it? i think maybe? //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new NotImplementedException("processes for automation not yet implemented"));
|
||||
}
|
||||
else if(action.getCodeReference() != null)
|
||||
{
|
||||
LOG.info(" Executing action: [" + action.getName() + "] as code reference: " + action.getCodeReference());
|
||||
RecordAutomationInput input = new RecordAutomationInput(instance);
|
||||
input.setSession(session);
|
||||
input.setTableName(table.getName());
|
||||
input.setRecordList(records);
|
||||
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
|
||||
recordAutomationHandler.execute(input);
|
||||
}
|
||||
}
|
||||
|
||||
updateRecordAutomationStatus(table, session, records, AutomationStatus.OK);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
updateRecordAutomationStatus(table, session, records, isInsert ? AutomationStatus.FAILED_INSERT_AUTOMATIONS : AutomationStatus.FAILED_UPDATE_AUTOMATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void updateRecordAutomationStatus(QTableMetaData table, QSession session, List<QRecord> records, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(instance, session, table, records, automationStatus);
|
||||
}
|
||||
|
||||
}
|
@ -24,12 +24,14 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@ -97,6 +99,42 @@ public class QCodeLoader
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QCodeReference codeReference = action.getCodeReference();
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
Class<?> codeClass = Class.forName(codeReference.getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler"));
|
||||
}
|
||||
return (recordAutomationHandler);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
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.insert.InsertOutput;
|
||||
@ -48,6 +50,9 @@ public class InsertAction
|
||||
*******************************************************************************/
|
||||
public InsertOutput execute(InsertInput insertInput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(insertInput);
|
||||
setAutomationStatusField(insertInput);
|
||||
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
// todo pre-customization - just get to modify the request?
|
||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
||||
@ -57,6 +62,16 @@ public class InsertAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If the table being inserted into uses an automation-status field, populate it now.
|
||||
*******************************************************************************/
|
||||
private void setAutomationStatusField(InsertInput insertInput)
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(insertInput.getTable(), insertInput.getRecords(), AutomationStatus.PENDING_INSERT_AUTOMATIONS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
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.actions.tables.update.UpdateOutput;
|
||||
@ -42,6 +44,7 @@ public class UpdateAction
|
||||
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(updateInput);
|
||||
setAutomationStatusField(updateInput);
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
||||
@ -50,4 +53,15 @@ public class UpdateAction
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If the table being updated uses an automation-status field, populate it now.
|
||||
*******************************************************************************/
|
||||
private void setAutomationStatusField(UpdateInput updateInput)
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(updateInput.getTable(), updateInput.getRecords(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
@ -39,10 +40,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -65,6 +70,8 @@ public class QInstanceValidator
|
||||
|
||||
private boolean printWarnings = false;
|
||||
|
||||
private List<String> errors = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -96,14 +103,14 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// do the validation checks - a good qInstance has all conditions TRUE! //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
List<String> errors = new ArrayList<>();
|
||||
try
|
||||
{
|
||||
validateBackends(qInstance, errors);
|
||||
validateTables(qInstance, errors);
|
||||
validateProcesses(qInstance, errors);
|
||||
validateApps(qInstance, errors);
|
||||
validatePossibleValueSources(qInstance, errors);
|
||||
validateBackends(qInstance);
|
||||
validateAutomationProviders(qInstance);
|
||||
validateTables(qInstance);
|
||||
validateProcesses(qInstance);
|
||||
validateApps(qInstance);
|
||||
validatePossibleValueSources(qInstance);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -123,13 +130,13 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateBackends(QInstance qInstance, List<String> errors)
|
||||
private void validateBackends(QInstance qInstance)
|
||||
{
|
||||
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()), "At least 1 backend must be defined."))
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(qInstance.getBackends()), "At least 1 backend must be defined."))
|
||||
{
|
||||
qInstance.getBackends().forEach((backendName, backend) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
|
||||
assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -139,42 +146,59 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTables(QInstance qInstance, List<String> errors)
|
||||
private void validateAutomationProviders(QInstance qInstance)
|
||||
{
|
||||
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()),
|
||||
if(qInstance.getAutomationProviders() != null)
|
||||
{
|
||||
qInstance.getAutomationProviders().forEach((name, automationProvider) ->
|
||||
{
|
||||
assertCondition(Objects.equals(name, automationProvider.getName()), "Inconsistent naming for automationProvider: " + name + "/" + automationProvider.getName() + ".");
|
||||
assertCondition(automationProvider.getType() != null, "Missing type for automationProvider: " + name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTables(QInstance qInstance)
|
||||
{
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(qInstance.getTables()),
|
||||
"At least 1 table must be defined."))
|
||||
{
|
||||
qInstance.getTables().forEach((tableName, table) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
|
||||
assertCondition(Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
|
||||
|
||||
validateAppChildHasValidParentAppName(qInstance, errors, table);
|
||||
validateAppChildHasValidParentAppName(qInstance, table);
|
||||
|
||||
////////////////////////////////////////
|
||||
// validate the backend for the table //
|
||||
////////////////////////////////////////
|
||||
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()),
|
||||
if(assertCondition(StringUtils.hasContent(table.getBackendName()),
|
||||
"Missing backend name for table " + tableName + "."))
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
|
||||
{
|
||||
assertCondition(errors, qInstance.getBackendForTable(tableName) != null, "Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
|
||||
assertCondition(qInstance.getBackendForTable(tableName) != null, "Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// validate fields in the table //
|
||||
//////////////////////////////////
|
||||
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(table.getFields()), "At least 1 field must be defined in table " + tableName + "."))
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(table.getFields()), "At least 1 field must be defined in table " + tableName + "."))
|
||||
{
|
||||
table.getFields().forEach((fieldName, field) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(fieldName, field.getName()),
|
||||
assertCondition(Objects.equals(fieldName, field.getName()),
|
||||
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
|
||||
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
assertCondition(errors, qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
|
||||
assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
|
||||
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
|
||||
}
|
||||
});
|
||||
@ -189,10 +213,10 @@ public class QInstanceValidator
|
||||
{
|
||||
for(QFieldSection section : table.getSections())
|
||||
{
|
||||
validateSection(errors, table, section, fieldNamesInSections);
|
||||
validateSection(table, section, fieldNamesInSections);
|
||||
if(section.getTier().equals(Tier.T1))
|
||||
{
|
||||
assertCondition(errors, tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
|
||||
assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
|
||||
tier1Section = section;
|
||||
}
|
||||
}
|
||||
@ -202,7 +226,7 @@ public class QInstanceValidator
|
||||
{
|
||||
for(String fieldName : table.getFields().keySet())
|
||||
{
|
||||
assertCondition(errors, fieldNamesInSections.contains(fieldName), "Table " + tableName + " field " + fieldName + " is not listed in any field sections.");
|
||||
assertCondition(fieldNamesInSections.contains(fieldName), "Table " + tableName + " field " + fieldName + " is not listed in any field sections.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +237,7 @@ public class QInstanceValidator
|
||||
{
|
||||
for(String recordLabelField : table.getRecordLabelFields())
|
||||
{
|
||||
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
||||
assertCondition(table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,9 +245,17 @@ public class QInstanceValidator
|
||||
{
|
||||
for(Map.Entry<String, QCodeReference> entry : table.getCustomizers().entrySet())
|
||||
{
|
||||
validateTableCustomizer(errors, tableName, entry.getKey(), entry.getValue());
|
||||
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// validate the table's automations //
|
||||
//////////////////////////////////////
|
||||
if(table.getAutomationDetails() != null)
|
||||
{
|
||||
validateTableAutomationDetails(qInstance, table);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -233,11 +265,112 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTableCustomizer(List<String> errors, String tableName, String customizerName, QCodeReference codeReference)
|
||||
private void validateTableAutomationDetails(QInstance qInstance, QTableMetaData table)
|
||||
{
|
||||
String tableName = table.getName();
|
||||
String prefix = "Table " + tableName + " automationDetails ";
|
||||
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
|
||||
//////////////////////////////////////
|
||||
// validate the automation provider //
|
||||
//////////////////////////////////////
|
||||
String providerName = automationDetails.getProviderName();
|
||||
if(assertCondition(StringUtils.hasContent(providerName), prefix + " is missing a providerName"))
|
||||
{
|
||||
assertCondition(qInstance.getAutomationProvider(providerName) != null, " has an unrecognized providerName: " + providerName);
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// validate the status tracking //
|
||||
//////////////////////////////////
|
||||
AutomationStatusTracking statusTracking = automationDetails.getStatusTracking();
|
||||
if(assertCondition(statusTracking != null, prefix + "do not have statusTracking defined."))
|
||||
{
|
||||
if(assertCondition(statusTracking.getType() != null, prefix + "statusTracking is missing a type"))
|
||||
{
|
||||
if(statusTracking.getType().equals(AutomationStatusTrackingType.FIELD_IN_TABLE))
|
||||
{
|
||||
assertCondition(StringUtils.hasContent(statusTracking.getFieldName()), prefix + "statusTracking of type fieldInTable is missing its fieldName");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// validate the actions //
|
||||
//////////////////////////
|
||||
Set<String> usedNames = new HashSet<>();
|
||||
if(automationDetails.getActions() != null)
|
||||
{
|
||||
automationDetails.getActions().forEach(action ->
|
||||
{
|
||||
assertCondition(StringUtils.hasContent(action.getName()), prefix + "has an action missing a name");
|
||||
assertCondition(!usedNames.contains(action.getName()), prefix + "has more than one action named " + action.getName());
|
||||
usedNames.add(action.getName());
|
||||
|
||||
String actionPrefix = prefix + "action " + action.getName() + " ";
|
||||
assertCondition(action.getTriggerEvent() != null, actionPrefix + "is missing a triggerEvent");
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// validate the code or process used by the action //
|
||||
/////////////////////////////////////////////////////
|
||||
int numberSet = 0;
|
||||
if(action.getCodeReference() != null)
|
||||
{
|
||||
numberSet++;
|
||||
if(preAssertionsForCodeReference(action.getCodeReference(), actionPrefix))
|
||||
{
|
||||
validateSimpleCodeReference(actionPrefix + "code reference: ", action.getCodeReference(), RecordAutomationHandler.class);
|
||||
}
|
||||
}
|
||||
|
||||
if(action.getProcessName() != null)
|
||||
{
|
||||
numberSet++;
|
||||
QProcessMetaData process = qInstance.getProcess(action.getProcessName());
|
||||
if(assertCondition(process != null, actionPrefix + "has an unrecognized processName: " + action.getProcessName()))
|
||||
{
|
||||
if(process.getTableName() != null)
|
||||
{
|
||||
assertCondition(tableName.equals(process.getTableName()), actionPrefix + " references a process from a different table");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(numberSet != 0, actionPrefix + "is missing both a codeReference and a processName");
|
||||
assertCondition(!(numberSet > 1), actionPrefix + "has both a codeReference and a processName (which is not allowed)");
|
||||
|
||||
///////////////////////////////////////////
|
||||
// validate the filter (if there is one) //
|
||||
///////////////////////////////////////////
|
||||
if(action.getFilter() != null && action.getFilter().getCriteria() != null)
|
||||
{
|
||||
action.getFilter().getCriteria().forEach((criteria) ->
|
||||
{
|
||||
if(assertCondition(StringUtils.hasContent(criteria.getFieldName()), actionPrefix + "has a filter criteria without a field name"))
|
||||
{
|
||||
assertNoException(() -> table.getField(criteria.getFieldName()), actionPrefix + "has a filter criteria referencing an unrecognized field: " + criteria.getFieldName());
|
||||
}
|
||||
|
||||
assertCondition(criteria.getOperator() != null, actionPrefix + "has a filter criteria without an operator");
|
||||
|
||||
// todo - validate cardinality of values...
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTableCustomizer(String tableName, String customizerName, QCodeReference codeReference)
|
||||
{
|
||||
String prefix = "Table " + tableName + ", customizer " + customizerName + ": ";
|
||||
|
||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -245,18 +378,18 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// make sure (at this time) that it's a java type, then do some java checks //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||
if(assertCondition(codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// make sure the class can be loaded //
|
||||
///////////////////////////////////////
|
||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||
Class<?> customizerClass = getClassForCodeReference(codeReference, prefix);
|
||||
if(customizerClass != null)
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass);
|
||||
|
||||
TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
|
||||
if(tableCustomizer == null)
|
||||
@ -273,7 +406,7 @@ public class QInstanceValidator
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(customizerInstance != null && tableCustomizer.getTableCustomizer().getExpectedType() != null)
|
||||
{
|
||||
Object castedObject = getCastedObject(errors, prefix, tableCustomizer.getTableCustomizer().getExpectedType(), customizerInstance);
|
||||
Object castedObject = getCastedObject(prefix, tableCustomizer.getTableCustomizer().getExpectedType(), customizerInstance);
|
||||
|
||||
Consumer<Object> validationFunction = tableCustomizer.getTableCustomizer().getValidationFunction();
|
||||
if(castedObject != null && validationFunction != null)
|
||||
@ -305,7 +438,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private <T> T getCastedObject(List<String> errors, String prefix, Class<T> expectedType, Object customizerInstance)
|
||||
private <T> T getCastedObject(String prefix, Class<T> expectedType, Object customizerInstance)
|
||||
{
|
||||
T castedObject = null;
|
||||
try
|
||||
@ -314,7 +447,7 @@ public class QInstanceValidator
|
||||
}
|
||||
catch(ClassCastException e)
|
||||
{
|
||||
errors.add(prefix + "CodeReference could not be casted to the expected type: " + expectedType);
|
||||
errors.add(prefix + "CodeReference is not of the expected type: " + expectedType);
|
||||
}
|
||||
return castedObject;
|
||||
}
|
||||
@ -324,7 +457,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Object getInstanceOfCodeReference(List<String> errors, String prefix, Class<?> customizerClass)
|
||||
private Object getInstanceOfCodeReference(String prefix, Class<?> customizerClass)
|
||||
{
|
||||
Object customizerInstance = null;
|
||||
try
|
||||
@ -343,18 +476,18 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateSection(List<String> errors, QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections)
|
||||
private void validateSection(QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections)
|
||||
{
|
||||
assertCondition(errors, StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + ".");
|
||||
assertCondition(errors, StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + ".");
|
||||
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(section.getFieldNames()), "Table " + table.getName() + " section " + section.getName() + " does not have any fields."))
|
||||
assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + ".");
|
||||
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + ".");
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(section.getFieldNames()), "Table " + table.getName() + " section " + section.getName() + " does not have any fields."))
|
||||
{
|
||||
if(table.getFields() != null)
|
||||
{
|
||||
for(String fieldName : section.getFieldNames())
|
||||
{
|
||||
assertCondition(errors, table.getFields().containsKey(fieldName), "Table " + table.getName() + " section " + section.getName() + " specifies fieldName " + fieldName + ", which is not a field on this table.");
|
||||
assertCondition(errors, !fieldNamesInSections.contains(fieldName), "Table " + table.getName() + " has field " + fieldName + " listed more than once in its field sections.");
|
||||
assertCondition(table.getFields().containsKey(fieldName), "Table " + table.getName() + " section " + section.getName() + " specifies fieldName " + fieldName + ", which is not a field on this table.");
|
||||
assertCondition(!fieldNamesInSections.contains(fieldName), "Table " + table.getName() + " has field " + fieldName + " listed more than once in its field sections.");
|
||||
|
||||
fieldNamesInSections.add(fieldName);
|
||||
}
|
||||
@ -367,33 +500,33 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateProcesses(QInstance qInstance, List<String> errors)
|
||||
private void validateProcesses(QInstance qInstance)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getProcesses()))
|
||||
{
|
||||
qInstance.getProcesses().forEach((processName, process) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + ".");
|
||||
assertCondition(Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + ".");
|
||||
|
||||
validateAppChildHasValidParentAppName(qInstance, errors, process);
|
||||
validateAppChildHasValidParentAppName(qInstance, process);
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// validate the table name for the process //
|
||||
/////////////////////////////////////////////
|
||||
if(process.getTableName() != null)
|
||||
{
|
||||
assertCondition(errors, qInstance.getTable(process.getTableName()) != null, "Unrecognized table " + process.getTableName() + " for process " + processName + ".");
|
||||
assertCondition(qInstance.getTable(process.getTableName()) != null, "Unrecognized table " + process.getTableName() + " for process " + processName + ".");
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// validate steps in the process //
|
||||
///////////////////////////////////
|
||||
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(process.getStepList()), "At least 1 step must be defined in process " + processName + "."))
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(process.getStepList()), "At least 1 step must be defined in process " + processName + "."))
|
||||
{
|
||||
int index = 0;
|
||||
for(QStepMetaData step : process.getStepList())
|
||||
{
|
||||
assertCondition(errors, StringUtils.hasContent(step.getName()), "Missing name for a step at index " + index + " in process " + processName);
|
||||
assertCondition(StringUtils.hasContent(step.getName()), "Missing name for a step at index " + index + " in process " + processName);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@ -406,26 +539,26 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateApps(QInstance qInstance, List<String> errors)
|
||||
private void validateApps(QInstance qInstance)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getApps()))
|
||||
{
|
||||
qInstance.getApps().forEach((appName, app) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(appName, app.getName()), "Inconsistent naming for app: " + appName + "/" + app.getName() + ".");
|
||||
assertCondition(Objects.equals(appName, app.getName()), "Inconsistent naming for app: " + appName + "/" + app.getName() + ".");
|
||||
|
||||
validateAppChildHasValidParentAppName(qInstance, errors, app);
|
||||
validateAppChildHasValidParentAppName(qInstance, app);
|
||||
|
||||
Set<String> appsVisited = new HashSet<>();
|
||||
visitAppCheckingForCycles(app, appsVisited, errors);
|
||||
visitAppCheckingForCycles(app, appsVisited);
|
||||
|
||||
if(app.getChildren() != null)
|
||||
{
|
||||
Set<String> childNames = new HashSet<>();
|
||||
for(QAppChildMetaData child : app.getChildren())
|
||||
{
|
||||
assertCondition(errors, Objects.equals(appName, child.getParentAppName()), "Child " + child.getName() + " of app " + appName + " does not have its parent app properly set.");
|
||||
assertCondition(errors, !childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName());
|
||||
assertCondition(Objects.equals(appName, child.getParentAppName()), "Child " + child.getName() + " of app " + appName + " does not have its parent app properly set.");
|
||||
assertCondition(!childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName());
|
||||
childNames.add(child.getName());
|
||||
}
|
||||
}
|
||||
@ -438,14 +571,14 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validatePossibleValueSources(QInstance qInstance, List<String> errors)
|
||||
private void validatePossibleValueSources(QInstance qInstance)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getPossibleValueSources()))
|
||||
{
|
||||
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
||||
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
||||
assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
||||
if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert about fields that should and should not be set, based on possible value source type //
|
||||
@ -455,30 +588,30 @@ public class QInstanceValidator
|
||||
{
|
||||
case ENUM ->
|
||||
{
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
assertCondition(errors, CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
|
||||
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
|
||||
}
|
||||
case TABLE ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
if(assertCondition(errors, StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
|
||||
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
|
||||
{
|
||||
assertCondition(errors, qInstance.getTable(possibleValueSource.getTableName()) != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
|
||||
assertCondition(qInstance.getTable(possibleValueSource.getTableName()) != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
|
||||
}
|
||||
}
|
||||
case CUSTOM ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
|
||||
if(assertCondition(errors, possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
|
||||
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
|
||||
{
|
||||
assertCondition(errors, QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||
validateCustomPossibleValueSourceCode(errors, pvsName, possibleValueSource.getCustomCodeReference());
|
||||
assertCondition(QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||
validateSimpleCodeReference("PossibleValueSource " + pvsName + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
|
||||
}
|
||||
}
|
||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
||||
@ -493,11 +626,9 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateCustomPossibleValueSourceCode(List<String> errors, String pvsName, QCodeReference codeReference)
|
||||
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?> expectedClass)
|
||||
{
|
||||
String prefix = "PossibleValueSource " + pvsName + " custom code reference: ";
|
||||
|
||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -505,25 +636,25 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// make sure (at this time) that it's a java type, then do some java checks //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||
if(assertCondition(codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA code references are supported at this time."))
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// make sure the class can be loaded //
|
||||
///////////////////////////////////////
|
||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||
Class<?> customizerClass = getClassForCodeReference(codeReference, prefix);
|
||||
if(customizerClass != null)
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// make sure the customizer instance can be cast to the expected type //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(customizerInstance != null)
|
||||
{
|
||||
getCastedObject(errors, prefix, QCustomPossibleValueProvider.class, customizerInstance);
|
||||
getCastedObject(prefix, expectedClass, customizerInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -534,7 +665,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Class<?> getClassForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||
private Class<?> getClassForCodeReference(QCodeReference codeReference, String prefix)
|
||||
{
|
||||
Class<?> customizerClass = null;
|
||||
try
|
||||
@ -553,15 +684,15 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean preAssertionsForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||
private boolean preAssertionsForCodeReference(QCodeReference codeReference, String prefix)
|
||||
{
|
||||
boolean okay = true;
|
||||
if(!assertCondition(errors, StringUtils.hasContent(codeReference.getName()), prefix + " is missing a code reference name"))
|
||||
if(!assertCondition(StringUtils.hasContent(codeReference.getName()), prefix + " is missing a code reference name"))
|
||||
{
|
||||
okay = false;
|
||||
}
|
||||
|
||||
if(!assertCondition(errors, codeReference.getCodeType() != null, prefix + " is missing a code type"))
|
||||
if(!assertCondition(codeReference.getCodeType() != null, prefix + " is missing a code type"))
|
||||
{
|
||||
okay = false;
|
||||
}
|
||||
@ -575,9 +706,9 @@ public class QInstanceValidator
|
||||
** Check if an app's child list can recursively be traversed without finding a
|
||||
** duplicate, which would indicate a cycle (e.g., an error)
|
||||
*******************************************************************************/
|
||||
private void visitAppCheckingForCycles(QAppMetaData app, Set<String> appsVisited, List<String> errors)
|
||||
private void visitAppCheckingForCycles(QAppMetaData app, Set<String> appsVisited)
|
||||
{
|
||||
if(assertCondition(errors, !appsVisited.contains(app.getName()), "Circular app reference detected, involving " + app.getName()))
|
||||
if(assertCondition(!appsVisited.contains(app.getName()), "Circular app reference detected, involving " + app.getName()))
|
||||
{
|
||||
appsVisited.add(app.getName());
|
||||
if(app.getChildren() != null)
|
||||
@ -586,7 +717,7 @@ public class QInstanceValidator
|
||||
{
|
||||
if(child instanceof QAppMetaData childApp)
|
||||
{
|
||||
visitAppCheckingForCycles(childApp, appsVisited, errors);
|
||||
visitAppCheckingForCycles(childApp, appsVisited);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,11 +729,11 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateAppChildHasValidParentAppName(QInstance qInstance, List<String> errors, QAppChildMetaData appChild)
|
||||
private void validateAppChildHasValidParentAppName(QInstance qInstance, QAppChildMetaData appChild)
|
||||
{
|
||||
if(appChild.getParentAppName() != null)
|
||||
{
|
||||
assertCondition(errors, qInstance.getApp(appChild.getParentAppName()) != null, "Unrecognized parent app " + appChild.getParentAppName() + " for " + appChild.getName() + ".");
|
||||
assertCondition(qInstance.getApp(appChild.getParentAppName()) != null, "Unrecognized parent app " + appChild.getParentAppName() + " for " + appChild.getName() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,7 +744,7 @@ public class QInstanceValidator
|
||||
** But if it's false, add the provided message to the list of errors (and return false,
|
||||
** e.g., in case you need to stop evaluating rules to avoid exceptions).
|
||||
*******************************************************************************/
|
||||
private boolean assertCondition(List<String> errors, boolean condition, String message)
|
||||
private boolean assertCondition(boolean condition, String message)
|
||||
{
|
||||
if(!condition)
|
||||
{
|
||||
@ -625,6 +756,41 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For the given lambda, if it doesn't throw an exception, then we're all good (and return true).
|
||||
** But if it throws, add the provided message to the list of errors (and return false,
|
||||
** e.g., in case you need to stop evaluating rules to avoid exceptions).
|
||||
*******************************************************************************/
|
||||
private boolean assertNoException(UnsafeLambda unsafeLambda, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
unsafeLambda.run();
|
||||
return (true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
errors.add(message);
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
interface UnsafeLambda
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,138 @@
|
||||
package com.kingsrook.qqq.backend.core.model.automation;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
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.tables.automation.TableAutomationAction;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input data for the RecordAutomationHandler interface.
|
||||
*******************************************************************************/
|
||||
public class RecordAutomationInput extends AbstractTableActionInput
|
||||
{
|
||||
private TableAutomationAction action;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// todo - why both? pick one? or don't? //
|
||||
// maybe - if recordList is null and primaryKeyList isn't, then do the record query in here?
|
||||
////////////////////////////////////////////
|
||||
private List<QRecord> recordList;
|
||||
private List<Serializable> primaryKeyList;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordAutomationInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAction(TableAutomationAction action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordAutomationInput withAction(TableAutomationAction action)
|
||||
{
|
||||
this.action = action;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecordList()
|
||||
{
|
||||
return recordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordAutomationInput withRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for primaryKeyList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<Serializable> getPrimaryKeyList()
|
||||
{
|
||||
return primaryKeyList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for primaryKeyList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPrimaryKeyList(List<Serializable> primaryKeyList)
|
||||
{
|
||||
this.primaryKeyList = primaryKeyList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for primaryKeyList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordAutomationInput withPrimaryKeyList(List<Serializable> primaryKeyList)
|
||||
{
|
||||
this.primaryKeyList = primaryKeyList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
** Enum to define the possible authentication types
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public enum QAuthenticationType
|
||||
{
|
||||
AUTH_0("auth0"),
|
||||
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
@ -50,6 +51,7 @@ public class QInstance
|
||||
private Map<String, QBackendMetaData> backends = new HashMap<>();
|
||||
|
||||
private QAuthenticationMetaData authentication = null;
|
||||
private Map<String, QAutomationProviderMetaData> automationProviders = new HashMap<>();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||
@ -103,17 +105,6 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hasBeenValidated
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setHasBeenValidated(QInstanceValidationKey key)
|
||||
{
|
||||
this.hasBeenValidated = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -148,6 +139,28 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backends
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QBackendMetaData> getBackends()
|
||||
{
|
||||
return backends;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backends
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBackends(Map<String, QBackendMetaData> backends)
|
||||
{
|
||||
this.backends = backends;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -187,6 +200,28 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tables
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QTableMetaData> getTables()
|
||||
{
|
||||
return tables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tables
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTables(Map<String, QTableMetaData> tables)
|
||||
{
|
||||
this.tables = tables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -221,6 +256,28 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QPossibleValueSource> getPossibleValueSources()
|
||||
{
|
||||
return possibleValueSources;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource> possibleValueSources)
|
||||
{
|
||||
this.possibleValueSources = possibleValueSources;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -271,6 +328,28 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QProcessMetaData> getProcesses()
|
||||
{
|
||||
return processes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setProcesses(Map<String, QProcessMetaData> processes)
|
||||
{
|
||||
this.processes = processes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -305,94 +384,6 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backends
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QBackendMetaData> getBackends()
|
||||
{
|
||||
return backends;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backends
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBackends(Map<String, QBackendMetaData> backends)
|
||||
{
|
||||
this.backends = backends;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tables
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QTableMetaData> getTables()
|
||||
{
|
||||
return tables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tables
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTables(Map<String, QTableMetaData> tables)
|
||||
{
|
||||
this.tables = tables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QPossibleValueSource> getPossibleValueSources()
|
||||
{
|
||||
return possibleValueSources;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource> possibleValueSources)
|
||||
{
|
||||
this.possibleValueSources = possibleValueSources;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QProcessMetaData> getProcesses()
|
||||
{
|
||||
return processes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setProcesses(Map<String, QProcessMetaData> processes)
|
||||
{
|
||||
this.processes = processes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for apps
|
||||
**
|
||||
@ -415,6 +406,62 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addAutomationProvider(QAutomationProviderMetaData automationProvider)
|
||||
{
|
||||
this.addAutomationProvider(automationProvider.getName(), automationProvider);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addAutomationProvider(String name, QAutomationProviderMetaData automationProvider)
|
||||
{
|
||||
if(this.automationProviders.containsKey(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a second automationProvider with name: " + name));
|
||||
}
|
||||
this.automationProviders.put(name, automationProvider);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAutomationProviderMetaData getAutomationProvider(String name)
|
||||
{
|
||||
return (this.automationProviders.get(name));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for automationProviders
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QAutomationProviderMetaData> getAutomationProviders()
|
||||
{
|
||||
return automationProviders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for automationProviders
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAutomationProviders(Map<String, QAutomationProviderMetaData> automationProviders)
|
||||
{
|
||||
this.automationProviders = automationProviders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hasBeenValidated
|
||||
**
|
||||
@ -426,6 +473,17 @@ public class QInstance
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hasBeenValidated
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setHasBeenValidated(QInstanceValidationKey key)
|
||||
{
|
||||
this.hasBeenValidated = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for authentication
|
||||
**
|
||||
|
@ -0,0 +1,43 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Metadata specifically for the polling automation provider.
|
||||
*******************************************************************************/
|
||||
public class PollingAutomationProviderMetaData extends QAutomationProviderMetaData
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public PollingAutomationProviderMetaData()
|
||||
{
|
||||
super();
|
||||
setType(QAutomationProviderType.POLLING);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public PollingAutomationProviderMetaData withName(String name)
|
||||
{
|
||||
setName(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public PollingAutomationProviderMetaData withType(QAutomationProviderType type)
|
||||
{
|
||||
setType(type);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data definition of a qqq service to drive record automations.
|
||||
*******************************************************************************/
|
||||
public class QAutomationProviderMetaData
|
||||
{
|
||||
private String name;
|
||||
private QAutomationProviderType type;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAutomationProviderMetaData withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAutomationProviderType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setType(QAutomationProviderType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAutomationProviderMetaData withType(QAutomationProviderType type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.model.metadata.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Enum to define the possible automation provider types
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum QAutomationProviderType
|
||||
{
|
||||
POLLING("polling"),
|
||||
SYNCHRONOUS("synchronous"),
|
||||
ASYNCHRONOUS("asynchronous"),
|
||||
MQ("mq"),
|
||||
AMAZON_SQS("sqs");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** enum constructor
|
||||
*******************************************************************************/
|
||||
QAutomationProviderType(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
|
||||
@ -59,6 +60,17 @@ public class QCodeReference
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "QCodeReference{name='" + name + "'}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that just takes a java class, and infers the other fields.
|
||||
*******************************************************************************/
|
||||
@ -75,6 +87,10 @@ public class QCodeReference
|
||||
{
|
||||
this.codeUsage = QCodeUsage.POSSIBLE_VALUE_PROVIDER;
|
||||
}
|
||||
else if(RecordAutomationHandler.class.isAssignableFrom(javaClass))
|
||||
{
|
||||
this.codeUsage = QCodeUsage.RECORD_AUTOMATION_HANDLER;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
|
||||
|
@ -30,5 +30,6 @@ public enum QCodeUsage
|
||||
{
|
||||
BACKEND_STEP, // a backend-step in a process
|
||||
CUSTOMIZER, // a function to customize part of a QQQ table's behavior
|
||||
POSSIBLE_VALUE_PROVIDER // code that drives a custom possibleValueSource
|
||||
POSSIBLE_VALUE_PROVIDER, // code that drives a custom possibleValueSource
|
||||
RECORD_AUTOMATION_HANDLER // code that executes record automations
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ 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.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -64,6 +65,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
private Map<String, QFieldMetaData> fields;
|
||||
|
||||
private QTableBackendDetails backendDetails;
|
||||
private QTableAutomationDetails automationDetails;
|
||||
|
||||
private Map<String, QCodeReference> customizers;
|
||||
|
||||
@ -410,6 +412,40 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for automationDetails
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails getAutomationDetails()
|
||||
{
|
||||
return automationDetails;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for automationDetails
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAutomationDetails(QTableAutomationDetails automationDetails)
|
||||
{
|
||||
this.automationDetails = automationDetails;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for automationDetails
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withAutomationDetails(QTableAutomationDetails automationDetails)
|
||||
{
|
||||
this.automationDetails = automationDetails;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,84 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Table-automation meta-data to define how this table's per-record automation
|
||||
** status is tracked.
|
||||
*******************************************************************************/
|
||||
public class AutomationStatusTracking
|
||||
{
|
||||
private AutomationStatusTrackingType type;
|
||||
|
||||
private String fieldName; // used when type is FIELD_IN_TABLE
|
||||
|
||||
// todo - fields for additional types (e.g., 1-1 table, shared-table)
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AutomationStatusTrackingType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setType(AutomationStatusTrackingType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AutomationStatusTracking withType(AutomationStatusTrackingType type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFieldName()
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AutomationStatusTracking withFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Enum of possible types of per-table, per-record automation-status tracking.
|
||||
*******************************************************************************/
|
||||
public enum AutomationStatusTrackingType
|
||||
{
|
||||
FIELD_IN_TABLE
|
||||
// todo - additional types (e.g., 1-1 table, shared-table)
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Details about how this table's record automations are set up.
|
||||
*******************************************************************************/
|
||||
public class QTableAutomationDetails
|
||||
{
|
||||
private AutomationStatusTracking statusTracking;
|
||||
private String providerName;
|
||||
private List<TableAutomationAction> actions;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for statusTracking
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AutomationStatusTracking getStatusTracking()
|
||||
{
|
||||
return statusTracking;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for statusTracking
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setStatusTracking(AutomationStatusTracking statusTracking)
|
||||
{
|
||||
this.statusTracking = statusTracking;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for statusTracking
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withStatusTracking(AutomationStatusTracking statusTracking)
|
||||
{
|
||||
this.statusTracking = statusTracking;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for providerName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getProviderName()
|
||||
{
|
||||
return providerName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for providerName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setProviderName(String providerName)
|
||||
{
|
||||
this.providerName = providerName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for providerName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withProviderName(String providerName)
|
||||
{
|
||||
this.providerName = providerName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for actions
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<TableAutomationAction> getActions()
|
||||
{
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for actions
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setActions(List<TableAutomationAction> actions)
|
||||
{
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for actions
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withActions(List<TableAutomationAction> actions)
|
||||
{
|
||||
this.actions = actions;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluently add an action to this table's automations.
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withAction(TableAutomationAction action)
|
||||
{
|
||||
if(this.actions == null)
|
||||
{
|
||||
this.actions = new ArrayList<>();
|
||||
}
|
||||
this.actions.add(action);
|
||||
return (this);
|
||||
}
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Definition of a specific action to run against a table
|
||||
*******************************************************************************/
|
||||
public class TableAutomationAction
|
||||
{
|
||||
private String name;
|
||||
private TriggerEvent triggerEvent;
|
||||
private Integer priority = 500;
|
||||
private QQueryFilter filter;
|
||||
|
||||
////////////////////////////////
|
||||
// mutually-exclusive options //
|
||||
////////////////////////////////
|
||||
private QCodeReference codeReference;
|
||||
private String processName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "TableAutomationAction{name='" + name + "'}";}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for triggerEvent
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TriggerEvent getTriggerEvent()
|
||||
{
|
||||
return triggerEvent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for triggerEvent
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTriggerEvent(TriggerEvent triggerEvent)
|
||||
{
|
||||
this.triggerEvent = triggerEvent;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for triggerEvent
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withTriggerEvent(TriggerEvent triggerEvent)
|
||||
{
|
||||
this.triggerEvent = triggerEvent;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for priority
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for priority
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPriority(Integer priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for priority
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withPriority(Integer priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getFilter()
|
||||
{
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for codeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QCodeReference getCodeReference()
|
||||
{
|
||||
return codeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for codeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCodeReference(QCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for codeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withCodeReference(QCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getProcessName()
|
||||
{
|
||||
return processName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableAutomationAction withProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Possible events that can trigger a record automation.
|
||||
*******************************************************************************/
|
||||
public enum TriggerEvent
|
||||
{
|
||||
POST_INSERT,
|
||||
POST_UPDATE,
|
||||
PRE_DELETE
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation.polling;
|
||||
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
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.query.QueryInput;
|
||||
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.automation.RecordAutomationInput;
|
||||
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.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
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;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for PollingAutomationExecutor
|
||||
*******************************************************************************/
|
||||
class PollingAutomationExecutorTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void beforeAndAfterEach()
|
||||
{
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInsert() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// insert 2 people - one who should be updated by the check-age automation //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(new QSession());
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "John").withValue("birthDate", LocalDate.of(1970, Month.JANUARY, 1)),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Jim").withValue("birthDate", LocalDate.now().minusDays(30))
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// have the polling executor run "for awhile" //
|
||||
////////////////////////////////////////////////
|
||||
runPollingAutomationExecutorForAwhile(qInstance);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// query for the records - assert their status //
|
||||
/////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(new QSession());
|
||||
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
assertEquals(2, queryOutput.getRecords().size());
|
||||
|
||||
Optional<QRecord> optionalPerson1 = queryOutput.getRecords().stream().filter(r -> r.getValueInteger("id") == 1).findFirst();
|
||||
assertThat(optionalPerson1).isPresent();
|
||||
QRecord person1 = optionalPerson1.get();
|
||||
assertThat(person1.getValueString("firstName")).isEqualTo("John");
|
||||
assertThat(person1.getValueInteger(TestUtils.standardQqqAutomationStatusField().getName())).isEqualTo(AutomationStatus.OK.getId());
|
||||
|
||||
Optional<QRecord> optionalPerson2 = queryOutput.getRecords().stream().filter(r -> r.getValueInteger("id") == 2).findFirst();
|
||||
assertThat(optionalPerson2).isPresent();
|
||||
QRecord person2 = optionalPerson2.get();
|
||||
assertThat(person2.getValueString("firstName")).isEqualTo("Jim" + TestUtils.CheckAge.SUFFIX_FOR_MINORS);
|
||||
assertThat(person2.getValueInteger(TestUtils.standardQqqAutomationStatusField().getName())).isEqualTo(AutomationStatus.OK.getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUpdate() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// insert 2 people - one who should be logged by logger-on-update automation //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(new QSession());
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Tim"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Darin")
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// have the polling executor run "for awhile" //
|
||||
////////////////////////////////////////////////
|
||||
runPollingAutomationExecutorForAwhile(qInstance);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// assert that the update-automation didn't run //
|
||||
//////////////////////////////////////////////////
|
||||
assertThat(TestUtils.LogPersonUpdate.updatedIds).isNullOrEmpty();
|
||||
|
||||
UpdateInput updateInput = new UpdateInput(qInstance);
|
||||
updateInput.setSession(new QSession());
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("lastName", "now with a LastName"),
|
||||
new QRecord().withValue("id", 2).withValue("lastName", "now with a LastName")
|
||||
));
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// have the polling executor run "for awhile" //
|
||||
////////////////////////////////////////////////
|
||||
runPollingAutomationExecutorForAwhile(qInstance);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// assert that the update-automation DID run now //
|
||||
///////////////////////////////////////////////////
|
||||
assertThat(TestUtils.LogPersonUpdate.updatedIds).contains(2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSessionSupplier() throws QException
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// make the person-memory table's insert-action run a class in here //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||
.getAutomationDetails().getActions().get(0)
|
||||
.setCodeReference(new QCodeReference(CaptureSessionIdAutomationHandler.class));
|
||||
|
||||
/////////////////////
|
||||
// insert a person //
|
||||
/////////////////////
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(new QSession());
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Tim")
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
QSession session = new QSession();
|
||||
session.setIdReference(uuid);
|
||||
PollingAutomationExecutor.getInstance().setSessionSupplier(() -> session);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// have the polling executor run "for awhile" //
|
||||
////////////////////////////////////////////////
|
||||
runPollingAutomationExecutorForAwhile(qInstance);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert that the uuid we put in our session was present in the CaptureSessionIdAutomationHandler //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertThat(CaptureSessionIdAutomationHandler.sessionId).isEqualTo(uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CaptureSessionIdAutomationHandler extends RecordAutomationHandler
|
||||
{
|
||||
static String sessionId;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
sessionId = recordAutomationInput.getSession().getIdReference();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runPollingAutomationExecutorForAwhile(QInstance qInstance)
|
||||
{
|
||||
PollingAutomationExecutor pollingAutomationExecutor = PollingAutomationExecutor.getInstance();
|
||||
pollingAutomationExecutor.setInitialDelayMillis(0);
|
||||
pollingAutomationExecutor.setDelayMillis(100);
|
||||
pollingAutomationExecutor.start(qInstance, TestUtils.POLLING_AUTOMATION);
|
||||
SleepUtils.sleep(1, TimeUnit.SECONDS);
|
||||
pollingAutomationExecutor.stop();
|
||||
}
|
||||
|
||||
}
|
@ -30,6 +30,9 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
@ -43,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -162,11 +166,13 @@ class QInstanceValidatorTest
|
||||
qInstance.getBackend("default").setName("notDefault");
|
||||
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setName("notGreetPeople");
|
||||
qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setName("notStates");
|
||||
qInstance.getAutomationProvider(TestUtils.POLLING_AUTOMATION).setName("notPolling");
|
||||
},
|
||||
"Inconsistent naming for table",
|
||||
"Inconsistent naming for backend",
|
||||
"Inconsistent naming for process",
|
||||
"Inconsistent naming for possibleValueSource"
|
||||
"Inconsistent naming for possibleValueSource",
|
||||
"Inconsistent naming for automationProvider"
|
||||
);
|
||||
}
|
||||
|
||||
@ -296,7 +302,7 @@ class QInstanceValidatorTest
|
||||
"Instance of CodeReference could not be created");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerThatIsNotAFunction.class, QCodeUsage.CUSTOMIZER)),
|
||||
"CodeReference could not be casted");
|
||||
"CodeReference is not of the expected type");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameters.class, QCodeUsage.CUSTOMIZER)),
|
||||
"Error validating customizer type parameters");
|
||||
@ -649,6 +655,230 @@ class QInstanceValidatorTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAutomationProviderType()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getAutomationProvider(TestUtils.POLLING_AUTOMATION).setType(null),
|
||||
"Missing type for automationProvider");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationProviderName()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().setProviderName(null),
|
||||
"is missing a providerName");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().setProviderName(""),
|
||||
"is missing a providerName");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().setProviderName("notARealProvider"),
|
||||
"unrecognized providerName");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationStatusTracking()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().setStatusTracking(null),
|
||||
"do not have statusTracking");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationStatusTrackingType()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().getStatusTracking().setType(null),
|
||||
"statusTracking is missing a type");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationStatusTrackingFieldName()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().getStatusTracking().setFieldName(null),
|
||||
"missing its fieldName");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().getStatusTracking().setFieldName(""),
|
||||
"missing its fieldName");
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// todo - make sure it's a field in the table?? //
|
||||
//////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionsNames()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> getAction0(qInstance).setName(null),
|
||||
"action missing a name");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> getAction0(qInstance).setName(""),
|
||||
"action missing a name");
|
||||
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
List<TableAutomationAction> actions = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().getActions();
|
||||
actions.add(actions.get(0));
|
||||
},
|
||||
"more than one action named");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionTriggerEvent()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> getAction0(qInstance).setTriggerEvent(null),
|
||||
"missing a triggerEvent");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionCodeReference()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> getAction0(qInstance).setCodeReference(new QCodeReference()),
|
||||
"missing a code reference name", "missing a code type");
|
||||
|
||||
assertValidationFailureReasons((qInstance) -> getAction0(qInstance).setCodeReference(new QCodeReference(TestUtils.CustomPossibleValueSource.class)),
|
||||
"is not of the expected type");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionProcessName()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setCodeReference(null);
|
||||
action.setProcessName("notAProcess");
|
||||
},
|
||||
"unrecognized processName");
|
||||
|
||||
assertValidationSuccess((qInstance) ->
|
||||
{
|
||||
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setCodeReference(null);
|
||||
action.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE);
|
||||
});
|
||||
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setCodeReference(null);
|
||||
action.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE);
|
||||
},
|
||||
"different table");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionCodeReferenceAndProcessName()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setCodeReference(null);
|
||||
action.setProcessName(null);
|
||||
},
|
||||
"missing both");
|
||||
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE);
|
||||
},
|
||||
"has both");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableAutomationActionFilter()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria())
|
||||
);
|
||||
},
|
||||
"without a field name", "without an operator");
|
||||
|
||||
assertValidationFailureReasons((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("notAField", QCriteriaOperator.EQUALS, Collections.emptyList()))
|
||||
);
|
||||
},
|
||||
"unrecognized field");
|
||||
|
||||
assertValidationSuccess((qInstance) ->
|
||||
{
|
||||
TableAutomationAction action = getAction0(qInstance);
|
||||
action.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(1701)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private TableAutomationAction getAction0(QInstance qInstance)
|
||||
{
|
||||
return qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().getActions().get(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a little setup code on a qInstance; then validate it, and assert that it
|
||||
** failed validation with reasons that match the supplied vararg-reasons (but allow
|
||||
@ -690,7 +920,8 @@ class QInstanceValidatorTest
|
||||
if(!allowExtraReasons)
|
||||
{
|
||||
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
|
||||
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons) + "\nActual reasons: " + e.getReasons());
|
||||
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons)
|
||||
+ "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", e.getReasons()) : "--"));
|
||||
}
|
||||
|
||||
for(String reason : reasons)
|
||||
|
@ -23,26 +23,39 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
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.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
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.automation.RecordAutomationInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
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.automation.PollingAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
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.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
@ -52,7 +65,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
@ -61,6 +79,8 @@ import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockB
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -69,6 +89,8 @@ import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackend
|
||||
*******************************************************************************/
|
||||
public class TestUtils
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(TestUtils.class);
|
||||
|
||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||
|
||||
@ -83,11 +105,15 @@ public class TestUtils
|
||||
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
||||
public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge";
|
||||
public static final String TABLE_NAME_PERSON_FILE = "personFile";
|
||||
public static final String TABLE_NAME_PERSON_MEMORY = "personMemory";
|
||||
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
||||
|
||||
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
||||
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
||||
public static final String POSSIBLE_VALUE_SOURCE_CUSTOM = "custom"; // custom-type
|
||||
public static final String POSSIBLE_VALUE_SOURCE_AUTOMATION_STATUS = "automationStatus";
|
||||
|
||||
public static final String POLLING_AUTOMATION = "polling";
|
||||
|
||||
|
||||
|
||||
@ -104,9 +130,11 @@ public class TestUtils
|
||||
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addTable(definePersonFileTable());
|
||||
qInstance.addTable(definePersonMemoryTable());
|
||||
qInstance.addTable(defineTableIdAndNameOnly());
|
||||
qInstance.addTable(defineTableShape());
|
||||
|
||||
qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource());
|
||||
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
||||
qInstance.addPossibleValueSource(defineShapePossibleValueSource());
|
||||
qInstance.addPossibleValueSource(defineCustomPossibleValueSource());
|
||||
@ -117,6 +145,8 @@ public class TestUtils
|
||||
qInstance.addProcess(new BasicETLProcess().defineProcessMetaData());
|
||||
qInstance.addProcess(new StreamedETLProcess().defineProcessMetaData());
|
||||
|
||||
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
||||
|
||||
defineApps(qInstance);
|
||||
|
||||
return (qInstance);
|
||||
@ -124,6 +154,18 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QAutomationProviderMetaData definePollingAutomationProvider()
|
||||
{
|
||||
return (new PollingAutomationProviderMetaData()
|
||||
.withName(POLLING_AUTOMATION)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -148,6 +190,21 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the "automationStatus" possible value source used in standard tests
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValueSource defineAutomationStatusPossibleValueSource()
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(POSSIBLE_VALUE_SOURCE_AUTOMATION_STATUS)
|
||||
.withType(QPossibleValueSourceType.ENUM)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||
.withValuesFromEnum(AutomationStatus.values());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the "states" possible value source used in standard tests
|
||||
**
|
||||
@ -252,6 +309,30 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QFieldMetaData standardQqqAutomationStatusField()
|
||||
{
|
||||
return (new QFieldMetaData("qqqAutomationStatus", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_AUTOMATION_STATUS));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QTableAutomationDetails defineStandardAutomationDetails()
|
||||
{
|
||||
return (new QTableAutomationDetails()
|
||||
.withProviderName(POLLING_AUTOMATION)
|
||||
.withStatusTracking(new AutomationStatusTracking()
|
||||
.withType(AutomationStatusTrackingType.FIELD_IN_TABLE)
|
||||
.withFieldName("qqqAutomationStatus")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the 'shape' table used in standard tests.
|
||||
*******************************************************************************/
|
||||
@ -289,6 +370,101 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define a 3nd version of the 'person' table, backed by the in-memory backend
|
||||
*******************************************************************************/
|
||||
public static QTableMetaData definePersonMemoryTable()
|
||||
{
|
||||
return (new QTableMetaData()
|
||||
.withName(TABLE_NAME_PERSON_MEMORY)
|
||||
.withBackendName(MEMORY_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withFields(TestUtils.defineTablePerson().getFields()))
|
||||
|
||||
.withField(standardQqqAutomationStatusField())
|
||||
.withAutomationDetails(defineStandardAutomationDetails()
|
||||
.withAction(new TableAutomationAction()
|
||||
.withName("checkAgeOnInsert")
|
||||
.withTriggerEvent(TriggerEvent.POST_INSERT)
|
||||
.withCodeReference(new QCodeReference(CheckAge.class))
|
||||
)
|
||||
.withAction(new TableAutomationAction()
|
||||
.withName("logOnUpdatePerFilter")
|
||||
.withTriggerEvent(TriggerEvent.POST_UPDATE)
|
||||
.withFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Darin"))))
|
||||
.withCodeReference(new QCodeReference(LogPersonUpdate.class))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class CheckAge extends RecordAutomationHandler
|
||||
{
|
||||
public static String SUFFIX_FOR_MINORS = " (a minor)";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
LocalDate limitDate = LocalDate.now().minusYears(18);
|
||||
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||
for(QRecord record : recordAutomationInput.getRecordList())
|
||||
{
|
||||
LocalDate birthDate = record.getValueLocalDate("birthDate");
|
||||
if(birthDate != null && birthDate.isAfter(limitDate))
|
||||
{
|
||||
LOG.info("Person [" + record.getValueInteger("id") + "] is a minor - updating their firstName to state such.");
|
||||
recordsToUpdate.add(new QRecord()
|
||||
.withValue("id", record.getValue("id"))
|
||||
.withValue("firstName", record.getValueString("firstName") + SUFFIX_FOR_MINORS)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(!recordsToUpdate.isEmpty())
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput(recordAutomationInput.getInstance());
|
||||
updateInput.setSession(recordAutomationInput.getSession());
|
||||
updateInput.setTableName(recordAutomationInput.getTableName());
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class LogPersonUpdate extends RecordAutomationHandler
|
||||
{
|
||||
public static List<Integer> updatedIds = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
for(QRecord record : recordAutomationInput.getRecordList())
|
||||
{
|
||||
updatedIds.add(record.getValueInteger("id"));
|
||||
LOG.info("Person [" + record.getValueInteger("id") + ":" + record.getValueString("firstName") + "] has been updated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define simple table with just an id and name
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user