mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merge branch 'feature/QQQ-40-record-automations' into feature/sprint-10
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,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
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.processes.BackendStep;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
@ -31,6 +32,7 @@ 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.QCodeType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
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.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -138,6 +140,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.ActionHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
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.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||||
@ -48,6 +50,9 @@ public class InsertAction
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public InsertOutput execute(InsertInput insertInput) throws QException
|
public InsertOutput execute(InsertInput insertInput) throws QException
|
||||||
{
|
{
|
||||||
|
ActionHelper.validateSession(insertInput);
|
||||||
|
setAutomationStatusField(insertInput);
|
||||||
|
|
||||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||||
// todo pre-customization - just get to modify the request?
|
// todo pre-customization - just get to modify the request?
|
||||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
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.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.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||||
@ -42,6 +44,7 @@ public class UpdateAction
|
|||||||
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
||||||
{
|
{
|
||||||
ActionHelper.validateSession(updateInput);
|
ActionHelper.validateSession(updateInput);
|
||||||
|
setAutomationStatusField(updateInput);
|
||||||
|
|
||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
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
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
return updateResult;
|
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.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
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.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
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.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
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.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.processes.QStepMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
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.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
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.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -65,6 +70,8 @@ public class QInstanceValidator
|
|||||||
|
|
||||||
private boolean printWarnings = false;
|
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! //
|
// do the validation checks - a good qInstance has all conditions TRUE! //
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
List<String> errors = new ArrayList<>();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
validateBackends(qInstance, errors);
|
validateBackends(qInstance);
|
||||||
validateTables(qInstance, errors);
|
validateAutomationProviders(qInstance);
|
||||||
validateProcesses(qInstance, errors);
|
validateTables(qInstance);
|
||||||
validateApps(qInstance, errors);
|
validateProcesses(qInstance);
|
||||||
validatePossibleValueSources(qInstance, errors);
|
validateApps(qInstance);
|
||||||
|
validatePossibleValueSources(qInstance);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
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) ->
|
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."))
|
"At least 1 table must be defined."))
|
||||||
{
|
{
|
||||||
qInstance.getTables().forEach((tableName, table) ->
|
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 //
|
// 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 + "."))
|
"Missing backend name for table " + tableName + "."))
|
||||||
{
|
{
|
||||||
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
|
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 //
|
// 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) ->
|
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() + ".");
|
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
|
||||||
|
|
||||||
if(field.getPossibleValueSourceName() != null)
|
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 + ".");
|
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -189,10 +213,10 @@ public class QInstanceValidator
|
|||||||
{
|
{
|
||||||
for(QFieldSection section : table.getSections())
|
for(QFieldSection section : table.getSections())
|
||||||
{
|
{
|
||||||
validateSection(errors, table, section, fieldNamesInSections);
|
validateSection(table, section, fieldNamesInSections);
|
||||||
if(section.getTier().equals(Tier.T1))
|
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;
|
tier1Section = section;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +226,7 @@ public class QInstanceValidator
|
|||||||
{
|
{
|
||||||
for(String fieldName : table.getFields().keySet())
|
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())
|
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())
|
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 + ": ";
|
String prefix = "Table " + tableName + ", customizer " + customizerName + ": ";
|
||||||
|
|
||||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -245,18 +378,18 @@ public class QInstanceValidator
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// make sure (at this time) that it's a java type, then do some java checks //
|
// 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 //
|
// make sure the class can be loaded //
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
Class<?> customizerClass = getClassForCodeReference(codeReference, prefix);
|
||||||
if(customizerClass != null)
|
if(customizerClass != null)
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// make sure the customizer can be instantiated //
|
// make sure the customizer can be instantiated //
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass);
|
||||||
|
|
||||||
TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
|
TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
|
||||||
if(tableCustomizer == null)
|
if(tableCustomizer == null)
|
||||||
@ -273,7 +406,7 @@ public class QInstanceValidator
|
|||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
if(customizerInstance != null && tableCustomizer.getTableCustomizer().getExpectedType() != null)
|
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();
|
Consumer<Object> validationFunction = tableCustomizer.getTableCustomizer().getValidationFunction();
|
||||||
if(castedObject != null && validationFunction != null)
|
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;
|
T castedObject = null;
|
||||||
try
|
try
|
||||||
@ -314,7 +447,7 @@ public class QInstanceValidator
|
|||||||
}
|
}
|
||||||
catch(ClassCastException e)
|
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;
|
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;
|
Object customizerInstance = null;
|
||||||
try
|
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(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() + ".");
|
assertCondition(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."))
|
if(assertCondition(CollectionUtils.nullSafeHasContents(section.getFieldNames()), "Table " + table.getName() + " section " + section.getName() + " does not have any fields."))
|
||||||
{
|
{
|
||||||
if(table.getFields() != null)
|
if(table.getFields() != null)
|
||||||
{
|
{
|
||||||
for(String fieldName : section.getFieldNames())
|
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(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(!fieldNamesInSections.contains(fieldName), "Table " + table.getName() + " has field " + fieldName + " listed more than once in its field sections.");
|
||||||
|
|
||||||
fieldNamesInSections.add(fieldName);
|
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()))
|
if(CollectionUtils.nullSafeHasContents(qInstance.getProcesses()))
|
||||||
{
|
{
|
||||||
qInstance.getProcesses().forEach((processName, process) ->
|
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 //
|
// validate the table name for the process //
|
||||||
/////////////////////////////////////////////
|
/////////////////////////////////////////////
|
||||||
if(process.getTableName() != null)
|
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 //
|
// 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;
|
int index = 0;
|
||||||
for(QStepMetaData step : process.getStepList())
|
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++;
|
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()))
|
if(CollectionUtils.nullSafeHasContents(qInstance.getApps()))
|
||||||
{
|
{
|
||||||
qInstance.getApps().forEach((appName, app) ->
|
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<>();
|
Set<String> appsVisited = new HashSet<>();
|
||||||
visitAppCheckingForCycles(app, appsVisited, errors);
|
visitAppCheckingForCycles(app, appsVisited);
|
||||||
|
|
||||||
if(app.getChildren() != null)
|
if(app.getChildren() != null)
|
||||||
{
|
{
|
||||||
Set<String> childNames = new HashSet<>();
|
Set<String> childNames = new HashSet<>();
|
||||||
for(QAppChildMetaData child : app.getChildren())
|
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(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(!childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName());
|
||||||
childNames.add(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()))
|
if(CollectionUtils.nullSafeHasContents(qInstance.getPossibleValueSources()))
|
||||||
{
|
{
|
||||||
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
||||||
{
|
{
|
||||||
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
||||||
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
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 //
|
// 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 ->
|
case ENUM ->
|
||||||
{
|
{
|
||||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
|
assertCondition(!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(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 ->
|
case TABLE ->
|
||||||
{
|
{
|
||||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
|
assertCondition(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(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 ->
|
case CUSTOM ->
|
||||||
{
|
{
|
||||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
|
assertCondition(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(!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.");
|
assertCondition(QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||||
validateCustomPossibleValueSourceCode(errors, pvsName, possibleValueSource.getCustomCodeReference());
|
validateSimpleCodeReference("PossibleValueSource " + pvsName + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
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(codeReference, prefix))
|
||||||
|
|
||||||
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -505,25 +636,25 @@ public class QInstanceValidator
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// make sure (at this time) that it's a java type, then do some java checks //
|
// 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 //
|
// make sure the class can be loaded //
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
Class<?> customizerClass = getClassForCodeReference(codeReference, prefix);
|
||||||
if(customizerClass != null)
|
if(customizerClass != null)
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// make sure the customizer can be instantiated //
|
// 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 //
|
// make sure the customizer instance can be cast to the expected type //
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
if(customizerInstance != null)
|
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;
|
Class<?> customizerClass = null;
|
||||||
try
|
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;
|
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;
|
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;
|
okay = false;
|
||||||
}
|
}
|
||||||
@ -575,9 +706,9 @@ public class QInstanceValidator
|
|||||||
** Check if an app's child list can recursively be traversed without finding a
|
** Check if an app's child list can recursively be traversed without finding a
|
||||||
** duplicate, which would indicate a cycle (e.g., an error)
|
** 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());
|
appsVisited.add(app.getName());
|
||||||
if(app.getChildren() != null)
|
if(app.getChildren() != null)
|
||||||
@ -586,7 +717,7 @@ public class QInstanceValidator
|
|||||||
{
|
{
|
||||||
if(child instanceof QAppMetaData childApp)
|
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)
|
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,
|
** 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).
|
** 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)
|
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
|
** Enum to define the possible authentication types
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public enum QAuthenticationType
|
public enum QAuthenticationType
|
||||||
{
|
{
|
||||||
AUTH_0("auth0"),
|
AUTH_0("auth0"),
|
||||||
|
@ -29,6 +29,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
|
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.layout.QAppMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
@ -51,6 +52,7 @@ public class QInstance
|
|||||||
private Map<String, QBackendMetaData> backends = new HashMap<>();
|
private Map<String, QBackendMetaData> backends = new HashMap<>();
|
||||||
|
|
||||||
private QAuthenticationMetaData authentication = null;
|
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. //
|
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||||
@ -104,17 +106,6 @@ public class QInstance
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for hasBeenValidated
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setHasBeenValidated(QInstanceValidationKey key)
|
|
||||||
{
|
|
||||||
this.hasBeenValidated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -153,6 +144,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -196,6 +209,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -234,6 +269,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -288,6 +345,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -326,94 +405,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
|
** Getter for apps
|
||||||
**
|
**
|
||||||
@ -436,6 +427,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
|
** Getter for hasBeenValidated
|
||||||
**
|
**
|
||||||
@ -447,6 +494,17 @@ public class QInstance
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for hasBeenValidated
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setHasBeenValidated(QInstanceValidationKey key)
|
||||||
|
{
|
||||||
|
this.hasBeenValidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for authentication
|
** 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
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.processes.BackendStep;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
|
|
||||||
@ -60,6 +61,17 @@ public class QCodeReference implements Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "QCodeReference{name='" + name + "'}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor that just takes a java class, and infers the other fields.
|
** Constructor that just takes a java class, and infers the other fields.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -76,6 +88,10 @@ public class QCodeReference implements Serializable
|
|||||||
{
|
{
|
||||||
this.codeUsage = QCodeUsage.POSSIBLE_VALUE_PROVIDER;
|
this.codeUsage = QCodeUsage.POSSIBLE_VALUE_PROVIDER;
|
||||||
}
|
}
|
||||||
|
else if(RecordAutomationHandler.class.isAssignableFrom(javaClass))
|
||||||
|
{
|
||||||
|
this.codeUsage = QCodeUsage.RECORD_AUTOMATION_HANDLER;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
|
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
|
BACKEND_STEP, // a backend-step in a process
|
||||||
CUSTOMIZER, // a function to customize part of a QQQ table's behavior
|
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.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
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.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 Map<String, QFieldMetaData> fields;
|
||||||
|
|
||||||
private QTableBackendDetails backendDetails;
|
private QTableBackendDetails backendDetails;
|
||||||
|
private QTableAutomationDetails automationDetails;
|
||||||
|
|
||||||
private Map<String, QCodeReference> customizers;
|
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 java.util.function.Function;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
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.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.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.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
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.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.model.metadata.tables.Tier;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -162,11 +166,13 @@ class QInstanceValidatorTest
|
|||||||
qInstance.getBackend("default").setName("notDefault");
|
qInstance.getBackend("default").setName("notDefault");
|
||||||
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setName("notGreetPeople");
|
qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setName("notGreetPeople");
|
||||||
qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setName("notStates");
|
qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setName("notStates");
|
||||||
|
qInstance.getAutomationProvider(TestUtils.POLLING_AUTOMATION).setName("notPolling");
|
||||||
},
|
},
|
||||||
"Inconsistent naming for table",
|
"Inconsistent naming for table",
|
||||||
"Inconsistent naming for backend",
|
"Inconsistent naming for backend",
|
||||||
"Inconsistent naming for process",
|
"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");
|
"Instance of CodeReference could not be created");
|
||||||
|
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerThatIsNotAFunction.class, QCodeUsage.CUSTOMIZER)),
|
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)),
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameters.class, QCodeUsage.CUSTOMIZER)),
|
||||||
"Error validating customizer type parameters");
|
"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
|
** 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
|
** failed validation with reasons that match the supplied vararg-reasons (but allow
|
||||||
@ -690,7 +920,8 @@ class QInstanceValidatorTest
|
|||||||
if(!allowExtraReasons)
|
if(!allowExtraReasons)
|
||||||
{
|
{
|
||||||
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
|
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)
|
for(String reason : reasons)
|
||||||
|
@ -23,26 +23,39 @@ package com.kingsrook.qqq.backend.core.utils;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.AddAge;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
|
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.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
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.actions.values.QCustomPossibleValueProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
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.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.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
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.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
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.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
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.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
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.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
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.QPossibleValue;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
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.QFunctionOutputMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
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.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.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.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
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.basic.BasicETLProcess;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
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
|
public class TestUtils
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(TestUtils.class);
|
||||||
|
|
||||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
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_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
||||||
public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge";
|
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_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 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_STATE = "state"; // enum-type
|
||||||
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-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_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(defineTablePerson());
|
||||||
qInstance.addTable(definePersonFileTable());
|
qInstance.addTable(definePersonFileTable());
|
||||||
|
qInstance.addTable(definePersonMemoryTable());
|
||||||
qInstance.addTable(defineTableIdAndNameOnly());
|
qInstance.addTable(defineTableIdAndNameOnly());
|
||||||
qInstance.addTable(defineTableShape());
|
qInstance.addTable(defineTableShape());
|
||||||
|
|
||||||
|
qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource());
|
||||||
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
||||||
qInstance.addPossibleValueSource(defineShapePossibleValueSource());
|
qInstance.addPossibleValueSource(defineShapePossibleValueSource());
|
||||||
qInstance.addPossibleValueSource(defineCustomPossibleValueSource());
|
qInstance.addPossibleValueSource(defineCustomPossibleValueSource());
|
||||||
@ -117,6 +145,8 @@ public class TestUtils
|
|||||||
qInstance.addProcess(new BasicETLProcess().defineProcessMetaData());
|
qInstance.addProcess(new BasicETLProcess().defineProcessMetaData());
|
||||||
qInstance.addProcess(new StreamedETLProcess().defineProcessMetaData());
|
qInstance.addProcess(new StreamedETLProcess().defineProcessMetaData());
|
||||||
|
|
||||||
|
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
||||||
|
|
||||||
defineApps(qInstance);
|
defineApps(qInstance);
|
||||||
|
|
||||||
return (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
|
** 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.
|
** 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
|
** Define simple table with just an id and name
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user