mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 06:00:44 +00:00
Merge branch 'feature/sprint-9-support-updates' of github.com:Kingsrook/qqq into feature/sprint-9-support-updates
This commit is contained in:
@ -215,7 +215,7 @@ public class RunBackendStepAction
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof BackendStep backendStepCodeObject))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of FunctionBody"));
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of BackendStep"));
|
||||
}
|
||||
|
||||
backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput);
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
@ -45,14 +46,24 @@ public class QueryAction
|
||||
ActionHelper.validateSession(queryInput);
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
if (queryInput.getRecordPipe() == null)
|
||||
if(queryInput.getRecordPipe() == null)
|
||||
{
|
||||
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
|
||||
if(queryInput.getShouldGenerateDisplayValues())
|
||||
{
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
qValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
|
||||
}
|
||||
|
||||
if(queryInput.getShouldTranslatePossibleValues())
|
||||
{
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
|
||||
}
|
||||
}
|
||||
|
||||
return queryOutput;
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by user-defined code that serves as the backing
|
||||
** for a CUSTOM type possibleValueSource
|
||||
*******************************************************************************/
|
||||
public interface QCustomPossibleValueProvider
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<?> getPossibleValue(Serializable idValue);
|
||||
|
||||
// todo - get/search list of possible values
|
||||
|
||||
}
|
@ -0,0 +1,359 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Class responsible for looking up possible-values for fields/records and
|
||||
** make them into display values.
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueTranslator
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QPossibleValueTranslator.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
private final QSession session;
|
||||
|
||||
// top-level keys are pvsNames (not table names)
|
||||
// 2nd-level keys are pkey values from the PVS table
|
||||
private Map<String, Map<Serializable, String>> possibleValueCache;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueTranslator(QInstance qInstance, QSession session)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.session = session;
|
||||
|
||||
this.possibleValueCache = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, translate their possible values (populating their display values)
|
||||
*******************************************************************************/
|
||||
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
primePvsCache(table, records);
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String translatePossibleValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource == null)
|
||||
{
|
||||
LOG.error("Missing possible value source named [" + field.getPossibleValueSourceName() + "] when formatting value for field [" + field.getName() + "]");
|
||||
return (null);
|
||||
}
|
||||
|
||||
// todo - memoize!!!
|
||||
// todo - bulk!!!
|
||||
|
||||
String resultValue = null;
|
||||
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
|
||||
{
|
||||
resultValue = translatePossibleValueEnum(value, possibleValueSource);
|
||||
}
|
||||
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
resultValue = translatePossibleValueTable(field, value, possibleValueSource);
|
||||
}
|
||||
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.CUSTOM))
|
||||
{
|
||||
resultValue = translatePossibleValueCustom(field, value, possibleValueSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Unrecognized possibleValueSourceType [" + possibleValueSource.getType() + "] in PVS named [" + possibleValueSource.getName() + "] on field [" + field.getName() + "]");
|
||||
}
|
||||
|
||||
if(resultValue == null)
|
||||
{
|
||||
resultValue = getDefaultForPossibleValue(possibleValueSource, value);
|
||||
}
|
||||
|
||||
return (resultValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
|
||||
}
|
||||
|
||||
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueTable(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
/////////////////////////////////
|
||||
// null input gets null output //
|
||||
/////////////////////////////////
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// look for cached value - if it's missing, call the primer //
|
||||
//////////////////////////////////////////////////////////////
|
||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
||||
Map<Serializable, String> cacheForPvs = possibleValueCache.get(possibleValueSource.getName());
|
||||
if(!cacheForPvs.containsKey(value))
|
||||
{
|
||||
primePvsCache(possibleValueSource.getTableName(), List.of(possibleValueSource), List.of(value));
|
||||
}
|
||||
|
||||
return (cacheForPvs.get(value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String formatPossibleValue(QPossibleValueSource possibleValueSource, QPossibleValue<?> possibleValue)
|
||||
{
|
||||
return (doFormatPossibleValue(possibleValueSource.getValueFormat(), possibleValueSource.getValueFields(), possibleValue.getId(), possibleValue.getLabel()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getDefaultForPossibleValue(QPossibleValueSource possibleValueSource, Serializable value)
|
||||
{
|
||||
if(possibleValueSource.getValueFormatIfNotFound() == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (doFormatPossibleValue(possibleValueSource.getValueFormatIfNotFound(), possibleValueSource.getValueFieldsIfNotFound(), value, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
private String doFormatPossibleValue(String formatString, List<String> valueFields, Object id, String label)
|
||||
{
|
||||
List<Object> values = new ArrayList<>();
|
||||
if(valueFields != null)
|
||||
{
|
||||
for(String valueField : valueFields)
|
||||
{
|
||||
Object value = switch(valueField)
|
||||
{
|
||||
case "id" -> id;
|
||||
case "label" -> label;
|
||||
default -> throw new IllegalArgumentException("Unexpected value field: " + valueField);
|
||||
};
|
||||
values.add(Objects.requireNonNullElse(value, ""));
|
||||
}
|
||||
}
|
||||
|
||||
return (formatString.formatted(values.toArray()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
if(possibleValue.getId().equals(value))
|
||||
{
|
||||
return (formatPossibleValue(possibleValueSource, possibleValue));
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
||||
*******************************************************************************/
|
||||
void primePvsCache(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
ListingHash<String, QFieldMetaData> fieldsByPvsTable = new ListingHash<>();
|
||||
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
fieldsByPvsTable.add(possibleValueSource.getTableName(), field);
|
||||
pvsesByTable.add(possibleValueSource.getTableName(), possibleValueSource);
|
||||
}
|
||||
}
|
||||
|
||||
for(String tableName : fieldsByPvsTable.keySet())
|
||||
{
|
||||
Set<Serializable> values = new HashSet<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
for(QFieldMetaData field : fieldsByPvsTable.get(tableName))
|
||||
{
|
||||
values.add(record.getValue(field.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
primePvsCache(tableName, pvsesByTable.get(tableName), values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a given table, and a list of pkey-values in that table, AND a list of
|
||||
** possible value sources based on that table (maybe usually 1, but could be more,
|
||||
** e.g., if they had different formatting, or different filters (todo, would that work?)
|
||||
** - query for the values in the table, and populate the possibleValueCache.
|
||||
*******************************************************************************/
|
||||
private void primePvsCache(String tableName, List<QPossibleValueSource> possibleValueSources, Collection<Serializable> values)
|
||||
{
|
||||
for(QPossibleValueSource possibleValueSource : possibleValueSources)
|
||||
{
|
||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String primaryKeyField = qInstance.getTable(tableName).getPrimaryKeyField();
|
||||
|
||||
for(List<Serializable> page : CollectionUtils.getPages(values, 1000))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, page)));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is needed to get record labels, which are what we use here... unclear if best! //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
Serializable pkeyValue = record.getValue(primaryKeyField);
|
||||
for(QPossibleValueSource possibleValueSource : possibleValueSources)
|
||||
{
|
||||
QPossibleValue<?> possibleValue = new QPossibleValue<>(pkeyValue, record.getRecordLabel());
|
||||
possibleValueCache.get(possibleValueSource.getName()).put(pkeyValue, formatPossibleValue(possibleValueSource, possibleValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error looking up possible values for table [" + tableName + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -25,8 +25,10 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -34,7 +36,8 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility to apply display formats to values for fields
|
||||
** Utility to apply display formats to values for records and fields.
|
||||
** Note that this includes handling PossibleValues.
|
||||
*******************************************************************************/
|
||||
public class QValueFormatter
|
||||
{
|
||||
@ -45,7 +48,16 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String formatValue(QFieldMetaData field, Serializable value)
|
||||
public QValueFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String formatValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
//////////////////////////////////
|
||||
// null values get null results //
|
||||
@ -55,6 +67,16 @@ public class QValueFormatter
|
||||
return (null);
|
||||
}
|
||||
|
||||
// todo - is this appropriate, with this class and possibleValueTransaltor being decoupled - to still do standard formatting here?
|
||||
// alternatively, shold we return null here?
|
||||
// ///////////////////////////////////////////////
|
||||
// // if the field has a possible value, use it //
|
||||
// ///////////////////////////////////////////////
|
||||
// if(field.getPossibleValueSourceName() != null)
|
||||
// {
|
||||
// return (this.possibleValueTranslator.translatePossibleValue(field, value));
|
||||
// }
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// if the field has a display format, try to apply it //
|
||||
////////////////////////////////////////////////////////
|
||||
@ -68,6 +90,7 @@ public class QValueFormatter
|
||||
{
|
||||
try
|
||||
{
|
||||
// todo - revisit if we actually want this - or - if you should get an error if you mis-configure your table this way (ideally during validation!)
|
||||
if(e.getMessage().equals("f != java.lang.Integer"))
|
||||
{
|
||||
return formatValue(field, ValueUtils.getValueAsBigDecimal(value));
|
||||
@ -99,7 +122,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** Make a string from a table's recordLabelFormat and fields, for a given record.
|
||||
*******************************************************************************/
|
||||
public static String formatRecordLabel(QTableMetaData table, QRecord record)
|
||||
public String formatRecordLabel(QTableMetaData table, QRecord record)
|
||||
{
|
||||
if(!StringUtils.hasContent(table.getRecordLabelFormat()))
|
||||
{
|
||||
@ -128,7 +151,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** Deal with non-happy-path cases for making a record label.
|
||||
*******************************************************************************/
|
||||
private static String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
|
||||
private String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's no record label format, then just return the primary key display value //
|
||||
@ -156,7 +179,7 @@ public class QValueFormatter
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values
|
||||
*******************************************************************************/
|
||||
public static void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
public void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
@ -167,12 +190,13 @@ public class QValueFormatter
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
String formattedValue = QValueFormatter.formatValue(field, record.getValue(field.getName()));
|
||||
String formattedValue = formatValue(field, record.getValue(field.getName()));
|
||||
record.setDisplayValue(field.getName(), formattedValue);
|
||||
}
|
||||
|
||||
record.setRecordLabel(QValueFormatter.formatRecordLabel(table, record));
|
||||
record.setRecordLabel(formatRecordLabel(table, record));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -95,6 +95,15 @@ public class CsvToQRecordAdapter
|
||||
throw (new IllegalArgumentException("Empty csv value was provided."));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// once, from a DOS csv file (that had come from Excel), we had a "" character (FEFF, Byte-order marker) at the start of a //
|
||||
// CSV, which caused our first header to not match... So, let us strip away any FEFF or FFFE's at the start of CSV strings. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(csv.length() > 1 && (csv.charAt(0) == 0xfeff || csv.charAt(0) == 0xfffe))
|
||||
{
|
||||
csv = csv.substring(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -118,7 +127,7 @@ public class CsvToQRecordAdapter
|
||||
// put values from the CSV record into a map of header -> value //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
Map<String, String> csvValues = new HashMap<>();
|
||||
for(int i = 0; i < headers.size(); i++)
|
||||
for(int i = 0; i < headers.size() && i < csvRecord.size(); i++)
|
||||
{
|
||||
csvValues.put(headers.get(i), csvRecord.get(i));
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -128,6 +129,11 @@ public class QInstanceEnricher
|
||||
{
|
||||
generateTableFieldSections(table);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(table.getRecordLabelFields()) && !StringUtils.hasContent(table.getRecordLabelFormat()))
|
||||
{
|
||||
table.setRecordLabelFormat(String.join(" ", Collections.nCopies(table.getRecordLabelFields().size(), "%s")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -211,7 +217,7 @@ public class QInstanceEnricher
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String nameToLabel(String name)
|
||||
static String nameToLabel(String name)
|
||||
{
|
||||
if(!StringUtils.hasContent(name))
|
||||
{
|
||||
@ -223,7 +229,7 @@ public class QInstanceEnricher
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z0-9]+)", " $1").replaceAll("([0-9])([A-Za-z])", "$1 $2"));
|
||||
}
|
||||
|
||||
|
||||
@ -579,7 +585,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
for(String fieldName : table.getRecordLabelFields())
|
||||
{
|
||||
if(!usedFieldNames.contains(fieldName))
|
||||
if(!usedFieldNames.contains(fieldName) && table.getFields().containsKey(fieldName))
|
||||
{
|
||||
identitySection.getFieldNames().add(fieldName);
|
||||
usedFieldNames.add(fieldName);
|
||||
|
@ -29,6 +29,7 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
@ -88,6 +89,7 @@ public class QInstanceValidator
|
||||
validateTables(qInstance, errors);
|
||||
validateProcesses(qInstance, errors);
|
||||
validateApps(qInstance, errors);
|
||||
validatePossibleValueSources(qInstance, errors);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -167,8 +169,8 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////
|
||||
// validate field sections in the table //
|
||||
//////////////////////////////////////////
|
||||
Set<String> fieldNamesInSections = new HashSet<>();
|
||||
QFieldSection tier1Section = null;
|
||||
Set<String> fieldNamesInSections = new HashSet<>();
|
||||
QFieldSection tier1Section = null;
|
||||
if(table.getSections() != null)
|
||||
{
|
||||
for(QFieldSection section : table.getSections())
|
||||
@ -190,6 +192,16 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// validate the record label //
|
||||
///////////////////////////////
|
||||
if(table.getRecordLabelFields() != null)
|
||||
{
|
||||
for(String recordLabelField : table.getRecordLabelFields())
|
||||
{
|
||||
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -225,7 +237,7 @@ public class QInstanceValidator
|
||||
*******************************************************************************/
|
||||
private void validateProcesses(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getProcesses()))
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getProcesses()))
|
||||
{
|
||||
qInstance.getProcesses().forEach((processName, process) ->
|
||||
{
|
||||
@ -264,7 +276,7 @@ public class QInstanceValidator
|
||||
*******************************************************************************/
|
||||
private void validateApps(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getApps()))
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getApps()))
|
||||
{
|
||||
qInstance.getApps().forEach((appName, app) ->
|
||||
{
|
||||
@ -291,6 +303,61 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validatePossibleValueSources(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getPossibleValueSources()))
|
||||
{
|
||||
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
||||
{
|
||||
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
||||
assertCondition(errors, possibleValueSource.getIdType() != null, "Missing an idType for possibleValueSource: " + pvsName);
|
||||
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert about fields that should and should not be set, based on possible value source type //
|
||||
// do additional type-specific validations as well //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
switch(possibleValueSource.getType())
|
||||
{
|
||||
case ENUM ->
|
||||
{
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
assertCondition(errors, CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
|
||||
}
|
||||
case TABLE ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
|
||||
|
||||
if(assertCondition(errors, StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
|
||||
{
|
||||
assertCondition(errors, qInstance.getTable(possibleValueSource.getTableName()) != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
|
||||
}
|
||||
}
|
||||
case CUSTOM ->
|
||||
{
|
||||
assertCondition(errors, CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
|
||||
assertCondition(errors, !StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
|
||||
|
||||
if(assertCondition(errors, possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
|
||||
{
|
||||
assertCondition(errors, QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||
}
|
||||
}
|
||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Check if an app's child list can recursively be traversed without finding a
|
||||
** duplicate, which would indicate a cycle (e.g., an error)
|
||||
|
@ -40,6 +40,8 @@ public class QueryInput extends AbstractTableActionInput
|
||||
|
||||
private RecordPipe recordPipe;
|
||||
|
||||
private boolean shouldTranslatePossibleValues = false;
|
||||
private boolean shouldGenerateDisplayValues = false;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -158,4 +160,47 @@ public class QueryInput extends AbstractTableActionInput
|
||||
this.recordPipe = recordPipe;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldTranslatePossibleValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getShouldTranslatePossibleValues()
|
||||
{
|
||||
return shouldTranslatePossibleValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldTranslatePossibleValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setShouldTranslatePossibleValues(boolean shouldTranslatePossibleValues)
|
||||
{
|
||||
this.shouldTranslatePossibleValues = shouldTranslatePossibleValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldGenerateDisplayValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getShouldGenerateDisplayValues()
|
||||
{
|
||||
return shouldGenerateDisplayValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldGenerateDisplayValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setShouldGenerateDisplayValues(boolean shouldGenerateDisplayValues)
|
||||
{
|
||||
this.shouldGenerateDisplayValues = shouldGenerateDisplayValues;
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,11 @@ public @interface QField
|
||||
*******************************************************************************/
|
||||
String displayFormat() default "";
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String possibleValueSourceName() default "";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// new attributes here likely need implementation in QFieldMetaData.constructFromGetter //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -54,10 +54,10 @@ public class QInstance
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
|
||||
private Map<String, QPossibleValueSource<?>> possibleValueSources = new LinkedHashMap<>();
|
||||
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
|
||||
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
|
||||
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
|
||||
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
|
||||
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
|
||||
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
|
||||
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated?
|
||||
|
||||
@ -190,7 +190,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addPossibleValueSource(QPossibleValueSource<?> possibleValueSource)
|
||||
public void addPossibleValueSource(QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
this.addPossibleValueSource(possibleValueSource.getName(), possibleValueSource);
|
||||
}
|
||||
@ -353,7 +353,7 @@ public class QInstance
|
||||
** Getter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QPossibleValueSource<?>> getPossibleValueSources()
|
||||
public Map<String, QPossibleValueSource> getPossibleValueSources()
|
||||
{
|
||||
return possibleValueSources;
|
||||
}
|
||||
@ -364,7 +364,7 @@ public class QInstance
|
||||
** Setter for possibleValueSources
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource<?>> possibleValueSources)
|
||||
public void setPossibleValueSources(Map<String, QPossibleValueSource> possibleValueSources)
|
||||
{
|
||||
this.possibleValueSources = possibleValueSources;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -70,6 +71,10 @@ public class QCodeReference
|
||||
{
|
||||
this.codeUsage = QCodeUsage.BACKEND_STEP;
|
||||
}
|
||||
else if(QCustomPossibleValueProvider.class.isAssignableFrom(javaClass))
|
||||
{
|
||||
this.codeUsage = QCodeUsage.POSSIBLE_VALUE_PROVIDER;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
|
||||
|
@ -29,5 +29,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
public enum QCodeUsage
|
||||
{
|
||||
BACKEND_STEP, // a backend-step in a process
|
||||
CUSTOMIZER // a function to customize part of a QQQ table's behavior
|
||||
CUSTOMIZER, // a function to customize part of a QQQ table's behavior
|
||||
POSSIBLE_VALUE_PROVIDER // code that drives a custom possibleValueSource
|
||||
}
|
||||
|
@ -133,6 +133,11 @@ public class QFieldMetaData
|
||||
{
|
||||
setDisplayFormat(fieldAnnotation.displayFormat());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.possibleValueSourceName()))
|
||||
{
|
||||
setPossibleValueSourceName(fieldAnnotation.possibleValueSourceName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
@ -406,6 +411,7 @@ public class QFieldMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for displayFormat
|
||||
**
|
||||
@ -427,6 +433,7 @@ public class QFieldMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for displayFormat
|
||||
**
|
||||
|
@ -36,11 +36,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class QFrontendFieldMetaData
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private String name;
|
||||
private String label;
|
||||
private QFieldType type;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
@ -58,6 +60,8 @@ public class QFrontendFieldMetaData
|
||||
this.type = fieldMetaData.getType();
|
||||
this.isRequired = fieldMetaData.getIsRequired();
|
||||
this.isEditable = fieldMetaData.getIsEditable();
|
||||
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
|
||||
this.displayFormat = fieldMetaData.getDisplayFormat();
|
||||
}
|
||||
|
||||
|
||||
@ -115,4 +119,26 @@ public class QFrontendFieldMetaData
|
||||
return isEditable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for displayFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getDisplayFormat()
|
||||
{
|
||||
return displayFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for possibleValueSourceName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getPossibleValueSourceName()
|
||||
{
|
||||
return possibleValueSourceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface PossibleValueEnum<T>
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
T getPossibleValueId();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getPossibleValueLabel();
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** An actual possible value - an id and label.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QPossibleValue<T>
|
||||
{
|
||||
private final T id;
|
||||
private final String label;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public QPossibleValue(String value)
|
||||
{
|
||||
this.id = (T) value;
|
||||
this.label = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValue(T id, String label)
|
||||
{
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
@ -24,19 +24,62 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to represent a single field in a table.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueSource<T>
|
||||
public class QPossibleValueSource
|
||||
{
|
||||
private String name;
|
||||
private String name;
|
||||
private QPossibleValueSourceType type;
|
||||
private QFieldType idType = QFieldType.INTEGER;
|
||||
|
||||
// should these be in sub-types??
|
||||
private List<T> enumValues;
|
||||
private String valueFormat = ValueFormat.DEFAULT;
|
||||
private List<String> valueFields = ValueFields.DEFAULT;
|
||||
private String valueFormatIfNotFound = null;
|
||||
private List<String> valueFieldsIfNotFound = null;
|
||||
|
||||
|
||||
|
||||
public interface ValueFormat
|
||||
{
|
||||
String DEFAULT = "%s";
|
||||
String LABEL_ONLY = "%s";
|
||||
String LABEL_PARENS_ID = "%s (%s)";
|
||||
String ID_COLON_LABEL = "%s: %s";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface ValueFields
|
||||
{
|
||||
List<String> DEFAULT = List.of("label");
|
||||
List<String> LABEL_ONLY = List.of("label");
|
||||
List<String> LABEL_PARENS_ID = List.of("label", "id");
|
||||
List<String> ID_COLON_LABEL = List.of("id", "label");
|
||||
}
|
||||
|
||||
// todo - optimization hints, such as "table is static, fully cache" or "table is small, so we can pull the whole thing into memory?"
|
||||
|
||||
//////////////////////
|
||||
// for type = TABLE //
|
||||
//////////////////////
|
||||
private String tableName;
|
||||
// todo - override labelFormat & labelFields?
|
||||
|
||||
/////////////////////
|
||||
// for type = ENUM //
|
||||
/////////////////////
|
||||
private List<QPossibleValue<?>> enumValues;
|
||||
|
||||
///////////////////////
|
||||
// for type = CUSTOM //
|
||||
///////////////////////
|
||||
private QCodeReference customCodeReference;
|
||||
|
||||
|
||||
|
||||
@ -72,7 +115,7 @@ public class QPossibleValueSource<T>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withName(String name)
|
||||
public QPossibleValueSource withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
@ -103,7 +146,7 @@ public class QPossibleValueSource<T>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withType(QPossibleValueSourceType type)
|
||||
public QPossibleValueSource withType(QPossibleValueSourceType type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
@ -111,11 +154,215 @@ public class QPossibleValueSource<T>
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for idType
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFieldType getIdType()
|
||||
{
|
||||
return idType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for idType
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setIdType(QFieldType idType)
|
||||
{
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for idType
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withIdType(QFieldType idType)
|
||||
{
|
||||
this.idType = idType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getValueFormat()
|
||||
{
|
||||
return valueFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFormat(String valueFormat)
|
||||
{
|
||||
this.valueFormat = valueFormat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFormat
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFormat(String valueFormat)
|
||||
{
|
||||
this.valueFormat = valueFormat;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getValueFields()
|
||||
{
|
||||
return valueFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFields(List<String> valueFields)
|
||||
{
|
||||
this.valueFields = valueFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFields(List<String> valueFields)
|
||||
{
|
||||
this.valueFields = valueFields;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getValueFormatIfNotFound()
|
||||
{
|
||||
return valueFormatIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFormatIfNotFound(String valueFormatIfNotFound)
|
||||
{
|
||||
this.valueFormatIfNotFound = valueFormatIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFormatIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFormatIfNotFound(String valueFormatIfNotFound)
|
||||
{
|
||||
this.valueFormatIfNotFound = valueFormatIfNotFound;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getValueFieldsIfNotFound()
|
||||
{
|
||||
return valueFieldsIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValueFieldsIfNotFound(List<String> valueFieldsIfNotFound)
|
||||
{
|
||||
this.valueFieldsIfNotFound = valueFieldsIfNotFound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueFieldsIfNotFound
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withValueFieldsIfNotFound(List<String> valueFieldsIfNotFound)
|
||||
{
|
||||
this.valueFieldsIfNotFound = valueFieldsIfNotFound;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<T> getEnumValues()
|
||||
public List<QPossibleValue<?>> getEnumValues()
|
||||
{
|
||||
return enumValues;
|
||||
}
|
||||
@ -126,7 +373,7 @@ public class QPossibleValueSource<T>
|
||||
** Setter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setEnumValues(List<T> enumValues)
|
||||
public void setEnumValues(List<QPossibleValue<?>> enumValues)
|
||||
{
|
||||
this.enumValues = enumValues;
|
||||
}
|
||||
@ -137,7 +384,7 @@ public class QPossibleValueSource<T>
|
||||
** Fluent setter for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> withEnumValues(List<T> enumValues)
|
||||
public QPossibleValueSource withEnumValues(List<QPossibleValue<?>> enumValues)
|
||||
{
|
||||
this.enumValues = enumValues;
|
||||
return this;
|
||||
@ -146,16 +393,64 @@ public class QPossibleValueSource<T>
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent adder for enumValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource<T> addEnumValue(T enumValue)
|
||||
public void addEnumValue(QPossibleValue<?> possibleValue)
|
||||
{
|
||||
if(this.enumValues == null)
|
||||
{
|
||||
this.enumValues = new ArrayList<>();
|
||||
}
|
||||
this.enumValues.add(enumValue);
|
||||
return this;
|
||||
this.enumValues.add(possibleValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <T extends PossibleValueEnum<?>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||
{
|
||||
for(T t : values)
|
||||
{
|
||||
addEnumValue(new QPossibleValue<>(t.getPossibleValueId(), t.getPossibleValueLabel()));
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QCodeReference getCustomCodeReference()
|
||||
{
|
||||
return customCodeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCustomCodeReference(QCodeReference customCodeReference)
|
||||
{
|
||||
this.customCodeReference = customCodeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for customCodeReference
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference)
|
||||
{
|
||||
this.customCodeReference = customCodeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -587,6 +588,18 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordLabelFields
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withRecordLabelFields(String... recordLabelFields)
|
||||
{
|
||||
this.recordLabelFields = Arrays.asList(recordLabelFields);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sections
|
||||
**
|
||||
|
@ -167,6 +167,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
return (false);
|
||||
}
|
||||
|
||||
if(session.getIdReference() == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
StateProviderInterface spi = getStateProvider();
|
||||
Auth0StateKey key = new Auth0StateKey(session.getIdReference());
|
||||
Optional<Instant> lastTimeCheckedOptional = spi.get(Instant.class, key);
|
||||
|
@ -24,18 +24,21 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -48,6 +51,12 @@ public class MemoryRecordStore
|
||||
private Map<String, Map<Serializable, QRecord>> data;
|
||||
private Map<String, Integer> nextSerials;
|
||||
|
||||
private static boolean collectStatistics = false;
|
||||
|
||||
private static final Map<String, Integer> statistics = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public static final String STAT_QUERIES_RAN = "queriesRan";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -105,9 +114,56 @@ public class MemoryRecordStore
|
||||
*******************************************************************************/
|
||||
public List<QRecord> query(QueryInput input)
|
||||
{
|
||||
incrementStatistic(STAT_QUERIES_RAN);
|
||||
|
||||
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
||||
List<QRecord> records = new ArrayList<>(tableData.values());
|
||||
// todo - filtering
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
|
||||
for(QRecord qRecord : tableData.values())
|
||||
{
|
||||
boolean recordMatches = true;
|
||||
if(input.getFilter() != null && input.getFilter().getCriteria() != null)
|
||||
{
|
||||
for(QFilterCriteria criterion : input.getFilter().getCriteria())
|
||||
{
|
||||
String fieldName = criterion.getFieldName();
|
||||
Serializable value = qRecord.getValue(fieldName);
|
||||
switch(criterion.getOperator())
|
||||
{
|
||||
case EQUALS:
|
||||
{
|
||||
if(!value.equals(criterion.getValues().get(0)))
|
||||
{
|
||||
recordMatches = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IN:
|
||||
{
|
||||
if(!criterion.getValues().contains(value))
|
||||
{
|
||||
recordMatches = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotImplementedException("Operator [" + criterion.getOperator() + "] is not yet implemented in the Memory backend.");
|
||||
}
|
||||
}
|
||||
if(!recordMatches)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(recordMatches)
|
||||
{
|
||||
records.add(qRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
@ -120,7 +176,7 @@ public class MemoryRecordStore
|
||||
{
|
||||
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
||||
List<QRecord> records = new ArrayList<>(tableData.values());
|
||||
// todo - filtering
|
||||
// todo - filtering (call query)
|
||||
return (records.size());
|
||||
}
|
||||
|
||||
@ -235,4 +291,53 @@ public class MemoryRecordStore
|
||||
|
||||
return (rowsDeleted);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for collectStatistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setCollectStatistics(boolean collectStatistics)
|
||||
{
|
||||
MemoryRecordStore.collectStatistics = collectStatistics;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Increment a statistic
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void incrementStatistic(String statName)
|
||||
{
|
||||
if(collectStatistics)
|
||||
{
|
||||
statistics.putIfAbsent(statName, 0);
|
||||
statistics.put(statName, statistics.get(statName) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clear the map of statistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void resetStatistics()
|
||||
{
|
||||
statistics.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for statistics
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Map<String, Integer> getStatistics()
|
||||
{
|
||||
return statistics;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,6 +82,8 @@ public class BasicETLLoadAsUpdateFunction implements BackendStep
|
||||
for(List<QRecord> page : CollectionUtils.getPages(inputRecords, pageSize))
|
||||
{
|
||||
LOG.info("Updating a page of [" + page.size() + "] records. Progress: " + recordsUpdated + " loaded out of " + inputRecords.size() + " total");
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Updating records", recordsUpdated, inputRecords.size());
|
||||
|
||||
UpdateInput updateInput = new UpdateInput(runBackendStepInput.getInstance());
|
||||
updateInput.setSession(runBackendStepInput.getSession());
|
||||
updateInput.setTableName(table);
|
||||
|
@ -86,6 +86,8 @@ public class BasicETLLoadFunction implements BackendStep
|
||||
for(List<QRecord> page : CollectionUtils.getPages(inputRecords, pageSize))
|
||||
{
|
||||
LOG.info("Inserting a page of [" + page.size() + "] records. Progress: " + recordsInserted + " loaded out of " + inputRecords.size() + " total");
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Inserting records", recordsInserted, inputRecords.size());
|
||||
|
||||
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
|
||||
insertInput.setSession(runBackendStepInput.getSession());
|
||||
insertInput.setTableName(table);
|
||||
|
@ -44,6 +44,7 @@ public class ValueUtils
|
||||
{
|
||||
private static final DateTimeFormatter dateTimeFormatter_yyyyMMddWithDashes = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter dateTimeFormatter_MdyyyyWithSlashes = DateTimeFormatter.ofPattern("M/d/yyyy");
|
||||
private static final DateTimeFormatter dateTimeFormatter_yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
|
||||
|
||||
@ -262,7 +263,7 @@ public class ValueUtils
|
||||
private static LocalDate tryLocalDateParsers(String s)
|
||||
{
|
||||
DateTimeParseException lastException = null;
|
||||
for(DateTimeFormatter dateTimeFormatter : List.of(dateTimeFormatter_yyyyMMddWithDashes, dateTimeFormatter_MdyyyyWithSlashes))
|
||||
for(DateTimeFormatter dateTimeFormatter : List.of(dateTimeFormatter_yyyyMMddWithDashes, dateTimeFormatter_MdyyyyWithSlashes, dateTimeFormatter_yyyyMMdd))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
Reference in New Issue
Block a user