diff --git a/pom.xml b/pom.xml index e915ea19..4623b96d 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ true true true - 0.75 + 0.80 0.95 none diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java index 8a2586ec..67cbecee 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java @@ -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 // diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java index 5ba0002b..49e62c5f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java @@ -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); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QBadRequestException.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QBadRequestException.java new file mode 100644 index 00000000..164f6785 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QBadRequestException.java @@ -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 . + */ + +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); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 6a55e80a..3bc59276 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -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); + } + } + }); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java index 7b108d3a..ef534824 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java @@ -43,6 +43,9 @@ public class QBackendMetaData private String name; private String backendType; + private Boolean usesVariants = false; + private String variantOptionsTableName; + private Set enabledCapabilities = new HashSet<>(); private Set 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); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java index 23f5b992..e99e5db0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java @@ -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 ** diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/scheduleing/QScheduleMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/scheduleing/QScheduleMetaData.java index f5a7dba3..03a66de1 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/scheduleing/QScheduleMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/scheduleing/QScheduleMetaData.java @@ -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); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java index e00103c7..b6e79026 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java @@ -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); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java index 4649efbd..ec911a7f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java @@ -47,8 +47,9 @@ public class QSession implements Serializable private QUser user; private String uuid; - private Map> securityKeyValues; private Set permissions; + private Map> securityKeyValues; + private Map backendVariants; // implementation-specific custom values private Map values; @@ -466,4 +467,35 @@ public class QSession implements Serializable return (this); } + + + /******************************************************************************* + ** Getter for backendVariants + *******************************************************************************/ + public Map getBackendVariants() + { + return (this.backendVariants); + } + + + + /******************************************************************************* + ** Setter for backendVariants + *******************************************************************************/ + public void setBackendVariants(Map backendVariants) + { + this.backendVariants = backendVariants; + } + + + + /******************************************************************************* + ** Fluent setter for backendVariants + *******************************************************************************/ + public QSession withBackendVariants(Map backendVariants) + { + this.backendVariants = backendVariants; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/AbstractTableSyncTransformStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/AbstractTableSyncTransformStep.java index 367bea56..6221787d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/AbstractTableSyncTransformStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/AbstractTableSyncTransformStep.java @@ -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 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 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 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 getExistingRecordsByForeignKey(RunBackendStepInput runBackendStepInput, String destinationTableForeignKeyField, String destinationTableName, List sourceKeyList) throws QException + { + Map 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 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); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java index 7c7afc3a..75860912 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java @@ -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 getForeignRecordMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException + public static Map getForeignRecordMap(List 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 getForeignRecordMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName, QQueryFilter additionalFilter) throws QException + public static Map getForeignRecordMap(List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName, QQueryFilter additionalFilter) throws QException { Map 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 getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException + public static ListingHash getForeignRecordListingHashMap(List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException { ListingHash 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 sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException + public static void addForeignRecordsToRecordList(List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException { - Map foreignRecordMap = getForeignRecordMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName); + Map 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 sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException + public static void addForeignRecordsListToRecordList(List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException { - ListingHash foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName); + ListingHash foreignRecordMap = getForeignRecordListingHashMap(sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName); for(QRecord sourceRecord : sourceRecords) { List 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 getRecordListByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException + public static List 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 getRecordByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException + public static Optional 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 Optional getEntityByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue, Class entityClass) throws QException + public static Optional getEntityByField(String tableName, String fieldName, Serializable fieldValue, Class entityClass) throws QException { - Optional optionalQRecord = getRecordByField(parentActionInput, tableName, fieldName, fieldValue); + Optional 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 getRecordByPrimaryKey(AbstractActionInput parentActionInput, String tableName, Serializable value) throws QException + public static Optional 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 loadTable(AbstractActionInput parentActionInput, String tableName) throws QException + public static List 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 List loadTable(AbstractActionInput parentActionInput, String tableName, Class entityClass) throws QException + public static List loadTable(String tableName, Class 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 loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException + public static Map 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 loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName, QQueryFilter filter) throws QException + public static Map 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 Map loadTableToMap(AbstractActionInput parentActionInput, String tableName, Class keyType, String keyFieldName) throws QException + public static Map loadTableToMap(String tableName, Class 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 Map loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName, Class entityClass) throws QException + public static Map loadTableToMap(String tableName, String keyFieldName, Class 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 loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException + public static ListingHash 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); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java index 02f4e84f..527b7bfc 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java @@ -83,7 +83,7 @@ public class RecordLookupHelper { if(disallowedOneOffLookups.isEmpty() || !disallowedOneOffLookups.contains(Pair.of(tableName, keyFieldName))) { - Optional optRecord = GeneralProcessUtils.getRecordByField(null, tableName, keyFieldName, key); + Optional 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 recordMap = GeneralProcessUtils.loadTableToMap(null, tableName, keyFieldName); + Map recordMap = GeneralProcessUtils.loadTableToMap(tableName, keyFieldName); recordMaps.put(mapKey, recordMap); preloadedKeys.add(mapKey); } @@ -126,7 +126,7 @@ public class RecordLookupHelper { String mapKey = tableName + "." + keyFieldName; Map 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 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) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/ScheduleManager.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/ScheduleManager.java index 4bdd250f..b8e91b99 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/ScheduleManager.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/ScheduleManager.java @@ -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,13 +141,71 @@ 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 getBackendVariantFilteredRecords(QProcessMetaData processMetaData) + { + List 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,7 +317,7 @@ public class ScheduleManager /******************************************************************************* ** *******************************************************************************/ - private void startProcess(QProcessMetaData process) + private void startProcess(QProcessMetaData process, Map backendVariantData) { Runnable runProcess = () -> { @@ -257,18 +325,31 @@ public class ScheduleManager try { - 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); + if(process.getSchedule().getBackendVariant() == null || QScheduleMetaData.RunStrategy.PARALLEL.equals(process.getSchedule().getVariantRunStrategy())) + { + QContext.init(qInstance, sessionSupplier.get()); + 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 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); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/audits/AuditActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/audits/AuditActionTest.java index dd8f10d3..40b05461 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/audits/AuditActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/audits/AuditActionTest.java @@ -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 auditRecord = GeneralProcessUtils.getRecordByField(null, "audit", "recordId", recordId); + Optional 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,28 +173,28 @@ 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 auditDetails = GeneralProcessUtils.getRecordListByField(null, "auditDetail", "auditId", auditRecord.getValue("id")); + List 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")); } -} \ No newline at end of file +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/mergeduplicates/MergeDuplicatesProcessTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/mergeduplicates/MergeDuplicatesProcessTest.java index ffc49016..3ea9be62 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/mergeduplicates/MergeDuplicatesProcessTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/mergeduplicates/MergeDuplicatesProcessTest.java @@ -111,7 +111,7 @@ class MergeDuplicatesProcessTest extends BaseTest ///////////////////////////////////////////////// // make sure records 4 and 5 have been deleted // ///////////////////////////////////////////////// - Map personMap = GeneralProcessUtils.loadTableToMap(runProcessInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "id"); + Map 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 shapesMap = GeneralProcessUtils.loadTableToMap(runProcessInput, TestUtils.TABLE_NAME_SHAPE, "id"); + Map shapesMap = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_SHAPE, "id"); assertEquals(5, shapesMap.size()); assertNull(shapesMap.get(4)); assertNull(shapesMap.get(5)); @@ -223,4 +223,4 @@ class MergeDuplicatesProcessTest extends BaseTest } -} \ No newline at end of file +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/TableSyncProcessTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/TableSyncProcessTest.java index 22d02a26..220b5cc5 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/TableSyncProcessTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/tablesync/TableSyncProcessTest.java @@ -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 syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(runProcessInput, TABLE_NAME_PEOPLE_SYNC, "sourcePersonId"); + Map 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 syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(runProcessInput, TABLE_NAME_PEOPLE_SYNC, "sourcePersonId"); + Map syncPersonsBySourceId = GeneralProcessUtils.loadTableToMap(TABLE_NAME_PEOPLE_SYNC, "sourcePersonId"); assertEquals("Tyler", syncPersonsBySourceId.get(3).getValueString("firstName")); assertEquals("Homer", syncPersonsBySourceId.get(5).getValueString("firstName")); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java index 38616e02..aad2d4ca 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java @@ -90,7 +90,7 @@ class GeneralProcessUtilsTest extends BaseTest queryInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); QueryOutput queryOutput = new QueryAction().execute(queryInput); - Map foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryInput, queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id"); + Map 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 foreignRecordMap = GeneralProcessUtils.getForeignRecordMap(queryInput, queryOutput.getRecords(), "favoriteShapeId", TestUtils.TABLE_NAME_SHAPE, "id", additionalFilter); + Map 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 foreignRecordListingHashMap = GeneralProcessUtils.getForeignRecordListingHashMap(queryInput, queryOutput.getRecords(), "id", TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId"); + ListingHash 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 records = GeneralProcessUtils.getRecordListByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "favoriteShapeId", 3); + List 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 record = GeneralProcessUtils.getRecordByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "James"); + Optional 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 records = GeneralProcessUtils.loadTable(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY); + List 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 recordMapById = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "id"); + Map 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 recordMapByFirstName = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName"); + Map 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 recordMapByFirstNameAsString = GeneralProcessUtils.loadTableToMap(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, String.class, "firstName"); + Map 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 map = GeneralProcessUtils.loadTableToListingHash(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName"); + ListingHash 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 shapes = GeneralProcessUtils.loadTable(actionInput, TestUtils.TABLE_NAME_SHAPE, Shape.class); + List 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 map = GeneralProcessUtils.loadTableToMap(actionInput, TestUtils.TABLE_NAME_SHAPE, "id", Shape.class); + Map map = GeneralProcessUtils.loadTableToMap(TestUtils.TABLE_NAME_SHAPE, "id", Shape.class); assertEquals(3, map.size()); assertTrue(map.get(1) instanceof Shape); } @@ -473,4 +473,4 @@ class GeneralProcessUtilsTest extends BaseTest assertEquals("round", shapes.get(0).getName()); } -} \ No newline at end of file +} diff --git a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java index 3c1197b9..7f793e71 100644 --- a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java +++ b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java @@ -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 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 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); + } } diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaDataTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaDataTest.java index e67936d5..90117f7a 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaDataTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaDataTest.java @@ -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)); } diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaDataTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaDataTest.java index 39103602..3f7e764e 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaDataTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaDataTest.java @@ -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)); } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 329c6e51..297cffcc 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -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); diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index 84176d7a..941c9f24 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -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) { diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinScriptsHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinScriptsHandler.java index 57c730ee..1a6bed72 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinScriptsHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinScriptsHandler.java @@ -143,7 +143,7 @@ public class QJavalinScriptsHandler QTableMetaData scriptTable = QContext.getQInstance().getTable(Script.TABLE_NAME); if(scriptTypeTable != null && scriptTable != null && scriptRevisionTable != null) { - Map scriptTypeMap = GeneralProcessUtils.loadTableToMap(getInput, ScriptType.TABLE_NAME, "id"); + Map 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 rs = new HashMap<>(); diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java index 77af26c3..9e15f2fb 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java @@ -84,6 +84,25 @@ class QJavalinImplementationTest extends QJavalinTestBase + /******************************************************************************* + ** test getting serverInfo + ** + *******************************************************************************/ + @Test + public void test_serverInfo() + { + HttpResponse 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 ** diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java index 162dacc9..cd25dee2 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandlerTest.java @@ -560,6 +560,34 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase + /******************************************************************************* + ** test calling download file endpoint + ** + *******************************************************************************/ + @Test + public void test_downloadFile() + { + HttpResponse 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 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 ** @@ -576,4 +604,4 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id")); assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label")); } -} \ No newline at end of file +}