Merge remote-tracking branch 'origin/integration/sprint-28' into feature/CTLE-503-optimization-weather-api-data

# Conflicts:
#	qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
#	qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java
#	qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java
#	qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/QRecordApiAdapter.java
This commit is contained in:
2023-07-03 15:38:55 -05:00
104 changed files with 7092 additions and 516 deletions

View File

@ -131,6 +131,16 @@ public class ApiImplementation
/*******************************************************************************
** Allow tests (that manipulate meta-data) to clear field caches.
*******************************************************************************/
public static void clearCaches()
{
tableApiNameMap.clear();
}
/*******************************************************************************
**
*******************************************************************************/
@ -281,6 +291,10 @@ public class ApiImplementation
try
{
////////////////////////////////////////////////////////////////////////////////////////////////
// todo - deal with removed fields; fields w/ custom value mappers (need new method(s) there) //
////////////////////////////////////////////////////////////////////////////////////////////////
QFieldMetaData field = table.getField(name);
for(String value : values)
{

View File

@ -48,6 +48,7 @@ import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessMetaData;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessOutputInterface;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessUtils;
import com.kingsrook.qqq.api.model.metadata.tables.ApiAssociationMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.Components;
@ -428,7 +429,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
//////////////////////////////////////
// build the schemas for this table //
//////////////////////////////////////
Schema tableSchema = buildTableSchema(apiInstanceMetaData, table, tableApiFields);
Schema tableSchema = buildTableSchema(apiInstanceMetaData, version, table, tableApiFields);
componentSchemas.put(tableApiName, tableSchema);
//////////////////////////////////////////////////////////////////////////////
@ -1188,7 +1189,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withVersion(version)
.withApiName(apiName)).getFields();
componentSchemas.put(tableApiName, buildTableSchema(apiInstanceMetaData, table, tableApiFields));
componentSchemas.put(tableApiName, buildTableSchema(apiInstanceMetaData, version, table, tableApiFields));
addedAny = true;
break;
}
@ -1202,7 +1203,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private Schema buildTableSchema(ApiInstanceMetaData apiInstanceMetaData, QTableMetaData table, List<QFieldMetaData> tableApiFields)
private Schema buildTableSchema(ApiInstanceMetaData apiInstanceMetaData, String version, QTableMetaData table, List<QFieldMetaData> tableApiFields)
{
LinkedHashMap<String, Schema> tableFields = new LinkedHashMap<>();
Schema tableSchema = new Schema()
@ -1225,7 +1226,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
//////////////////////////////////
// recursively add associations //
//////////////////////////////////
addAssociations(apiInstanceMetaData.getName(), table, tableSchema);
addAssociations(apiInstanceMetaData.getName(), version, table, tableSchema);
return (tableSchema);
}
@ -1367,8 +1368,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private void addAssociations(String apiName, QTableMetaData table, Schema tableSchema)
private void addAssociations(String apiName, String version, QTableMetaData table, Schema tableSchema)
{
ApiTableMetaData thisApiTableMetaData = ObjectUtils.tryElse(() -> ApiTableMetaDataContainer.of(table).getApiTableMetaData(apiName), new ApiTableMetaData());
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
String associatedTableName = association.getAssociatedTableName();
@ -1376,6 +1379,23 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
ApiTableMetaData associatedApiTableMetaData = ObjectUtils.tryElse(() -> ApiTableMetaDataContainer.of(associatedTable).getApiTableMetaData(apiName), new ApiTableMetaData());
String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName;
ApiAssociationMetaData apiAssociationMetaData = thisApiTableMetaData.getApiAssociationMetaData().get(association.getName());
if(apiAssociationMetaData != null)
{
if(BooleanUtils.isTrue(apiAssociationMetaData.getIsExcluded()))
{
LOG.debug("Omitting table [" + table.getName() + "] association [" + association.getName() + "] because it is marked as excluded.");
continue;
}
APIVersionRange apiVersionRange = apiAssociationMetaData.getApiVersionRange();
if(!apiVersionRange.includes(new APIVersion(version)))
{
LOG.debug("Omitting table [" + table.getName() + "] association [" + association.getName() + "] because its api version range [" + apiVersionRange + "] does not include this version [" + version + "]");
continue;
}
}
neededTableSchemas.add(associatedTable.getName());
tableSchema.getProperties().put(association.getName(), new Schema()

View File

@ -31,9 +31,16 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.kingsrook.qqq.api.javalin.QBadRequestException;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.ApiFieldCustomValueMapper;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiAssociationMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -45,6 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.lang.BooleanUtils;
import org.json.JSONArray;
import org.json.JSONObject;
@ -61,6 +69,17 @@ public class QRecordApiAdapter
/*******************************************************************************
** Allow tests (that manipulate meta-data) to clear field caches.
*******************************************************************************/
public static void clearCaches()
{
fieldListCache.clear();
fieldMapCache.clear();
}
/*******************************************************************************
** Convert a QRecord to a map for the API
*******************************************************************************/
@ -87,6 +106,11 @@ public class QRecordApiAdapter
{
value = record.getValue(apiFieldMetaData.getReplacedByFieldName());
}
else if(apiFieldMetaData.getCustomValueMapper() != null)
{
ApiFieldCustomValueMapper customValueMapper = QCodeLoader.getAdHoc(ApiFieldCustomValueMapper.class, apiFieldMetaData.getCustomValueMapper());
value = customValueMapper.produceApiValue(record);
}
else
{
value = record.getValue(field.getName());
@ -107,6 +131,11 @@ public class QRecordApiAdapter
QTableMetaData table = QContext.getQInstance().getTable(tableName);
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
if(isAssociationOmitted(apiName, apiVersion, table, association))
{
continue;
}
ArrayList<Map<String, Serializable>> associationList = new ArrayList<>();
outputRecord.put(association.getName(), associationList);
@ -121,6 +150,31 @@ public class QRecordApiAdapter
/*******************************************************************************
**
*******************************************************************************/
private static boolean isAssociationOmitted(String apiName, String apiVersion, QTableMetaData table, Association association)
{
ApiTableMetaData thisApiTableMetaData = ObjectUtils.tryElse(() -> ApiTableMetaDataContainer.of(table).getApiTableMetaData(apiName), new ApiTableMetaData());
ApiAssociationMetaData apiAssociationMetaData = thisApiTableMetaData.getApiAssociationMetaData().get(association.getName());
if(apiAssociationMetaData != null)
{
if(BooleanUtils.isTrue(apiAssociationMetaData.getIsExcluded()))
{
return (true);
}
APIVersionRange apiVersionRange = apiAssociationMetaData.getApiVersionRange();
if(!apiVersionRange.includes(new APIVersion(apiVersion)))
{
return true;
}
}
return false;
}
/*******************************************************************************
**
*******************************************************************************/
@ -137,7 +191,10 @@ public class QRecordApiAdapter
QTableMetaData table = QContext.getQInstance().getTable(tableName);
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
associationMap.put(association.getName(), association);
if(!isAssociationOmitted(apiName, apiVersion, table, association))
{
associationMap.put(association.getName(), association);
}
}
//////////////////////////////////////////
@ -179,6 +236,11 @@ public class QRecordApiAdapter
{
qRecord.setValue(apiFieldMetaData.getReplacedByFieldName(), value);
}
else if(apiFieldMetaData.getCustomValueMapper() != null)
{
ApiFieldCustomValueMapper customValueMapper = QCodeLoader.getAdHoc(ApiFieldCustomValueMapper.class, apiFieldMetaData.getCustomValueMapper());
customValueMapper.consumeApiValue(qRecord, value, jsonObject);
}
else
{
qRecord.setValue(field.getName(), value);
@ -276,5 +338,4 @@ public class QRecordApiAdapter
{
}
}

View File

@ -0,0 +1,59 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.api.model.actions;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import org.json.JSONObject;
/*******************************************************************************
**
*******************************************************************************/
public abstract class ApiFieldCustomValueMapper
{
/*******************************************************************************
**
*******************************************************************************/
public Serializable produceApiValue(QRecord record)
{
/////////////////////
// null by default //
/////////////////////
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
public void consumeApiValue(QRecord record, Object value, JSONObject fullApiJsonObject)
{
/////////////////////
// noop by default //
/////////////////////
}
}

View File

@ -123,6 +123,11 @@ public class ApiInstanceMetaDataProvider
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
allVersions.addAll(apiInstanceMetaData.getPastVersions());
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
///////////////////////////////////////////////////////////////////////////////////////////////////////
// I think we don't want future-versions in this dropdown, I think... //
// grr, actually todo maybe we want this to be a table-backed enum, with past/present/future columns //
///////////////////////////////////////////////////////////////////////////////////////////////////////
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.api.model.metadata.fields;
import java.util.Map;
import com.kingsrook.qqq.api.model.openapi.Example;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -39,8 +40,9 @@ public class ApiFieldMetaData
private String apiFieldName;
private String description;
private Boolean isExcluded;
private String replacedByFieldName;
private Boolean isExcluded;
private String replacedByFieldName;
private QCodeReference customValueMapper;
private Example example;
private Map<String, Example> examples;
@ -313,4 +315,35 @@ public class ApiFieldMetaData
return (this);
}
/*******************************************************************************
** Getter for customValueMapper
*******************************************************************************/
public QCodeReference getCustomValueMapper()
{
return (this.customValueMapper);
}
/*******************************************************************************
** Setter for customValueMapper
*******************************************************************************/
public void setCustomValueMapper(QCodeReference customValueMapper)
{
this.customValueMapper = customValueMapper;
}
/*******************************************************************************
** Fluent setter for customValueMapper
*******************************************************************************/
public ApiFieldMetaData withCustomValueMapper(QCodeReference customValueMapper)
{
this.customValueMapper = customValueMapper;
return (this);
}
}

View File

@ -61,6 +61,23 @@ public class ApiFieldMetaDataContainer extends QSupplementalFieldMetaData
/*******************************************************************************
** either get the container attached to a field - or create a new one and attach
** it to the field, and return that.
*******************************************************************************/
public static ApiFieldMetaDataContainer ofOrWithNew(QFieldMetaData field)
{
ApiFieldMetaDataContainer apiFieldMetaDataContainer = (ApiFieldMetaDataContainer) field.getSupplementalMetaData(ApiSupplementType.NAME);
if(apiFieldMetaDataContainer == null)
{
apiFieldMetaDataContainer = new ApiFieldMetaDataContainer();
field.withSupplementalMetaData(apiFieldMetaDataContainer);
}
return (apiFieldMetaDataContainer);
}
/*******************************************************************************
** either get the container attached to a field - or a new one - note - the new
** one will NOT be attached to the field!!

View File

@ -0,0 +1,147 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.api.model.metadata.tables;
import com.kingsrook.qqq.api.model.APIVersionRange;
/*******************************************************************************
**
*******************************************************************************/
public class ApiAssociationMetaData
{
private String initialVersion;
private String finalVersion;
private Boolean isExcluded;
/*******************************************************************************
**
*******************************************************************************/
public APIVersionRange getApiVersionRange()
{
if(getInitialVersion() == null)
{
return APIVersionRange.none();
}
return (getFinalVersion() != null
? APIVersionRange.betweenAndIncluding(getInitialVersion(), getFinalVersion())
: APIVersionRange.afterAndIncluding(getInitialVersion()));
}
/*******************************************************************************
** Getter for initialVersion
*******************************************************************************/
public String getInitialVersion()
{
return (this.initialVersion);
}
/*******************************************************************************
** Setter for initialVersion
*******************************************************************************/
public void setInitialVersion(String initialVersion)
{
this.initialVersion = initialVersion;
}
/*******************************************************************************
** Fluent setter for initialVersion
*******************************************************************************/
public ApiAssociationMetaData withInitialVersion(String initialVersion)
{
this.initialVersion = initialVersion;
return (this);
}
/*******************************************************************************
** Getter for finalVersion
*******************************************************************************/
public String getFinalVersion()
{
return (this.finalVersion);
}
/*******************************************************************************
** Setter for finalVersion
*******************************************************************************/
public void setFinalVersion(String finalVersion)
{
this.finalVersion = finalVersion;
}
/*******************************************************************************
** Fluent setter for finalVersion
*******************************************************************************/
public ApiAssociationMetaData withFinalVersion(String finalVersion)
{
this.finalVersion = finalVersion;
return (this);
}
/*******************************************************************************
** Getter for isExcluded
*******************************************************************************/
public Boolean getIsExcluded()
{
return (this.isExcluded);
}
/*******************************************************************************
** Setter for isExcluded
*******************************************************************************/
public void setIsExcluded(Boolean isExcluded)
{
this.isExcluded = isExcluded;
}
/*******************************************************************************
** Fluent setter for isExcluded
*******************************************************************************/
public ApiAssociationMetaData withIsExcluded(Boolean isExcluded)
{
this.isExcluded = isExcluded;
return (this);
}
}

View File

@ -24,8 +24,10 @@ package com.kingsrook.qqq.api.model.metadata.tables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qqq.api.ApiSupplementType;
import com.kingsrook.qqq.api.model.APIVersionRange;
@ -53,6 +55,8 @@ public class ApiTableMetaData implements ApiOperation.EnabledOperationsProvider
private Set<ApiOperation> enabledOperations = new HashSet<>();
private Set<ApiOperation> disabledOperations = new HashSet<>();
private Map<String, ApiAssociationMetaData> apiAssociationMetaData = new HashMap<>();
/*******************************************************************************
@ -410,4 +414,50 @@ public class ApiTableMetaData implements ApiOperation.EnabledOperationsProvider
return (this);
}
/*******************************************************************************
** Getter for apiAssociationMetaData
*******************************************************************************/
public Map<String, ApiAssociationMetaData> getApiAssociationMetaData()
{
return (this.apiAssociationMetaData);
}
/*******************************************************************************
** Setter for apiAssociationMetaData
*******************************************************************************/
public void setApiAssociationMetaData(Map<String, ApiAssociationMetaData> apiAssociationMetaData)
{
this.apiAssociationMetaData = apiAssociationMetaData;
}
/*******************************************************************************
** Fluent setter for apiAssociationMetaData
*******************************************************************************/
public ApiTableMetaData withApiAssociationMetaData(Map<String, ApiAssociationMetaData> apiAssociationMetaData)
{
this.apiAssociationMetaData = apiAssociationMetaData;
return (this);
}
/*******************************************************************************
** Fluent setter for apiAssociationMetaData
*******************************************************************************/
public ApiTableMetaData withApiAssociationMetaData(String associationName, ApiAssociationMetaData apiAssociationMetaData)
{
if(this.apiAssociationMetaData == null)
{
this.apiAssociationMetaData = new HashMap<>();
}
this.apiAssociationMetaData.put(associationName, apiAssociationMetaData);
return (this);
}
}

View File

@ -59,6 +59,23 @@ public class ApiTableMetaDataContainer extends QSupplementalTableMetaData
/*******************************************************************************
** either get the container attached to a table - or create a new one and attach
** it to the table, and return that.
*******************************************************************************/
public static ApiTableMetaDataContainer ofOrWithNew(QTableMetaData table)
{
ApiTableMetaDataContainer apiTableMetaDataContainer = (ApiTableMetaDataContainer) table.getSupplementalMetaData(ApiSupplementType.NAME);
if(apiTableMetaDataContainer == null)
{
apiTableMetaDataContainer = new ApiTableMetaDataContainer();
table.withSupplementalMetaData(apiTableMetaDataContainer);
}
return (apiTableMetaDataContainer);
}
/*******************************************************************************
**
*******************************************************************************/
@ -100,6 +117,26 @@ public class ApiTableMetaDataContainer extends QSupplementalTableMetaData
/*******************************************************************************
** Getter for api
*******************************************************************************/
public ApiTableMetaData getOrWithNewApiTableMetaData(String apiName)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
if(!this.apis.containsKey(apiName))
{
this.apis.put(apiName, new ApiTableMetaData());
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/