Merge branch 'feature/CTLE-433-cart-rover-now-extensiv-integration' into integration/sprint-26

This commit is contained in:
Tim Chamberlain
2023-06-06 11:14:58 -05:00
26 changed files with 695 additions and 128 deletions

View File

@ -53,7 +53,7 @@
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
<coverage.haltOnFailure>true</coverage.haltOnFailure>
<coverage.instructionCoveredRatioMinimum>0.75</coverage.instructionCoveredRatioMinimum>
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
<plugin.shade.phase>none</plugin.shade.phase>
</properties>

View File

@ -52,6 +52,7 @@ 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.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
@ -430,7 +431,7 @@ public class GetAction
{
returnRecord.removeValue(fieldName);
}
else if(field.getType() != null && field.getType().needsMasked())
else if(getInput.getShouldMaskPasswords() && field.getType() != null && field.getType().needsMasked() && !field.hasAdornmentType(AdornmentType.REVEAL))
{
//////////////////////////////////////////////////////////////////////
// empty out the value completely first (which will remove from //

View File

@ -47,6 +47,7 @@ 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
@ -283,7 +284,7 @@ public class QueryAction
{
hiddenFields.add(fieldName);
}
else if(field.getType() != null && field.getType().needsMasked())
else if(queryInput.getShouldMaskPasswords() && field.getType() != null && field.getType().needsMasked() && !field.hasAdornmentType(AdornmentType.REVEAL))
{
maskedFields.add(fieldName);
}

View File

@ -0,0 +1,53 @@
/*
* 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.exceptions;
/*******************************************************************************
** User-facing exception for when user provided bad or missing data in their request
**
*******************************************************************************/
public class QBadRequestException extends QUserFacingException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QBadRequestException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QBadRequestException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@ -67,6 +67,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMeta
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
@ -1208,6 +1209,23 @@ public class QInstanceValidator
}
}
}
///////////////////////////////////////////////////////////////////////////////
// if the process has a schedule, make sure required schedule data populated //
///////////////////////////////////////////////////////////////////////////////
if(process.getSchedule() != null)
{
QScheduleMetaData schedule = process.getSchedule();
assertCondition(schedule.getRepeatMillis() != null || schedule.getRepeatSeconds() != null, "Either repeat millis or repeat seconds must be set on schedule in process " + processName);
if(schedule.getBackendVariant() != null)
{
assertCondition(schedule.getVariantRunStrategy() != null, "A variant strategy was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
assertCondition(schedule.getVariantTableName() != null, "A variant table name was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
assertCondition(schedule.getVariantFieldName() != null, "A variant field name was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
}
}
});
}
}

View File

@ -43,6 +43,9 @@ public class QBackendMetaData
private String name;
private String backendType;
private Boolean usesVariants = false;
private String variantOptionsTableName;
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
@ -343,4 +346,67 @@ public class QBackendMetaData
// noop in base class //
////////////////////////
}
/*******************************************************************************
** Getter for usesVariants
*******************************************************************************/
public Boolean getUsesVariants()
{
return (this.usesVariants);
}
/*******************************************************************************
** Setter for usesVariants
*******************************************************************************/
public void setUsesVariants(Boolean usesVariants)
{
this.usesVariants = usesVariants;
}
/*******************************************************************************
** Fluent setter for usesVariants
*******************************************************************************/
public QBackendMetaData withUsesVariants(Boolean usesVariants)
{
this.usesVariants = usesVariants;
return (this);
}
/*******************************************************************************
** Getter for variantsOptionTableName
*******************************************************************************/
public String getVariantOptionsTableName()
{
return (this.variantOptionsTableName);
}
/*******************************************************************************
** Setter for variantsOptionTableName
*******************************************************************************/
public void setVariantOptionsTableName(String variantOptionsTableName)
{
this.variantOptionsTableName = variantOptionsTableName;
}
/*******************************************************************************
** Fluent setter for variantsOptionTableName
*******************************************************************************/
public QBackendMetaData withVariantsOptionTableName(String variantsOptionTableName)
{
this.variantOptionsTableName = variantsOptionTableName;
return (this);
}
}

View File

@ -524,6 +524,25 @@ public class QFieldMetaData implements Cloneable
/*******************************************************************************
** does this field have the given addornment
**
*******************************************************************************/
public boolean hasAdornmentType(AdornmentType adornmentType)
{
for(FieldAdornment thisAdornment : CollectionUtils.nonNullList(this.adornments))
{
if(AdornmentType.REVEAL.equals(thisAdornment.getType()))
{
return (true);
}
}
return (false);
}
/*******************************************************************************
** Getter for adornments
**

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
/*******************************************************************************
** Meta-data to define scheduled actions within QQQ.
**
@ -33,11 +36,22 @@ package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
*******************************************************************************/
public class QScheduleMetaData
{
public enum RunStrategy
{PARALLEL, SERIAL}
private Integer repeatSeconds;
private Integer repeatMillis;
private Integer initialDelaySeconds;
private Integer initialDelayMillis;
private RunStrategy variantRunStrategy;
private String backendVariant;
private String variantTableName;
private QQueryFilter variantFilter;
private String variantFieldName;
/*******************************************************************************
@ -174,4 +188,159 @@ public class QScheduleMetaData
return (this);
}
/*******************************************************************************
** Getter for backendVariant
*******************************************************************************/
public String getBackendVariant()
{
return (this.backendVariant);
}
/*******************************************************************************
** Setter for backendVariant
*******************************************************************************/
public void setBackendVariant(String backendVariant)
{
this.backendVariant = backendVariant;
}
/*******************************************************************************
** Fluent setter for backendVariant
*******************************************************************************/
public QScheduleMetaData withBackendVariant(String backendVariant)
{
this.backendVariant = backendVariant;
return (this);
}
/*******************************************************************************
** Getter for variantTableName
*******************************************************************************/
public String getVariantTableName()
{
return (this.variantTableName);
}
/*******************************************************************************
** Setter for variantTableName
*******************************************************************************/
public void setVariantTableName(String variantTableName)
{
this.variantTableName = variantTableName;
}
/*******************************************************************************
** Fluent setter for variantTableName
*******************************************************************************/
public QScheduleMetaData withVariantTableName(String variantTableName)
{
this.variantTableName = variantTableName;
return (this);
}
/*******************************************************************************
** Getter for variantFilter
*******************************************************************************/
public QQueryFilter getVariantFilter()
{
return (this.variantFilter);
}
/*******************************************************************************
** Setter for variantFilter
*******************************************************************************/
public void setVariantFilter(QQueryFilter variantFilter)
{
this.variantFilter = variantFilter;
}
/*******************************************************************************
** Fluent setter for variantFilter
*******************************************************************************/
public QScheduleMetaData withVariantFilter(QQueryFilter variantFilter)
{
this.variantFilter = variantFilter;
return (this);
}
/*******************************************************************************
** Getter for variantFieldName
*******************************************************************************/
public String getVariantFieldName()
{
return (this.variantFieldName);
}
/*******************************************************************************
** Setter for variantFieldName
*******************************************************************************/
public void setVariantFieldName(String variantFieldName)
{
this.variantFieldName = variantFieldName;
}
/*******************************************************************************
** Fluent setter for variantFieldName
*******************************************************************************/
public QScheduleMetaData withVariantFieldName(String variantFieldName)
{
this.variantFieldName = variantFieldName;
return (this);
}
/*******************************************************************************
** Getter for variantRunStrategy
*******************************************************************************/
public RunStrategy getVariantRunStrategy()
{
return (this.variantRunStrategy);
}
/*******************************************************************************
** Setter for variantRunStrategy
*******************************************************************************/
public void setVariantRunStrategy(RunStrategy variantRunStrategy)
{
this.variantRunStrategy = variantRunStrategy;
}
/*******************************************************************************
** Fluent setter for variantRunStrategy
*******************************************************************************/
public QScheduleMetaData withVariantRunStrategy(RunStrategy variantRunStrategy)
{
this.variantRunStrategy = variantRunStrategy;
return (this);
}
}

View File

@ -37,6 +37,7 @@ import java.util.function.Consumer;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
@ -283,6 +284,10 @@ public class DeserializerUtils
{
setterMap.get(fieldName).accept(textNode.asText());
}
else if(fieldNode instanceof BooleanNode booleanNode)
{
setterMap.get(fieldName).accept(booleanNode);
}
else if(fieldNode instanceof ObjectNode)
{
setterMap.get(fieldName).accept(fieldNode);

View File

@ -47,8 +47,9 @@ public class QSession implements Serializable
private QUser user;
private String uuid;
private Map<String, List<Serializable>> securityKeyValues;
private Set<String> permissions;
private Map<String, List<Serializable>> securityKeyValues;
private Map<String, Serializable> backendVariants;
// implementation-specific custom values
private Map<String, String> values;
@ -466,4 +467,35 @@ public class QSession implements Serializable
return (this);
}
/*******************************************************************************
** Getter for backendVariants
*******************************************************************************/
public Map<String, Serializable> getBackendVariants()
{
return (this.backendVariants);
}
/*******************************************************************************
** Setter for backendVariants
*******************************************************************************/
public void setBackendVariants(Map<String, Serializable> backendVariants)
{
this.backendVariants = backendVariants;
}
/*******************************************************************************
** Fluent setter for backendVariants
*******************************************************************************/
public QSession withBackendVariants(Map<String, Serializable> backendVariants)
{
this.backendVariants = backendVariants;
return (this);
}
}

View File

@ -232,25 +232,7 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
///////////////////////////////////////////////////////////////////////////////////////////////////
// query to see if we already have those records in the destination (to determine insert/update) //
///////////////////////////////////////////////////////////////////////////////////////////////////
Map<Serializable, QRecord> existingRecordsByForeignKey = Collections.emptyMap();
if(!sourceKeyList.isEmpty())
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(destinationTableName);
getTransaction().ifPresent(queryInput::setTransaction);
QQueryFilter filter = getExistingRecordQueryFilter(runBackendStepInput, sourceKeyList);
queryInput.setFilter(filter);
Collection<String> associationNamesToInclude = getAssociationNamesToInclude();
if(CollectionUtils.nullSafeHasContents(associationNamesToInclude))
{
queryInput.setIncludeAssociations(true);
queryInput.setAssociationNamesToInclude(associationNamesToInclude);
}
QueryOutput queryOutput = new QueryAction().execute(queryInput);
existingRecordsByForeignKey = CollectionUtils.recordsToMap(queryOutput.getRecords(), destinationTableForeignKeyField);
}
Map<Serializable, QRecord> existingRecordsByForeignKey = getExistingRecordsByForeignKey(runBackendStepInput, destinationTableForeignKeyField, destinationTableName, sourceKeyList);
/////////////////////////////////////////////////////////////////
// foreach source record, build the record we'll insert/update //
@ -348,6 +330,35 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
/*******************************************************************************
**
*******************************************************************************/
protected Map<Serializable, QRecord> getExistingRecordsByForeignKey(RunBackendStepInput runBackendStepInput, String destinationTableForeignKeyField, String destinationTableName, List<Serializable> sourceKeyList) throws QException
{
Map<Serializable, QRecord> existingRecordsByForeignKey = Collections.emptyMap();
if(!sourceKeyList.isEmpty())
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(destinationTableName);
getTransaction().ifPresent(queryInput::setTransaction);
QQueryFilter filter = getExistingRecordQueryFilter(runBackendStepInput, sourceKeyList);
queryInput.setFilter(filter);
Collection<String> associationNamesToInclude = getAssociationNamesToInclude();
if(CollectionUtils.nullSafeHasContents(associationNamesToInclude))
{
queryInput.setIncludeAssociations(true);
queryInput.setAssociationNamesToInclude(associationNamesToInclude);
}
QueryOutput queryOutput = new QueryAction().execute(queryInput);
existingRecordsByForeignKey = CollectionUtils.recordsToMap(queryOutput.getRecords(), destinationTableForeignKeyField);
}
return (existingRecordsByForeignKey);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -34,7 +34,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
@ -67,9 +66,9 @@ public class GeneralProcessUtils
** e.g., for a list of orders (with a clientId field), build a map of client.id => client record
** via getForeignRecordMap(input, orderList, "clientId", "client", "id")
*******************************************************************************/
public static Map<Serializable, QRecord> getForeignRecordMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
public static Map<Serializable, QRecord> getForeignRecordMap(List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
{
return getForeignRecordMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName, new QQueryFilter());
return getForeignRecordMap(sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName, new QQueryFilter());
}
// todo not commit - clean up all method sigs
@ -84,7 +83,7 @@ public class GeneralProcessUtils
** e.g., for a list of orders (with a clientId field), build a map of client.id => client record
** via getForeignRecordMap(input, orderList, "clientId", "client", "id")
*******************************************************************************/
public static Map<Serializable, QRecord> getForeignRecordMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName, QQueryFilter additionalFilter) throws QException
public static Map<Serializable, QRecord> getForeignRecordMap(List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName, QQueryFilter additionalFilter) throws QException
{
Map<Serializable, QRecord> foreignRecordMap = new HashMap<>();
QueryInput queryInput = new QueryInput();
@ -112,7 +111,7 @@ public class GeneralProcessUtils
** e.g., for a list of orders, build a ListingHash of order.id => List(OrderLine records)
** via getForeignRecordListingHashMap(input, orderList, "id", "orderLine", "orderId")
*******************************************************************************/
public static ListingHash<Serializable, QRecord> getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
public static ListingHash<Serializable, QRecord> getForeignRecordListingHashMap(List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{
ListingHash<Serializable, QRecord> foreignRecordMap = new ListingHash<>();
QueryInput queryInput = new QueryInput();
@ -139,9 +138,9 @@ public class GeneralProcessUtils
** e.g., for a list of orders (with a clientId field), setValue("client", QRecord(client));
** via addForeignRecordsToRecordList(input, orderList, "clientId", "client", "id")
*******************************************************************************/
public static void addForeignRecordsToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
public static void addForeignRecordsToRecordList(List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
{
Map<Serializable, QRecord> foreignRecordMap = getForeignRecordMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName);
Map<Serializable, QRecord> foreignRecordMap = getForeignRecordMap(sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName);
for(QRecord sourceRecord : sourceRecords)
{
QRecord foreignRecord = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName));
@ -159,9 +158,9 @@ public class GeneralProcessUtils
** e.g., for a list of orders, setValue("orderLine", List(QRecord(orderLine)))
** via addForeignRecordsListToRecordList(input, orderList, "id", "orderLine", "orderId")
*******************************************************************************/
public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
public static void addForeignRecordsListToRecordList(List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{
ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName);
ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName);
for(QRecord sourceRecord : sourceRecords)
{
List<QRecord> foreignRecordList = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName));
@ -185,7 +184,7 @@ public class GeneralProcessUtils
** Run a query on tableName, for where fieldName equals fieldValue, and return
** the list of QRecords.
*******************************************************************************/
public static List<QRecord> getRecordListByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
public static List<QRecord> getRecordListByField(String tableName, String fieldName, Serializable fieldValue) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -201,7 +200,7 @@ public class GeneralProcessUtils
** key, or any other field on the table. Note, if multiple rows do match the value,
** only 1 (determined in an unspecified way) is returned.
*******************************************************************************/
public static Optional<QRecord> getRecordByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
public static Optional<QRecord> getRecordByField(String tableName, String fieldName, Serializable fieldValue) throws QException
{
if(fieldValue == null)
{
@ -222,9 +221,9 @@ public class GeneralProcessUtils
** key, or any other field on the table. Note, if multiple rows do match the value,
** only 1 (determined in an unspecified way) is returned.
*******************************************************************************/
public static <T extends QRecordEntity> Optional<T> getEntityByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue, Class<T> entityClass) throws QException
public static <T extends QRecordEntity> Optional<T> getEntityByField(String tableName, String fieldName, Serializable fieldValue, Class<T> entityClass) throws QException
{
Optional<QRecord> optionalQRecord = getRecordByField(parentActionInput, tableName, fieldName, fieldValue);
Optional<QRecord> optionalQRecord = getRecordByField(tableName, fieldName, fieldValue);
if(optionalQRecord.isPresent())
{
return (Optional.of(QRecordEntity.fromQRecord(entityClass, optionalQRecord.get())));
@ -237,9 +236,9 @@ public class GeneralProcessUtils
/*******************************************************************************
** Query to get one record by a unique key value.
*******************************************************************************/
public static QRecord getRecordByFieldOrElseThrow(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
public static QRecord getRecordByFieldOrElseThrow(String tableName, String fieldName, Serializable fieldValue) throws QException
{
return getRecordByField(parentActionInput, tableName, fieldName, fieldValue)
return getRecordByField(tableName, fieldName, fieldValue)
.orElseThrow(() -> new QException(tableName + " with " + fieldName + " of " + fieldValue + " was not found."));
}
@ -248,7 +247,7 @@ public class GeneralProcessUtils
/*******************************************************************************
** Query to get one record by its primary key value.
*******************************************************************************/
public static Optional<QRecord> getRecordByPrimaryKey(AbstractActionInput parentActionInput, String tableName, Serializable value) throws QException
public static Optional<QRecord> getRecordByPrimaryKey(String tableName, Serializable value) throws QException
{
GetInput getInput = new GetInput();
getInput.setTableName(tableName);
@ -262,9 +261,9 @@ public class GeneralProcessUtils
/*******************************************************************************
** Query to get one record by its primary key value.
*******************************************************************************/
public static QRecord getRecordByPrimaryKeyOrElseThrow(AbstractActionInput parentActionInput, String tableName, Serializable value) throws QException
public static QRecord getRecordByPrimaryKeyOrElseThrow(String tableName, Serializable value) throws QException
{
return getRecordByPrimaryKey(parentActionInput, tableName, value)
return getRecordByPrimaryKey(tableName, value)
.orElseThrow(() -> new QException(tableName + " with primary key of " + value + " was not found."));
}
@ -276,7 +275,7 @@ public class GeneralProcessUtils
** Note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static List<QRecord> loadTable(AbstractActionInput parentActionInput, String tableName) throws QException
public static List<QRecord> loadTable(String tableName) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -292,7 +291,7 @@ public class GeneralProcessUtils
** Note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static <T extends QRecordEntity> List<T> loadTable(AbstractActionInput parentActionInput, String tableName, Class<T> entityClass) throws QException
public static <T extends QRecordEntity> List<T> loadTable(String tableName, Class<T> entityClass) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -320,9 +319,9 @@ public class GeneralProcessUtils
** Also, note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static Map<Serializable, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException
public static Map<Serializable, QRecord> loadTableToMap(String tableName, String keyFieldName) throws QException
{
return (loadTableToMap(parentActionInput, tableName, keyFieldName, (QQueryFilter) null));
return (loadTableToMap(tableName, keyFieldName, (QQueryFilter) null));
}
@ -335,7 +334,7 @@ public class GeneralProcessUtils
** If multiple values are found for the key, they'll squash each other, and only
** one (random) value will appear.
*******************************************************************************/
public static Map<Serializable, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName, QQueryFilter filter) throws QException
public static Map<Serializable, QRecord> loadTableToMap(String tableName, String keyFieldName, QQueryFilter filter) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -369,7 +368,7 @@ public class GeneralProcessUtils
** Also, note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static <T extends Serializable> Map<T, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, Class<T> keyType, String keyFieldName) throws QException
public static <T extends Serializable> Map<T, QRecord> loadTableToMap(String tableName, Class<T> keyType, String keyFieldName) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -394,7 +393,7 @@ public class GeneralProcessUtils
/*******************************************************************************
** Note - null values from the key field are NOT put in the map.
*******************************************************************************/
public static <T extends QRecordEntity> Map<Serializable, T> loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName, Class<T> entityClass) throws QException
public static <T extends QRecordEntity> Map<Serializable, T> loadTableToMap(String tableName, String keyFieldName, Class<T> entityClass) throws QException
{
return (loadTableToMap(tableName, keyFieldName, entityClass, null));
}
@ -441,7 +440,7 @@ public class GeneralProcessUtils
** Also, note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static ListingHash<Serializable, QRecord> loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException
public static ListingHash<Serializable, QRecord> loadTableToListingHash(String tableName, String keyFieldName) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
@ -514,7 +513,7 @@ public class GeneralProcessUtils
/*******************************************************************************
**
*******************************************************************************/
public static Integer count(AbstractActionInput input, String tableName, QQueryFilter filter) throws QException
public static Integer count(String tableName, QQueryFilter filter) throws QException
{
CountInput countInput = new CountInput();
countInput.setTableName(tableName);

View File

@ -83,7 +83,7 @@ public class RecordLookupHelper
{
if(disallowedOneOffLookups.isEmpty() || !disallowedOneOffLookups.contains(Pair.of(tableName, keyFieldName)))
{
Optional<QRecord> optRecord = GeneralProcessUtils.getRecordByField(null, tableName, keyFieldName, key);
Optional<QRecord> optRecord = GeneralProcessUtils.getRecordByField(tableName, keyFieldName, key);
recordMap.put(key, optRecord.orElse(null));
}
}
@ -106,7 +106,7 @@ public class RecordLookupHelper
String mapKey = tableName + "." + keyFieldName;
if(!preloadedKeys.contains(mapKey))
{
Map<Serializable, QRecord> recordMap = GeneralProcessUtils.loadTableToMap(null, tableName, keyFieldName);
Map<Serializable, QRecord> recordMap = GeneralProcessUtils.loadTableToMap(tableName, keyFieldName);
recordMaps.put(mapKey, recordMap);
preloadedKeys.add(mapKey);
}
@ -126,7 +126,7 @@ public class RecordLookupHelper
{
String mapKey = tableName + "." + keyFieldName;
Map<Serializable, QRecord> tableMap = recordMaps.computeIfAbsent(mapKey, s -> new HashMap<>());
tableMap.putAll(GeneralProcessUtils.loadTableToMap(null, tableName, keyFieldName, filter));
tableMap.putAll(GeneralProcessUtils.loadTableToMap(tableName, keyFieldName, filter));
}
@ -148,7 +148,7 @@ public class RecordLookupHelper
Map<Serializable, QRecord> tableMap = recordMaps.computeIfAbsent(mapKey, s -> new HashMap<>());
QQueryFilter filter = new QQueryFilter(new QFilterCriteria(keyFieldName, QCriteriaOperator.IN, inList));
tableMap.putAll(GeneralProcessUtils.loadTableToMap(null, tableName, keyFieldName, filter));
tableMap.putAll(GeneralProcessUtils.loadTableToMap(tableName, keyFieldName, filter));
QFieldType type = QContext.getQInstance().getTable(tableName).getField(keyFieldName).getType();
for(Serializable keyValue : inList)

View File

@ -22,17 +22,25 @@
package com.kingsrook.qqq.backend.core.scheduler;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.queues.SQSQueuePoller;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@ -41,6 +49,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaDa
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
/*******************************************************************************
@ -131,10 +141,68 @@ public class ScheduleManager
{
if(process.getSchedule() != null && allowedToStart(process.getName()))
{
startProcess(process);
QScheduleMetaData scheduleMetaData = process.getSchedule();
if(process.getSchedule().getBackendVariant() == null || QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
{
///////////////////////////////////////////////
// if no variants, or variant is serial mode //
///////////////////////////////////////////////
startProcess(process, null);
}
else if(QScheduleMetaData.RunStrategy.PARALLEL.equals(process.getSchedule().getVariantRunStrategy()))
{
/////////////////////////////////////////////////////////////////////////////////////////////////////
// if this a "parallel", which for example means we want to have a thread for each backend variant //
// running at the same time, get the variant records and schedule each separately //
/////////////////////////////////////////////////////////////////////////////////////////////////////
QContext.init(qInstance, sessionSupplier.get());
for(QRecord qRecord : CollectionUtils.nonNullList(getBackendVariantFilteredRecords(process)))
{
try
{
startProcess(process, MapBuilder.of(scheduleMetaData.getBackendVariant(), qRecord.getValue(scheduleMetaData.getVariantFieldName())));
}
catch(Exception e)
{
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
}
}
}
else
{
LOG.error("Unsupported Schedule Run Strategy [" + process.getSchedule().getVariantFilter() + "] was provided.");
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private List<QRecord> getBackendVariantFilteredRecords(QProcessMetaData processMetaData)
{
List<QRecord> records = null;
try
{
QScheduleMetaData scheduleMetaData = processMetaData.getSchedule();
QueryInput queryInput = new QueryInput();
queryInput.setTableName(scheduleMetaData.getVariantTableName());
queryInput.setFilter(scheduleMetaData.getVariantFilter());
QContext.init(qInstance, sessionSupplier.get());
QueryOutput queryOutput = new QueryAction().execute(queryInput);
records = queryOutput.getRecords();
}
catch(Exception e)
{
LOG.error("An error fetching variant data for process [" + processMetaData.getLabel() + "]", e);
}
return (records);
}
@ -249,26 +317,39 @@ public class ScheduleManager
/*******************************************************************************
**
*******************************************************************************/
private void startProcess(QProcessMetaData process)
private void startProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData)
{
Runnable runProcess = () ->
{
String originalThreadName = Thread.currentThread().getName();
try
{
if(process.getSchedule().getBackendVariant() == null || QScheduleMetaData.RunStrategy.PARALLEL.equals(process.getSchedule().getVariantRunStrategy()))
{
QContext.init(qInstance, sessionSupplier.get());
Thread.currentThread().setName("ScheduledProcess>" + process.getName());
LOG.debug("Running Scheduled Process [" + process.getName() + "]");
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(process.getName());
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
QContext.pushAction(runProcessInput);
RunProcessAction runProcessAction = new RunProcessAction();
runProcessAction.execute(runProcessInput);
executeSingleProcess(process, backendVariantData);
}
else if(QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
{
///////////////////////////////////////////////////////////////////////////////////////////////////
// if this is "serial", which for example means we want to run each backend variant one after //
// the other in the same thread so loop over these here so that they run in same lambda function //
///////////////////////////////////////////////////////////////////////////////////////////////////
for(QRecord qRecord : getBackendVariantFilteredRecords(process))
{
try
{
QContext.init(qInstance, sessionSupplier.get());
QScheduleMetaData scheduleMetaData = process.getSchedule();
executeSingleProcess(process, MapBuilder.of(scheduleMetaData.getBackendVariant(), qRecord.getValue(scheduleMetaData.getVariantFieldName())));
}
catch(Exception e)
{
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
}
}
}
}
catch(Exception e)
{
@ -294,6 +375,31 @@ public class ScheduleManager
/*******************************************************************************
**
*******************************************************************************/
private static void executeSingleProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData) throws QException
{
if(backendVariantData != null)
{
QContext.getQSession().setBackendVariants(backendVariantData);
}
Thread.currentThread().setName("ScheduledProcess>" + process.getName());
LOG.debug("Running Scheduled Process [" + process.getName() + "]");
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(process.getName());
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
QContext.pushAction(runProcessInput);
RunProcessAction runProcessAction = new RunProcessAction();
runProcessAction.execute(runProcessInput);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -69,9 +69,9 @@ class AuditActionTest extends BaseTest
/////////////////////////////////////
// make sure things can be fetched //
/////////////////////////////////////
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId);
assertEquals("Test Audit", auditRecord.getValueString("message"));
}
@ -95,7 +95,7 @@ class AuditActionTest extends BaseTest
///////////////////////////////////////////////////////////////////
// it should not throw, but it should also not insert the audit. //
///////////////////////////////////////////////////////////////////
Optional<QRecord> auditRecord = GeneralProcessUtils.getRecordByField(null, "audit", "recordId", recordId);
Optional<QRecord> auditRecord = GeneralProcessUtils.getRecordByField("audit", "recordId", recordId);
assertFalse(auditRecord.isPresent());
////////////////////////////////////////////////////////////////////////////////////////////////
@ -109,7 +109,7 @@ class AuditActionTest extends BaseTest
/////////////////////////////////////
// now the audit should be stored. //
/////////////////////////////////////
auditRecord = GeneralProcessUtils.getRecordByField(null, "audit", "recordId", recordId);
auditRecord = GeneralProcessUtils.getRecordByField("audit", "recordId", recordId);
assertTrue(auditRecord.isPresent());
}
@ -137,12 +137,12 @@ class AuditActionTest extends BaseTest
/////////////////////////////////////
// make sure things can be fetched //
/////////////////////////////////////
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId1);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId1);
assertEquals("Test Audit", auditRecord.getValueString("message"));
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId2);
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId2);
assertEquals("Test Another Audit", auditRecord.getValueString("message"));
assertEquals(47, auditRecord.getValueInteger(TestUtils.SECURITY_KEY_TYPE_STORE));
}
@ -173,26 +173,26 @@ class AuditActionTest extends BaseTest
/////////////////////////////////////
// make sure things can be fetched //
/////////////////////////////////////
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId1);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditTable", "name", TestUtils.TABLE_NAME_PERSON_MEMORY);
GeneralProcessUtils.getRecordByFieldOrElseThrow("auditUser", "name", userName);
QRecord auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId1);
assertEquals("Test Audit", auditRecord.getValueString("message"));
List<QRecord> auditDetails = GeneralProcessUtils.getRecordListByField(null, "auditDetail", "auditId", auditRecord.getValue("id"));
List<QRecord> auditDetails = GeneralProcessUtils.getRecordListByField("auditDetail", "auditId", auditRecord.getValue("id"));
assertEquals(2, auditDetails.size());
assertThat(auditDetails).anyMatch(r -> r.getValueString("message").equals("Detail1"));
assertThat(auditDetails).anyMatch(r -> r.getValueString("message").equals("Detail2"));
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId2);
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId2);
assertEquals("Test Another Audit", auditRecord.getValueString("message"));
assertEquals(47, auditRecord.getValueInteger(TestUtils.SECURITY_KEY_TYPE_STORE));
auditDetails = GeneralProcessUtils.getRecordListByField(null, "auditDetail", "auditId", auditRecord.getValue("id"));
auditDetails = GeneralProcessUtils.getRecordListByField("auditDetail", "auditId", auditRecord.getValue("id"));
assertEquals(0, auditDetails.size());
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow(null, "audit", "recordId", recordId3);
auditRecord = GeneralProcessUtils.getRecordByFieldOrElseThrow("audit", "recordId", recordId3);
assertEquals("Audit 3", auditRecord.getValueString("message"));
assertEquals(42, auditRecord.getValueInteger(TestUtils.SECURITY_KEY_TYPE_STORE));
auditDetails = GeneralProcessUtils.getRecordListByField(null, "auditDetail", "auditId", auditRecord.getValue("id"));
auditDetails = GeneralProcessUtils.getRecordListByField("auditDetail", "auditId", auditRecord.getValue("id"));
assertEquals(1, auditDetails.size());
assertThat(auditDetails).anyMatch(r -> r.getValueString("message").equals("Detail3"));
}

View File

@ -111,7 +111,7 @@ class MergeDuplicatesProcessTest extends BaseTest
/////////////////////////////////////////////////
// make sure records 4 and 5 have been deleted //
/////////////////////////////////////////////////
Map<Serializable, QRecord> personMap = GeneralProcessUtils.loadTableToMap(runProcessInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "id");
Map<Serializable, QRecord> personMap = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_PERSON_MEMORY, "id");
assertEquals(5, personMap.size());
assertNull(personMap.get(4));
assertNull(personMap.get(5));
@ -124,7 +124,7 @@ class MergeDuplicatesProcessTest extends BaseTest
/////////////////////////////////////////////////////////////////////////////
// make sure the shapes corresponding to records 4 and 5 have been deleted //
/////////////////////////////////////////////////////////////////////////////
Map<Serializable, QRecord> shapesMap = GeneralProcessUtils.loadTableToMap(runProcessInput, TestUtils.TABLE_NAME_SHAPE, "id");
Map<Serializable, QRecord> shapesMap = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_SHAPE, "id");
assertEquals(5, shapesMap.size());
assertNull(shapesMap.get(4));
assertNull(shapesMap.get(5));

View File

@ -94,7 +94,7 @@ class TableSyncProcessTest extends BaseTest
// make sure the record referencing 3 has had its name updated //
// and the one referencing 5 stayed the same //
/////////////////////////////////////////////////////////////////
Map<Serializable, QRecord> syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(runProcessInput, TABLE_NAME_PEOPLE_SYNC, "sourcePersonId");
Map<Serializable, QRecord> syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(TABLE_NAME_PEOPLE_SYNC, "sourcePersonId");
assertEquals("Tyler", syncPersonsBySourceId.get(3).getValueString("firstName"));
assertEquals("Homer", syncPersonsBySourceId.get(5).getValueString("firstName"));
}
@ -177,7 +177,7 @@ class TableSyncProcessTest extends BaseTest
// make sure the record referencing 3 has had its name updated //
// and the one referencing 5 stayed the same //
/////////////////////////////////////////////////////////////////
Map<Serializable, QRecord> syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(runProcessInput, TABLE_NAME_PEOPLE_SYNC, "sourcePersonId");
Map<Serializable, QRecord> syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(TABLE_NAME_PEOPLE_SYNC, "sourcePersonId");
assertEquals("Tyler", syncPersonsBySourceId.get(3).getValueString("firstName"));
assertEquals("Homer", syncPersonsBySourceId.get(5).getValueString("firstName"));
}

View File

@ -90,7 +90,7 @@ class GeneralProcessUtilsTest extends BaseTest
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Map<Serializable, QRecord> foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryInput, queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id");
Map<Serializable, QRecord> foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id");
assertEquals(2, foreignRecordMap.size());
assertEquals(1, foreignRecordMap.get(1).getValueInteger("id"));
@ -118,7 +118,7 @@ class GeneralProcessUtilsTest extends BaseTest
QueryOutput queryOutput = new QueryAction().execute(queryInput);
QQueryFilter additionalFilter = new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.EQUALS, "Circle"));
Map<Serializable, QRecord> foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryInput, queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id", additionalFilter);
Map<Serializable, QRecord> foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id", additionalFilter);
assertEquals(1, foreignRecordMap.size());
assertEquals(3, foreignRecordMap.get(3).getValueInteger("id"));
@ -145,7 +145,7 @@ class GeneralProcessUtilsTest extends BaseTest
queryInput.setTableName(TestUtils.TABLE_NAME_SHAPE);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
ListingHash<Serializable, QRecord> foreignRecordListingHashMap = GeneralProcessUtils.getForeignRecordListingHashMap(queryInput, queryOutput.getRecords(), "id", TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId");
ListingHash<Serializable, QRecord> foreignRecordListingHashMap = GeneralProcessUtils.getForeignRecordListingHashMap(queryOutput.getRecords(), "id", TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId");
assertEquals(2, foreignRecordListingHashMap.size());
@ -176,7 +176,7 @@ class GeneralProcessUtilsTest extends BaseTest
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
GeneralProcessUtils.addForeignRecordsToRecordList(queryInput, queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id");
GeneralProcessUtils.addForeignRecordsToRecordList(queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id");
for(QRecord record : queryOutput.getRecords())
{
@ -205,7 +205,7 @@ class GeneralProcessUtilsTest extends BaseTest
queryInput.setTableName(TestUtils.TABLE_NAME_SHAPE);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
GeneralProcessUtils.addForeignRecordsListToRecordList(queryInput, queryOutput.getRecords(), "id", TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId");
GeneralProcessUtils.addForeignRecordsListToRecordList(queryOutput.getRecords(), "id", TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId");
for(QRecord record : queryOutput.getRecords())
{
@ -246,7 +246,7 @@ class GeneralProcessUtilsTest extends BaseTest
));
QueryInput queryInput = new QueryInput();
List<QRecord> records = GeneralProcessUtils.getRecordListByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId", 3);
List<QRecord> records = GeneralProcessUtils.getRecordListByField(TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId", 3);
assertEquals(2, records.size());
assertTrue(records.stream().anyMatch(r -> r.getValue("id").equals(1)));
assertTrue(records.stream().anyMatch(r -> r.getValue("id").equals(2)));
@ -270,11 +270,11 @@ class GeneralProcessUtilsTest extends BaseTest
));
QueryInput queryInput = new QueryInput();
Optional<QRecord> record = GeneralProcessUtils.getRecordByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "James");
Optional<QRecord> record = GeneralProcessUtils.getRecordByField(TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "James");
assertTrue(record.isPresent());
assertEquals(2, record.get().getValueInteger("id"));
record = GeneralProcessUtils.getRecordByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "Bobby");
record = GeneralProcessUtils.getRecordByField(TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "Bobby");
assertFalse(record.isPresent());
}
@ -295,7 +295,7 @@ class GeneralProcessUtilsTest extends BaseTest
));
QueryInput queryInput = new QueryInput();
List<QRecord> records = GeneralProcessUtils.loadTable(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY);
List<QRecord> records = GeneralProcessUtils.loadTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
assertEquals(3, records.size());
}
@ -316,17 +316,17 @@ class GeneralProcessUtilsTest extends BaseTest
));
QueryInput queryInput = new QueryInput();
Map<Serializable, QRecord> recordMapById = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "id");
Map<Serializable, QRecord> recordMapById = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_PERSON_MEMORY, "id");
assertEquals(3, recordMapById.size());
assertEquals("Darin", recordMapById.get(1).getValueString("firstName"));
assertEquals("James", recordMapById.get(2).getValueString("firstName"));
Map<Serializable, QRecord> recordMapByFirstName = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName");
Map<Serializable, QRecord> recordMapByFirstName = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName");
assertEquals(3, recordMapByFirstName.size());
assertEquals(1, recordMapByFirstName.get("Darin").getValueInteger("id"));
assertEquals(3, recordMapByFirstName.get("Tim").getValueInteger("id"));
Map<String, QRecord> recordMapByFirstNameAsString = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, String.class, "firstName");
Map<String, QRecord> recordMapByFirstNameAsString = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_PERSON_MEMORY, String.class, "firstName");
assertEquals(3, recordMapByFirstName.size());
assertEquals(1, recordMapByFirstName.get("Darin").getValueInteger("id"));
assertEquals(3, recordMapByFirstName.get("Tim").getValueInteger("id"));
@ -349,7 +349,7 @@ class GeneralProcessUtilsTest extends BaseTest
));
QueryInput queryInput = new QueryInput();
ListingHash<Serializable, QRecord> map = GeneralProcessUtils.loadTableToListingHash(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName");
ListingHash<Serializable, QRecord> map = GeneralProcessUtils.loadTableToListingHash(TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName");
assertEquals(2, map.size());
assertEquals(1, map.get("Darin").size());
assertEquals(2, map.get("James").size());
@ -366,8 +366,8 @@ class GeneralProcessUtilsTest extends BaseTest
QInstance instance = QContext.getQInstance();
TestUtils.insertDefaultShapes(instance);
assertNotNull(GeneralProcessUtils.getRecordByFieldOrElseThrow(new AbstractActionInput(), TestUtils.TABLE_NAME_SHAPE, "name", "Triangle"));
assertThrows(QException.class, () -> GeneralProcessUtils.getRecordByFieldOrElseThrow(new AbstractActionInput(), TestUtils.TABLE_NAME_SHAPE, "name", "notAShape"));
assertNotNull(GeneralProcessUtils.getRecordByFieldOrElseThrow(TestUtils.TABLE_NAME_SHAPE, "name", "Triangle"));
assertThrows(QException.class, () -> GeneralProcessUtils.getRecordByFieldOrElseThrow(TestUtils.TABLE_NAME_SHAPE, "name", "notAShape"));
}
@ -382,10 +382,10 @@ class GeneralProcessUtilsTest extends BaseTest
TestUtils.insertDefaultShapes(instance);
AbstractActionInput actionInput = new AbstractActionInput();
assertTrue(GeneralProcessUtils.getRecordByPrimaryKey(actionInput, TestUtils.TABLE_NAME_SHAPE, 1).isPresent());
assertFalse(GeneralProcessUtils.getRecordByPrimaryKey(actionInput, TestUtils.TABLE_NAME_SHAPE, -1).isPresent());
assertNotNull(GeneralProcessUtils.getRecordByPrimaryKeyOrElseThrow(actionInput, TestUtils.TABLE_NAME_SHAPE, 1));
assertThrows(QException.class, () -> GeneralProcessUtils.getRecordByPrimaryKeyOrElseThrow(actionInput, TestUtils.TABLE_NAME_SHAPE, -1));
assertTrue(GeneralProcessUtils.getRecordByPrimaryKey(TestUtils.TABLE_NAME_SHAPE, 1).isPresent());
assertFalse(GeneralProcessUtils.getRecordByPrimaryKey(TestUtils.TABLE_NAME_SHAPE, -1).isPresent());
assertNotNull(GeneralProcessUtils.getRecordByPrimaryKeyOrElseThrow(TestUtils.TABLE_NAME_SHAPE, 1));
assertThrows(QException.class, () -> GeneralProcessUtils.getRecordByPrimaryKeyOrElseThrow(TestUtils.TABLE_NAME_SHAPE, -1));
}
@ -400,9 +400,9 @@ class GeneralProcessUtilsTest extends BaseTest
TestUtils.insertDefaultShapes(instance);
AbstractActionInput actionInput = new AbstractActionInput();
assertEquals(3, GeneralProcessUtils.count(actionInput, TestUtils.TABLE_NAME_SHAPE, null));
assertEquals(1, GeneralProcessUtils.count(actionInput, TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 2))));
assertEquals(0, GeneralProcessUtils.count(actionInput, TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.IS_BLANK))));
assertEquals(3, GeneralProcessUtils.count(TestUtils.TABLE_NAME_SHAPE, null));
assertEquals(1, GeneralProcessUtils.count(TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 2))));
assertEquals(0, GeneralProcessUtils.count(TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.IS_BLANK))));
}
@ -417,8 +417,8 @@ class GeneralProcessUtilsTest extends BaseTest
TestUtils.insertDefaultShapes(instance);
AbstractActionInput actionInput = new AbstractActionInput();
assertEquals("Triangle", GeneralProcessUtils.getEntityByField(actionInput, TestUtils.TABLE_NAME_SHAPE, "name", "Triangle", Shape.class).get().getName());
assertFalse(GeneralProcessUtils.getEntityByField(actionInput, TestUtils.TABLE_NAME_SHAPE, "name", "notAShape", Shape.class).isPresent());
assertEquals("Triangle", GeneralProcessUtils.getEntityByField(TestUtils.TABLE_NAME_SHAPE, "name", "Triangle", Shape.class).get().getName());
assertFalse(GeneralProcessUtils.getEntityByField(TestUtils.TABLE_NAME_SHAPE, "name", "notAShape", Shape.class).isPresent());
}
@ -433,7 +433,7 @@ class GeneralProcessUtilsTest extends BaseTest
TestUtils.insertDefaultShapes(instance);
AbstractActionInput actionInput = new AbstractActionInput();
List<Shape> shapes = GeneralProcessUtils.loadTable(actionInput, TestUtils.TABLE_NAME_SHAPE, Shape.class);
List<Shape> shapes = GeneralProcessUtils.loadTable(TestUtils.TABLE_NAME_SHAPE, Shape.class);
assertEquals(3, shapes.size());
assertTrue(shapes.get(0) instanceof Shape);
}
@ -450,7 +450,7 @@ class GeneralProcessUtilsTest extends BaseTest
TestUtils.insertDefaultShapes(instance);
AbstractActionInput actionInput = new AbstractActionInput();
Map<Serializable, Shape> map = GeneralProcessUtils.loadTableToMap(actionInput, TestUtils.TABLE_NAME_SHAPE, "id", Shape.class);
Map<Serializable, Shape> map = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_SHAPE, "id", Shape.class);
assertEquals(3, map.size());
assertTrue(map.get(1) instanceof Shape);
}

View File

@ -75,6 +75,7 @@ import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
@ -105,6 +106,11 @@ public class BaseAPIActionUtil
public enum UpdateHttpMethod
{PUT, POST}
/*******************************************************************************
**
*******************************************************************************/
@ -334,8 +340,9 @@ public class BaseAPIActionUtil
{
try
{
String url = buildTableUrl(table);
HttpPut request = new HttpPut(url);
String paramString = buildQueryStringForUpdate(table, recordList);
String url = buildTableUrl(table) + paramString;
HttpEntityEnclosingRequestBase request = getUpdateMethod().equals(UpdateHttpMethod.PUT) ? new HttpPut(url) : new HttpPost(url);
request.setEntity(recordsToEntity(table, recordList));
QHttpResponse response = makeRequest(table, request);
@ -560,14 +567,24 @@ public class BaseAPIActionUtil
/*******************************************************************************
** method to build up a query string for updates based on a given QFilter object
**
*******************************************************************************/
protected String buildQueryStringForUpdate(QTableMetaData table, List<QRecord> recordList) throws QException
{
return ("");
}
/*******************************************************************************
** method to build up a query string based on a given QFilter object
**
*******************************************************************************/
protected String buildQueryStringForGet(QQueryFilter filter, Integer limit, Integer skip, Map<String, QFieldMetaData> fields) throws QException
{
// todo: reasonable default action
return (null);
return ("");
}
@ -1225,4 +1242,14 @@ public class BaseAPIActionUtil
{
return (20);
}
/*******************************************************************************
**
*******************************************************************************/
protected UpdateHttpMethod getUpdateMethod()
{
return (UpdateHttpMethod.PUT);
}
}

View File

@ -52,7 +52,7 @@ class FilesystemBackendMetaDataTest
System.out.println(JsonUtils.prettyPrint(json));
System.out.println(json);
String expectToContain = """
"local-filesystem":{"basePath":"/tmp/filesystem-tests/0","backendType":"filesystem","name":"local-filesystem"}""";
"local-filesystem":{"basePath":"/tmp/filesystem-tests/0","backendType":"filesystem","name":"local-filesystem","usesVariants":false}""";
assertTrue(json.contains(expectToContain));
}

View File

@ -51,7 +51,7 @@ class S3BackendMetaDataTest
System.out.println(JsonUtils.prettyPrint(json));
System.out.println(json);
String expectToContain = """
{"s3":{"bucketName":"localstack-test-bucket","basePath":"test-files","backendType":"s3","name":"s3"}""";
{"s3":{"bucketName":"localstack-test-bucket","basePath":"test-files","backendType":"s3","name":"s3","usesVariants":false}""";
assertTrue(json.contains(expectToContain));
}

View File

@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAc
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
@ -1634,6 +1635,11 @@ public class QJavalinImplementation
statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.NOT_FOUND); // 404
respondWithError(context, statusCode, userFacingException.getMessage());
}
else if(userFacingException instanceof QBadRequestException)
{
statusCode = Objects.requireNonNullElse(statusCode, HttpStatus.Code.BAD_REQUEST); // 400
respondWithError(context, statusCode, userFacingException.getMessage());
}
else
{
LOG.info("User-facing exception", e);

View File

@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
@ -280,7 +281,13 @@ public class QJavalinProcessHandler
QJavalinImplementation.setupSession(context, new AbstractActionInput());
// todo context.contentType(reportFormat.getMimeType());
context.header("Content-Disposition", "filename=" + context.pathParam("file"));
context.result(new FileInputStream(context.queryParam("filePath")));
String filePath = context.queryParam("filePath");
if(filePath == null)
{
throw (new QBadRequestException("A filePath was not provided."));
}
context.result(new FileInputStream(filePath));
}
catch(Exception e)
{

View File

@ -143,7 +143,7 @@ public class QJavalinScriptsHandler
QTableMetaData scriptTable = QContext.getQInstance().getTable(Script.TABLE_NAME);
if(scriptTypeTable != null && scriptTable != null && scriptRevisionTable != null)
{
Map<Serializable, QRecord> scriptTypeMap = GeneralProcessUtils.loadTableToMap(getInput, ScriptType.TABLE_NAME, "id");
Map<Serializable, QRecord> scriptTypeMap = GeneralProcessUtils.loadTableToMap(ScriptType.TABLE_NAME, "id");
///////////////////////////////////////////////////////
// process each associated script type for the table //
@ -259,7 +259,7 @@ public class QJavalinScriptsHandler
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
{
GeneralProcessUtils.addForeignRecordsListToRecordList(queryInput, queryOutput.getRecords(), "id", "scriptLogLine", "scriptLogId");
GeneralProcessUtils.addForeignRecordsListToRecordList(queryOutput.getRecords(), "id", "scriptLogLine", "scriptLogId");
}
Map<String, Serializable> rs = new HashMap<>();

View File

@ -84,6 +84,25 @@ class QJavalinImplementationTest extends QJavalinTestBase
/*******************************************************************************
** test getting serverInfo
**
*******************************************************************************/
@Test
public void test_serverInfo()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/serverInfo").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("startTimeMillis"));
assertTrue(jsonObject.has("startTimeHuman"));
assertTrue(jsonObject.has("uptimeHuman"));
assertTrue(jsonObject.has("uptimeMillis"));
}
/*******************************************************************************
** test the table-level meta-data endpoint
**

View File

@ -560,6 +560,34 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test calling download file endpoint
**
*******************************************************************************/
@Test
public void test_downloadFile()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/download/myTestFile.txt?filePath=/dev/null").asString();
assertEquals(200, response.getStatus());
assertEquals("OK", response.getStatusText());
}
/*******************************************************************************
** test calling download file with missing filePath
**
*******************************************************************************/
@Test
public void test_downloadFileMissingFilePath()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/download/myTestFile.txt").asString();
assertEquals(400, response.getStatus());
assertTrue(response.getBody().contains("A filePath was not provided"));
}
/*******************************************************************************
** test calling for possibleValue
**