mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Feedback from code reviews
This commit is contained in:
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.customizers;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Standard place where the names of QQQ Customization points are defined.
|
|
||||||
*******************************************************************************/
|
|
||||||
public interface Customizers
|
|
||||||
{
|
|
||||||
String POST_QUERY_RECORD = "postQueryRecord";
|
|
||||||
|
|
||||||
}
|
|
@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -43,15 +46,14 @@ public class QCodeLoader
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static Function<?, ?> getTableCustomizerFunction(QTableMetaData table, String customizerName)
|
public static <T, R> Optional<Function<T, R>> getTableCustomizerFunction(QTableMetaData table, String customizerName)
|
||||||
{
|
{
|
||||||
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
||||||
if(codeReference.isPresent())
|
if(codeReference.isPresent())
|
||||||
{
|
{
|
||||||
return (QCodeLoader.getFunction(codeReference.get()));
|
return (Optional.ofNullable(QCodeLoader.getFunction(codeReference.get())));
|
||||||
}
|
}
|
||||||
|
return (Optional.empty());
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,4 +95,30 @@ public class QCodeLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
|
||||||
|
{
|
||||||
|
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 (customPossibleValueProvider);
|
||||||
|
}
|
||||||
|
catch(QException qe)
|
||||||
|
{
|
||||||
|
throw (qe);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Object used by TableCustomizers enum (and similar enums in backend modules)
|
||||||
|
** to assist with definition and validation of Customizers applied to tables.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class TableCustomizer
|
||||||
|
{
|
||||||
|
private final String role;
|
||||||
|
private final Class<?> expectedType;
|
||||||
|
private final Consumer<Object> validationFunction;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public TableCustomizer(String role, Class<?> expectedType, Consumer<Object> validationFunction)
|
||||||
|
{
|
||||||
|
this.role = role;
|
||||||
|
this.expectedType = expectedType;
|
||||||
|
this.validationFunction = validationFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for role
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRole()
|
||||||
|
{
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for expectedType
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Class<?> getExpectedType()
|
||||||
|
{
|
||||||
|
return expectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for validationFunction
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Consumer<Object> getValidationFunction()
|
||||||
|
{
|
||||||
|
return validationFunction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Enum definition of possible table customizers - "roles" for custom code that
|
||||||
|
** can be applied to tables.
|
||||||
|
**
|
||||||
|
** Works with TableCustomizer (singular version of this name) objects, during
|
||||||
|
** instance validation, to provide validation of the referenced code (and to
|
||||||
|
** make such validation from sub-backend-modules possible in the future).
|
||||||
|
**
|
||||||
|
** The idea of the 3rd argument here is to provide a way that we can enforce
|
||||||
|
** the type-parameters for the custom code. E.g., if it's a Function - how
|
||||||
|
** can we check at run-time that the type-params are correct? We couldn't find
|
||||||
|
** how to do this "reflectively", so we can instead try to run the custom code,
|
||||||
|
** passing it objects of the type that this customizer expects, and a validation
|
||||||
|
** error will raise upon ClassCastException... This maybe could improve!
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum TableCustomizers
|
||||||
|
{
|
||||||
|
POST_QUERY_RECORD(new TableCustomizer("postQueryRecord", Function.class, ((Object x) ->
|
||||||
|
{
|
||||||
|
Function<QRecord, QRecord> function = (Function<QRecord, QRecord>) x;
|
||||||
|
QRecord output = function.apply(new QRecord());
|
||||||
|
})));
|
||||||
|
|
||||||
|
|
||||||
|
private final TableCustomizer tableCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
TableCustomizers(TableCustomizer tableCustomizer)
|
||||||
|
{
|
||||||
|
this.tableCustomizer = tableCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get the TableCustomer for a given role (e.g., the role used in meta-data, not
|
||||||
|
** the enum-constant name).
|
||||||
|
*******************************************************************************/
|
||||||
|
public static TableCustomizers forRole(String name)
|
||||||
|
{
|
||||||
|
for(TableCustomizers value : values())
|
||||||
|
{
|
||||||
|
if(value.tableCustomizer.getRole().equals(name))
|
||||||
|
{
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for tableCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public TableCustomizer getTableCustomizer()
|
||||||
|
{
|
||||||
|
return tableCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** get the role from the tableCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRole()
|
||||||
|
{
|
||||||
|
return (tableCustomizer.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -42,12 +43,31 @@ public class RecordPipe
|
|||||||
|
|
||||||
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(1_000);
|
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(1_000);
|
||||||
|
|
||||||
|
private Consumer<List<QRecord>> postRecordActions = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Add a record to the pipe
|
** Add a record to the pipe
|
||||||
** Returns true iff the record fit in the pipe; false if the pipe is currently full.
|
** Returns true iff the record fit in the pipe; false if the pipe is currently full.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record)
|
||||||
|
{
|
||||||
|
if(postRecordActions != null)
|
||||||
|
{
|
||||||
|
postRecordActions.accept(List.of(record));
|
||||||
|
}
|
||||||
|
|
||||||
|
doAddRecord(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Private internal version of add record - assumes the postRecordActions have
|
||||||
|
** already ran.
|
||||||
|
*******************************************************************************/
|
||||||
|
private void doAddRecord(QRecord record)
|
||||||
{
|
{
|
||||||
boolean offerResult = queue.offer(record);
|
boolean offerResult = queue.offer(record);
|
||||||
|
|
||||||
@ -66,7 +86,15 @@ public class RecordPipe
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records)
|
||||||
{
|
{
|
||||||
records.forEach(this::addRecord);
|
if(postRecordActions != null)
|
||||||
|
{
|
||||||
|
postRecordActions.accept(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure to go to the private version of doAddRecord - to avoid re-running the post-actions //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
records.forEach(this::doAddRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -101,4 +129,14 @@ public class RecordPipe
|
|||||||
return (queue.size());
|
return (queue.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setPostRecordActions(Consumer<List<QRecord>> postRecordActions)
|
||||||
|
{
|
||||||
|
this.postRecordActions = postRecordActions;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,18 @@
|
|||||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
|
|
||||||
@ -38,6 +44,14 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryAction
|
public class QueryAction
|
||||||
{
|
{
|
||||||
|
private Optional<Function<QRecord, QRecord>> postQueryRecordCustomizer;
|
||||||
|
|
||||||
|
private QueryInput queryInput;
|
||||||
|
private QValueFormatter qValueFormatter;
|
||||||
|
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -45,6 +59,14 @@ public class QueryAction
|
|||||||
{
|
{
|
||||||
ActionHelper.validateSession(queryInput);
|
ActionHelper.validateSession(queryInput);
|
||||||
|
|
||||||
|
postQueryRecordCustomizer = QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||||
|
this.queryInput = queryInput;
|
||||||
|
|
||||||
|
if(queryInput.getRecordPipe() != null)
|
||||||
|
{
|
||||||
|
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
|
||||||
|
}
|
||||||
|
|
||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
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?
|
// todo pre-customization - just get to modify the request?
|
||||||
@ -53,20 +75,37 @@ public class QueryAction
|
|||||||
|
|
||||||
if(queryInput.getRecordPipe() == null)
|
if(queryInput.getRecordPipe() == null)
|
||||||
{
|
{
|
||||||
if(queryInput.getShouldGenerateDisplayValues())
|
postRecordActions(queryOutput.getRecords());
|
||||||
{
|
|
||||||
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;
|
return queryOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void postRecordActions(List<QRecord> records)
|
||||||
|
{
|
||||||
|
this.postQueryRecordCustomizer.ifPresent(qRecordQRecordFunction -> records.replaceAll(qRecordQRecordFunction::apply));
|
||||||
|
|
||||||
|
if(queryInput.getShouldGenerateDisplayValues())
|
||||||
|
{
|
||||||
|
if(qValueFormatter == null)
|
||||||
|
{
|
||||||
|
qValueFormatter = new QValueFormatter();
|
||||||
|
}
|
||||||
|
qValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(queryInput.getShouldTranslatePossibleValues())
|
||||||
|
{
|
||||||
|
if(qPossibleValueTranslator == null)
|
||||||
|
{
|
||||||
|
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||||
|
}
|
||||||
|
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
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.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
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.QQueryFilter;
|
||||||
@ -63,8 +63,10 @@ public class QPossibleValueTranslator
|
|||||||
private final QInstance qInstance;
|
private final QInstance qInstance;
|
||||||
private final QSession session;
|
private final QSession session;
|
||||||
|
|
||||||
// top-level keys are pvsNames (not table names)
|
///////////////////////////////////////////////////////
|
||||||
// 2nd-level keys are pkey values from the PVS table
|
// 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;
|
private Map<String, Map<Serializable, String>> possibleValueCache;
|
||||||
|
|
||||||
|
|
||||||
@ -120,9 +122,6 @@ public class QPossibleValueTranslator
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo - memoize!!!
|
|
||||||
// todo - bulk!!!
|
|
||||||
|
|
||||||
String resultValue = null;
|
String resultValue = null;
|
||||||
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
|
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
|
||||||
{
|
{
|
||||||
@ -154,22 +153,14 @@ public class QPossibleValueTranslator
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
|
||||||
{
|
{
|
||||||
try
|
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
|
||||||
{
|
{
|
||||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
if(possibleValue.getId().equals(value))
|
||||||
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, possibleValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
return (null);
|
||||||
@ -205,6 +196,26 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -256,26 +267,11 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
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
|
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
||||||
|
**
|
||||||
|
** @param table the table that the records are from
|
||||||
|
** @param records the records that have the possible value id's (e.g., foreign keys)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void primePvsCache(QTableMetaData table, List<QRecord> records)
|
void primePvsCache(QTableMetaData table, List<QRecord> records)
|
||||||
{
|
{
|
||||||
|
@ -25,10 +25,8 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.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.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -37,7 +35,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Utility to apply display formats to values for records and fields.
|
** Utility to apply display formats to values for records and fields.
|
||||||
** Note that this includes handling PossibleValues.
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QValueFormatter
|
public class QValueFormatter
|
||||||
{
|
{
|
||||||
@ -45,15 +43,6 @@ public class QValueFormatter
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QValueFormatter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -67,16 +56,6 @@ public class QValueFormatter
|
|||||||
return (null);
|
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 //
|
// if the field has a display format, try to apply it //
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
@ -198,5 +177,4 @@ public class QValueFormatter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,21 @@ public class QInstanceEnricher
|
|||||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z0-9]+)", " $1").replaceAll("([0-9])([A-Za-z])", "$1 $2"));
|
String suffix = name.substring(1)
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Put a space before capital letters or numbers embedded in a name //
|
||||||
|
// e.g., omethingElse -> omething Else; umber1 -> umber 1 //
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
.replaceAll("([A-Z0-9]+)", " $1")
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// put a space between numbers and words that come after them //
|
||||||
|
// e.g., umber1dad -> number 1 dad //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
.replaceAll("([0-9])([A-Za-z])", "$1 $2");
|
||||||
|
|
||||||
|
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,13 +22,20 @@
|
|||||||
package com.kingsrook.qqq.backend.core.instances;
|
package com.kingsrook.qqq.backend.core.instances;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||||
@ -38,6 +45,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -52,6 +61,11 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QInstanceValidator
|
public class QInstanceValidator
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(QInstanceValidator.class);
|
||||||
|
|
||||||
|
private boolean printWarnings = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
@ -202,12 +216,130 @@ public class QInstanceValidator
|
|||||||
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
assertCondition(errors, table.getFields().containsKey(recordLabelField), "Table " + tableName + " record label field " + recordLabelField + " is not a field on this table.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(table.getCustomizers() != null)
|
||||||
|
{
|
||||||
|
for(Map.Entry<String, QCodeReference> entry : table.getCustomizers().entrySet())
|
||||||
|
{
|
||||||
|
validateTableCustomizer(errors, tableName, entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void validateTableCustomizer(List<String> errors, String tableName, String customizerName, QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
String prefix = "Table " + tableName + ", customizer " + customizerName + ": ";
|
||||||
|
|
||||||
|
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure (at this time) that it's a java type, then do some java checks //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////
|
||||||
|
// make sure the class can be loaded //
|
||||||
|
///////////////////////////////////////
|
||||||
|
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||||
|
if(customizerClass != null)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// make sure the customizer can be instantiated //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||||
|
|
||||||
|
TableCustomizers tableCustomizer = TableCustomizers.forRole(customizerName);
|
||||||
|
if(tableCustomizer == null)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - in the future, load customizers from backend-modules (e.g., FilesystemTableCustomizers) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
warn(prefix + "Unrecognized table customizer name (at least at backend-core level)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure the customizer instance can be cast to the expected type //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
if(customizerInstance != null && tableCustomizer.getTableCustomizer().getExpectedType() != null)
|
||||||
|
{
|
||||||
|
Object castedObject = getCastedObject(errors, prefix, tableCustomizer.getTableCustomizer().getExpectedType(), customizerInstance);
|
||||||
|
|
||||||
|
Consumer<Object> validationFunction = tableCustomizer.getTableCustomizer().getValidationFunction();
|
||||||
|
if(castedObject != null && validationFunction != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
validationFunction.accept(castedObject);
|
||||||
|
}
|
||||||
|
catch(ClassCastException e)
|
||||||
|
{
|
||||||
|
errors.add(prefix + "Error validating customizer type parameters: " + e.getMessage());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// mmm, calling customizers w/ random data is expected to often throw, so, this check is iffy at best... //
|
||||||
|
// if we run into more trouble here, we might consider disabling the whole "validation function" check. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private <T> T getCastedObject(List<String> errors, String prefix, Class<T> expectedType, Object customizerInstance)
|
||||||
|
{
|
||||||
|
T castedObject = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
castedObject = expectedType.cast(customizerInstance);
|
||||||
|
}
|
||||||
|
catch(ClassCastException e)
|
||||||
|
{
|
||||||
|
errors.add(prefix + "CodeReference could not be casted to the expected type: " + expectedType);
|
||||||
|
}
|
||||||
|
return castedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private Object getInstanceOfCodeReference(List<String> errors, String prefix, Class<?> customizerClass)
|
||||||
|
{
|
||||||
|
Object customizerInstance = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
customizerInstance = customizerClass.getConstructor().newInstance();
|
||||||
|
}
|
||||||
|
catch(InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
errors.add(prefix + "Instance of CodeReference could not be created: " + e);
|
||||||
|
}
|
||||||
|
return customizerInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -313,7 +445,6 @@ public class QInstanceValidator
|
|||||||
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
|
||||||
{
|
{
|
||||||
assertCondition(errors, Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
|
assertCondition(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))
|
if(assertCondition(errors, possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -347,6 +478,7 @@ public class QInstanceValidator
|
|||||||
if(assertCondition(errors, possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
|
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.");
|
assertCondition(errors, QCodeUsage.POSSIBLE_VALUE_PROVIDER.equals(possibleValueSource.getCustomCodeReference().getCodeUsage()), "customCodeReference for possibleValueSource " + pvsName + " is not a possibleValueProvider.");
|
||||||
|
validateCustomPossibleValueSourceCode(errors, pvsName, possibleValueSource.getCustomCodeReference());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
||||||
@ -358,6 +490,87 @@ public class QInstanceValidator
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void validateCustomPossibleValueSourceCode(List<String> errors, String pvsName, QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
String prefix = "PossibleValueSource " + pvsName + " custom code reference: ";
|
||||||
|
|
||||||
|
if(!preAssertionsForCodeReference(errors, codeReference, prefix))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure (at this time) that it's a java type, then do some java checks //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(assertCondition(errors, codeReference.getCodeType().equals(QCodeType.JAVA), prefix + "Only JAVA customizers are supported at this time."))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////
|
||||||
|
// make sure the class can be loaded //
|
||||||
|
///////////////////////////////////////
|
||||||
|
Class<?> customizerClass = getClassForCodeReference(errors, codeReference, prefix);
|
||||||
|
if(customizerClass != null)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// make sure the customizer can be instantiated //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
Object customizerInstance = getInstanceOfCodeReference(errors, prefix, customizerClass);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure the customizer instance can be cast to the expected type //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
if(customizerInstance != null)
|
||||||
|
{
|
||||||
|
getCastedObject(errors, prefix, QCustomPossibleValueProvider.class, customizerInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private Class<?> getClassForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||||
|
{
|
||||||
|
Class<?> customizerClass = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
customizerClass = Class.forName(codeReference.getName());
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
errors.add(prefix + "Class for CodeReference could not be found.");
|
||||||
|
}
|
||||||
|
return customizerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private boolean preAssertionsForCodeReference(List<String> errors, QCodeReference codeReference, String prefix)
|
||||||
|
{
|
||||||
|
boolean okay = true;
|
||||||
|
if(!assertCondition(errors, StringUtils.hasContent(codeReference.getName()), prefix + " is missing a code reference name"))
|
||||||
|
{
|
||||||
|
okay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!assertCondition(errors, codeReference.getCodeType() != null, prefix + " is missing a code type"))
|
||||||
|
{
|
||||||
|
okay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (okay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Check if an app's child list can recursively be traversed without finding a
|
** Check if an app's child list can recursively be traversed without finding a
|
||||||
** duplicate, which would indicate a cycle (e.g., an error)
|
** duplicate, which would indicate a cycle (e.g., an error)
|
||||||
@ -410,4 +623,16 @@ public class QInstanceValidator
|
|||||||
return (condition);
|
return (condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void warn(String message)
|
||||||
|
{
|
||||||
|
if(printWarnings)
|
||||||
|
{
|
||||||
|
LOG.info("Validation warning: " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -39,12 +34,8 @@ import org.apache.logging.log4j.Logger;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryOutput extends AbstractActionOutput implements Serializable
|
public class QueryOutput extends AbstractActionOutput implements Serializable
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LogManager.getLogger(QueryOutput.class);
|
|
||||||
|
|
||||||
private QueryOutputStorageInterface storage;
|
private QueryOutputStorageInterface storage;
|
||||||
|
|
||||||
private Function<QRecord, QRecord> postQueryRecordCustomizer;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -61,8 +52,6 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
{
|
{
|
||||||
storage = new QueryOutputList();
|
storage = new QueryOutputList();
|
||||||
}
|
}
|
||||||
|
|
||||||
postQueryRecordCustomizer = (Function<QRecord, QRecord>) QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -76,36 +65,16 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record)
|
||||||
{
|
{
|
||||||
record = runPostQueryRecordCustomizer(record);
|
|
||||||
storage.addRecord(record);
|
storage.addRecord(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QRecord runPostQueryRecordCustomizer(QRecord record)
|
|
||||||
{
|
|
||||||
if(this.postQueryRecordCustomizer != null)
|
|
||||||
{
|
|
||||||
record = this.postQueryRecordCustomizer.apply(record);
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** add a list of records to this output
|
** add a list of records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records)
|
||||||
{
|
{
|
||||||
if(this.postQueryRecordCustomizer != null)
|
|
||||||
{
|
|
||||||
records.replaceAll(t -> this.postQueryRecordCustomizer.apply(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.addRecords(records);
|
storage.addRecords(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ public class QRecord implements Serializable
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public LocalTime getValueLocalTime(String fieldName)
|
public LocalTime getValueLocalTime(String fieldName)
|
||||||
{
|
{
|
||||||
return ((LocalTime) ValueUtils.getValueAsLocalTime(values.get(fieldName)));
|
return (ValueUtils.getValueAsLocalTime(values.get(fieldName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define some standard ways to format the value portion of a PossibleValueSource.
|
||||||
|
**
|
||||||
|
** Can be passed to short-cut {set,with}ValueFormatAndFields methods in QPossibleValueSource
|
||||||
|
** class, or the format & field properties can be extracted and passed to regular field-level setters.
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum PVSValueFormatAndFields
|
||||||
|
{
|
||||||
|
LABEL_ONLY("%s", "label"),
|
||||||
|
LABEL_PARENS_ID("%s (%s)", "label", "id"),
|
||||||
|
ID_COLON_LABEL("%s: %s", "id", "label");
|
||||||
|
|
||||||
|
|
||||||
|
private final String format;
|
||||||
|
private final List<String> fields;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
PVSValueFormatAndFields(String format, String... fields)
|
||||||
|
{
|
||||||
|
this.format = format;
|
||||||
|
this.fields = List.of(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for format
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getFormat()
|
||||||
|
{
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fields
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<String> getFields()
|
||||||
|
{
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
** Interface to be implemented by enums which can be used as a PossibleValueSource.
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public interface PossibleValueEnum<T>
|
public interface PossibleValueEnum<T>
|
||||||
|
@ -25,44 +25,24 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Meta-data to represent a single field in a table.
|
** Meta-data to represent a "Possible value" - e.g., a translation of a foreign
|
||||||
|
** key and/or a limited set of "possible values" for a field (e.g., from a foreign
|
||||||
|
** table or an enum).
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QPossibleValueSource
|
public class QPossibleValueSource
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private QPossibleValueSourceType type;
|
private QPossibleValueSourceType type;
|
||||||
private QFieldType idType = QFieldType.INTEGER;
|
|
||||||
|
|
||||||
private String valueFormat = ValueFormat.DEFAULT;
|
private String valueFormat = PVSValueFormatAndFields.LABEL_ONLY.getFormat();
|
||||||
private List<String> valueFields = ValueFields.DEFAULT;
|
private List<String> valueFields = PVSValueFormatAndFields.LABEL_ONLY.getFields();
|
||||||
private String valueFormatIfNotFound = null;
|
private String valueFormatIfNotFound = null;
|
||||||
private List<String> valueFieldsIfNotFound = 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?"
|
// todo - optimization hints, such as "table is static, fully cache" or "table is small, so we can pull the whole thing into memory?"
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
@ -154,40 +134,6 @@ public class QPossibleValueSource
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** 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
|
** Getter for valueFormat
|
||||||
**
|
**
|
||||||
@ -407,6 +353,9 @@ public class QPossibleValueSource
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
** This is the easiest way to add the values from an enum to a PossibleValueSource.
|
||||||
|
** Make sure the enum implements PossibleValueEnum - then call as:
|
||||||
|
** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public <T extends PossibleValueEnum<?>> QPossibleValueSource withValuesFromEnum(T[] values)
|
public <T extends PossibleValueEnum<?>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||||
@ -453,4 +402,26 @@ public class QPossibleValueSource
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
|
||||||
|
{
|
||||||
|
this.valueFormat = valueFormatAndFields.getFormat();
|
||||||
|
this.valueFields = valueFormatAndFields.getFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QPossibleValueSource withValueFormatAndFields(PVSValueFormatAndFields valueFormatAndFields)
|
||||||
|
{
|
||||||
|
setValueFormatAndFields(valueFormatAndFields);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||||
@ -85,6 +86,17 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return ("QTableMetaData[" + name + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -451,6 +463,16 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QTableMetaData withCustomizer(TableCustomizer tableCustomizer, QCodeReference customizer)
|
||||||
|
{
|
||||||
|
return (withCustomizer(tableCustomizer.getRole(), customizer));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -174,10 +174,13 @@ public class MemoryRecordStore
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Integer count(CountInput input)
|
public Integer count(CountInput input)
|
||||||
{
|
{
|
||||||
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
QueryInput queryInput = new QueryInput(input.getInstance());
|
||||||
List<QRecord> records = new ArrayList<>(tableData.values());
|
queryInput.setSession(input.getSession());
|
||||||
// todo - filtering (call query)
|
queryInput.setTableName(input.getTableName());
|
||||||
return (records.size());
|
queryInput.setFilter(input.getFilter());
|
||||||
|
List<QRecord> queryResult = query(queryInput);
|
||||||
|
|
||||||
|
return (queryResult.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -194,25 +197,41 @@ public class MemoryRecordStore
|
|||||||
|
|
||||||
QTableMetaData table = input.getTable();
|
QTableMetaData table = input.getTable();
|
||||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// grab the next unique serial to use //
|
||||||
|
////////////////////////////////////////
|
||||||
Integer nextSerial = nextSerials.get(table.getName());
|
Integer nextSerial = nextSerials.get(table.getName());
|
||||||
if(nextSerial == null)
|
if(nextSerial == null)
|
||||||
{
|
{
|
||||||
nextSerial = 1;
|
nextSerial = 1;
|
||||||
|
}
|
||||||
|
|
||||||
while(tableData.containsKey(nextSerial))
|
while(tableData.containsKey(nextSerial))
|
||||||
{
|
{
|
||||||
nextSerial++;
|
nextSerial++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
List<QRecord> outputRecords = new ArrayList<>();
|
List<QRecord> outputRecords = new ArrayList<>();
|
||||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||||
for(QRecord record : input.getRecords())
|
for(QRecord record : input.getRecords())
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// set the next serial in the record if needed //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
if(record.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER))
|
if(record.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER))
|
||||||
{
|
{
|
||||||
record.setValue(primaryKeyField.getName(), nextSerial++);
|
record.setValue(primaryKeyField.getName(), nextSerial++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure that if the user supplied a serial, greater than the one we had, that we skip ahead //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(primaryKeyField.getType().equals(QFieldType.INTEGER) && record.getValueInteger(primaryKeyField.getName()) > nextSerial)
|
||||||
|
{
|
||||||
|
nextSerial = record.getValueInteger(primaryKeyField.getName()) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
tableData.put(record.getValue(primaryKeyField.getName()), record);
|
tableData.put(record.getValue(primaryKeyField.getName()), record);
|
||||||
if(returnInsertedRecords)
|
if(returnInsertedRecords)
|
||||||
{
|
{
|
||||||
@ -220,6 +239,8 @@ public class MemoryRecordStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextSerials.put(table.getName(), nextSerial);
|
||||||
|
|
||||||
return (outputRecords);
|
return (outputRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,10 +277,6 @@ public class MemoryRecordStore
|
|||||||
outputRecords.add(record);
|
outputRecords.add(record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
outputRecords.add(record);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (outputRecords);
|
return (outputRecords);
|
||||||
|
@ -423,7 +423,7 @@ public class ValueUtils
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static Object getValueAsLocalTime(Serializable value)
|
public static LocalTime getValueAsLocalTime(Serializable value)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -30,25 +30,39 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
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.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** Unit test for QPossibleValueTranslator
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QPossibleValueTranslatorTest
|
public class QPossibleValueTranslatorTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach()
|
||||||
|
{
|
||||||
|
MemoryRecordStore.getInstance().reset();
|
||||||
|
MemoryRecordStore.resetStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -90,22 +104,20 @@ public class QPossibleValueTranslatorTest
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// assert the LABEL_ONLY format (when called out specifically) //
|
// assert the LABEL_ONLY format (when called out specifically) //
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_ONLY);
|
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||||
possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_ONLY);
|
|
||||||
assertEquals("IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
assertEquals("IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||||
|
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// assert the LABEL_PARAMS_ID format //
|
// assert the LABEL_PARAMS_ID format //
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_PARENS_ID);
|
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
|
||||||
possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_PARENS_ID);
|
|
||||||
assertEquals("IL (1)", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
assertEquals("IL (1)", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
// assert the ID_COLON_LABEL format //
|
// assert the ID_COLON_LABEL format //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.ID_COLON_LABEL);
|
possibleValueSource.setValueFormat(PVSValueFormatAndFields.ID_COLON_LABEL.getFormat());
|
||||||
possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.ID_COLON_LABEL);
|
possibleValueSource.setValueFields(PVSValueFormatAndFields.ID_COLON_LABEL.getFields());
|
||||||
assertEquals("1: IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
assertEquals("1: IL", possibleValueTranslator.translatePossibleValue(stateField, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +168,7 @@ public class QPossibleValueTranslatorTest
|
|||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// assert the LABEL_PARAMS_ID format //
|
// assert the LABEL_PARAMS_ID format //
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
possibleValueSource.setValueFormat(QPossibleValueSource.ValueFormat.LABEL_PARENS_ID);
|
possibleValueSource.setValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID);
|
||||||
possibleValueSource.setValueFields(QPossibleValueSource.ValueFields.LABEL_PARENS_ID);
|
|
||||||
assertEquals("Circle (3)", possibleValueTranslator.translatePossibleValue(shapeField, 3));
|
assertEquals("Circle (3)", possibleValueTranslator.translatePossibleValue(shapeField, 3));
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
@ -195,19 +206,124 @@ public class QPossibleValueTranslatorTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Make sure that if we have 2 different PVS's pointed at the same 1 table,
|
||||||
|
** that we avoid re-doing queries, and that we actually get different (formatted) values.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPossibleValueTableMultiplePvsForATable() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QTableMetaData shapeTable = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||||
|
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// define a second version of the Shape PVS, with a unique format //
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("shapeV2")
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_SHAPE)
|
||||||
|
.withValueFormat("%d: %s")
|
||||||
|
.withValueFields(List.of("id", "label"))
|
||||||
|
);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// use that PVS in a new column on the person table //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
personTable.addField(new QFieldMetaData("currentShapeId", QFieldType.INTEGER)
|
||||||
|
.withPossibleValueSourceName("shapeV2")
|
||||||
|
);
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// insert the list of shapes //
|
||||||
|
///////////////////////////////
|
||||||
|
List<QRecord> shapeRecords = List.of(
|
||||||
|
new QRecord().withTableName(shapeTable.getName()).withValue("id", 1).withValue("name", "Triangle"),
|
||||||
|
new QRecord().withTableName(shapeTable.getName()).withValue("id", 2).withValue("name", "Square"),
|
||||||
|
new QRecord().withTableName(shapeTable.getName()).withValue("id", 3).withValue("name", "Circle"));
|
||||||
|
InsertInput insertInput = new InsertInput(qInstance);
|
||||||
|
insertInput.setSession(new QSession());
|
||||||
|
insertInput.setTableName(shapeTable.getName());
|
||||||
|
insertInput.setRecords(shapeRecords);
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
// define a list of persons pointing at those shapes //
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
List<QRecord> personRecords = List.of(
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 2),
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 1).withValue("currentShapeId", 3),
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3),
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue("favoriteShapeId", 2).withValue("currentShapeId", 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// translate the PVS's //
|
||||||
|
/////////////////////////
|
||||||
|
MemoryRecordStore.setCollectStatistics(true);
|
||||||
|
new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// assert only 1 query was ran //
|
||||||
|
/////////////////////////////////
|
||||||
|
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// assert expected values and formats //
|
||||||
|
////////////////////////////////////////
|
||||||
|
assertEquals("Triangle", personRecords.get(0).getDisplayValue("favoriteShapeId"));
|
||||||
|
assertEquals("2: Square", personRecords.get(0).getDisplayValue("currentShapeId"));
|
||||||
|
assertEquals("Triangle", personRecords.get(1).getDisplayValue("favoriteShapeId"));
|
||||||
|
assertEquals("3: Circle", personRecords.get(1).getDisplayValue("currentShapeId"));
|
||||||
|
assertEquals("Square", personRecords.get(2).getDisplayValue("favoriteShapeId"));
|
||||||
|
assertEquals("3: Circle", personRecords.get(2).getDisplayValue("currentShapeId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Make sure that if we have 2 different PVS's pointed at the same 1 table,
|
||||||
|
** that we avoid re-doing queries, and that we actually get different (formatted) values.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCustomPossibleValue() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
String fieldName = "customValue";
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// define a list of persons with values in the custom field //
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
List<QRecord> personRecords = List.of(
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 1),
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, 2),
|
||||||
|
new QRecord().withTableName(TestUtils.TABLE_NAME_PERSON).withValue(fieldName, "Buckle my shoe")
|
||||||
|
);
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// translate the PVS's //
|
||||||
|
/////////////////////////
|
||||||
|
new QPossibleValueTranslator(qInstance, new QSession()).translatePossibleValuesInRecords(personTable, personRecords);
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// assert expected values and formats //
|
||||||
|
////////////////////////////////////////
|
||||||
|
assertEquals("Custom[1]", personRecords.get(0).getDisplayValue(fieldName));
|
||||||
|
assertEquals("Custom[2]", personRecords.get(1).getDisplayValue(fieldName));
|
||||||
|
assertEquals("Custom[Buckle my shoe]", personRecords.get(2).getDisplayValue(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testSetDisplayValuesInRecords()
|
void testSetDisplayValuesInRecords()
|
||||||
{
|
{
|
||||||
QTableMetaData table = new QTableMetaData()
|
QTableMetaData table = TestUtils.defineTablePerson();
|
||||||
.withRecordLabelFormat("%s %s")
|
|
||||||
.withRecordLabelFields("firstName", "lastName")
|
|
||||||
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
|
|
||||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
|
||||||
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
|
||||||
.withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(TestUtils.POSSIBLE_VALUE_SOURCE_STATE));
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// first, make sure it doesn't crash with null or empty inputs //
|
// first, make sure it doesn't crash with null or empty inputs //
|
||||||
|
@ -292,6 +292,11 @@ class CsvToQRecordAdapterTest
|
|||||||
void testByteOrderMarker()
|
void testByteOrderMarker()
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - there's a zero-width non-breaking-space character (0xFEFF or some-such) //
|
||||||
|
// at the start of this string!! You may not be able to see it, depending on where you view this file. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
||||||
id,firstName
|
id,firstName
|
||||||
1,John""", TestUtils.defineTablePerson(), null);
|
1,John""", TestUtils.defineTablePerson(), null);
|
||||||
|
@ -140,6 +140,7 @@ class QInstanceEnricherTest
|
|||||||
assertEquals("Field 20", QInstanceEnricher.nameToLabel("field20"));
|
assertEquals("Field 20", QInstanceEnricher.nameToLabel("field20"));
|
||||||
assertEquals("Something USA", QInstanceEnricher.nameToLabel("somethingUSA"));
|
assertEquals("Something USA", QInstanceEnricher.nameToLabel("somethingUSA"));
|
||||||
assertEquals("Number 1 Dad", QInstanceEnricher.nameToLabel("number1Dad"));
|
assertEquals("Number 1 Dad", QInstanceEnricher.nameToLabel("number1Dad"));
|
||||||
|
assertEquals("Number 417 Dad", QInstanceEnricher.nameToLabel("number417Dad"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,9 +27,14 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||||
@ -114,12 +119,13 @@ class QInstanceValidatorTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_validateNullTables()
|
public void test_validateNullTables()
|
||||||
{
|
{
|
||||||
assertValidationFailureReasonsAllowingExtraReasons((qInstance) ->
|
assertValidationFailureReasons((qInstance) ->
|
||||||
{
|
{
|
||||||
qInstance.setTables(null);
|
qInstance.setTables(null);
|
||||||
qInstance.setProcesses(null);
|
qInstance.setProcesses(null);
|
||||||
},
|
},
|
||||||
"At least 1 table must be defined");
|
"At least 1 table must be defined",
|
||||||
|
"Unrecognized table shape for possibleValueSource shape");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -131,12 +137,13 @@ class QInstanceValidatorTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_validateEmptyTables()
|
public void test_validateEmptyTables()
|
||||||
{
|
{
|
||||||
assertValidationFailureReasonsAllowingExtraReasons((qInstance) ->
|
assertValidationFailureReasons((qInstance) ->
|
||||||
{
|
{
|
||||||
qInstance.setTables(new HashMap<>());
|
qInstance.setTables(new HashMap<>());
|
||||||
qInstance.setProcesses(new HashMap<>());
|
qInstance.setProcesses(new HashMap<>());
|
||||||
},
|
},
|
||||||
"At least 1 table must be defined");
|
"At least 1 table must be defined",
|
||||||
|
"Unrecognized table shape for possibleValueSource shape");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -191,7 +198,6 @@ class QInstanceValidatorTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -265,6 +271,138 @@ class QInstanceValidatorTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTableCustomizers()
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference()),
|
||||||
|
"missing a code reference name", "missing a code type");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(null, QCodeType.JAVA, null)),
|
||||||
|
"missing a code reference name");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("", QCodeType.JAVA, null)),
|
||||||
|
"missing a code reference name");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", null, null)),
|
||||||
|
"missing a code type");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference("Test", QCodeType.JAVA, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"Class for CodeReference could not be found");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerWithNoVoidConstructor.class, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"Instance of CodeReference could not be created");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerThatIsNotAFunction.class, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"CodeReference could not be casted");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameters.class, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"Error validating customizer type parameters");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter1.class, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"Error validating customizer type parameters");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerFunctionWithIncorrectTypeParameter2.class, QCodeUsage.CUSTOMIZER)),
|
||||||
|
"Error validating customizer type parameters");
|
||||||
|
|
||||||
|
assertValidationSuccess((qInstance) -> qInstance.getTable("person").withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(CustomizerValid.class, QCodeUsage.CUSTOMIZER)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerWithNoVoidConstructor
|
||||||
|
{
|
||||||
|
public CustomizerWithNoVoidConstructor(boolean b)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerWithOnlyPrivateConstructor
|
||||||
|
{
|
||||||
|
private CustomizerWithOnlyPrivateConstructor()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerThatIsNotAFunction
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerFunctionWithIncorrectTypeParameters implements Function<String, String>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String apply(String s)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerFunctionWithIncorrectTypeParameter1 implements Function<String, QRecord>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public QRecord apply(String s)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerFunctionWithIncorrectTypeParameter2 implements Function<QRecord, String>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String apply(QRecord s)
|
||||||
|
{
|
||||||
|
return "Test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class CustomizerValid implements Function<QRecord, QRecord>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public QRecord apply(QRecord record)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test that if a field specifies a backend that doesn't exist, that it fails.
|
** Test that if a field specifies a backend that doesn't exist, that it fails.
|
||||||
**
|
**
|
||||||
@ -443,18 +581,6 @@ class QInstanceValidatorTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testPossibleValueSourceMissingIdType()
|
|
||||||
{
|
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE).setIdType(null),
|
|
||||||
"Missing an idType for possibleValueSource");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -516,7 +642,9 @@ class QInstanceValidatorTest
|
|||||||
"is missing a customCodeReference");
|
"is missing a customCodeReference");
|
||||||
|
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()),
|
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()),
|
||||||
"not a possibleValueProvider");
|
"not a possibleValueProvider",
|
||||||
|
"missing a code reference name",
|
||||||
|
"missing a code type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -561,7 +689,8 @@ class QInstanceValidatorTest
|
|||||||
{
|
{
|
||||||
if(!allowExtraReasons)
|
if(!allowExtraReasons)
|
||||||
{
|
{
|
||||||
assertEquals(reasons.length, e.getReasons().size(), "Expected number of validation failure reasons\nExpected: " + String.join(",", reasons) + "\nActual: " + e.getReasons());
|
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
|
||||||
|
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons) + "\nActual reasons: " + e.getReasons());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(String reason : reasons)
|
for(String reason : reasons)
|
||||||
@ -573,6 +702,25 @@ class QInstanceValidatorTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Assert that an instance is valid!
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertValidationSuccess(Consumer<QInstance> setup)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
setup.accept(qInstance);
|
||||||
|
new QInstanceValidator().validate(qInstance);
|
||||||
|
}
|
||||||
|
catch(QInstanceValidationException e)
|
||||||
|
{
|
||||||
|
fail("Expected no validation errors, but received: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** utility method for asserting that a specific reason string is found within
|
** utility method for asserting that a specific reason string is found within
|
||||||
** the list of reasons in the QInstanceValidationException.
|
** the list of reasons in the QInstanceValidationException.
|
||||||
|
@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
@ -36,6 +36,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
@ -68,6 +71,7 @@ class MemoryBackendModuleTest
|
|||||||
void beforeAndAfter()
|
void beforeAndAfter()
|
||||||
{
|
{
|
||||||
MemoryRecordStore.getInstance().reset();
|
MemoryRecordStore.getInstance().reset();
|
||||||
|
MemoryRecordStore.resetStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -122,8 +126,6 @@ class MemoryBackendModuleTest
|
|||||||
|
|
||||||
assertEquals(3, new CountAction().execute(countInput).getCount());
|
assertEquals(3, new CountAction().execute(countInput).getCount());
|
||||||
|
|
||||||
// todo - filters in query
|
|
||||||
|
|
||||||
//////////////////
|
//////////////////
|
||||||
// do an update //
|
// do an update //
|
||||||
//////////////////
|
//////////////////
|
||||||
@ -152,6 +154,24 @@ class MemoryBackendModuleTest
|
|||||||
|
|
||||||
assertEquals(3, new CountAction().execute(countInput).getCount());
|
assertEquals(3, new CountAction().execute(countInput).getCount());
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// do a filtered query //
|
||||||
|
/////////////////////////
|
||||||
|
queryInput = new QueryInput(qInstance);
|
||||||
|
queryInput.setSession(session);
|
||||||
|
queryInput.setTableName(table.getName());
|
||||||
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 3))));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size());
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// do a filtered count //
|
||||||
|
/////////////////////////
|
||||||
|
countInput.setFilter(queryInput.getFilter());
|
||||||
|
assertEquals(2, new CountAction().execute(countInput).getCount());
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// do a delete //
|
// do a delete //
|
||||||
/////////////////
|
/////////////////
|
||||||
@ -173,6 +193,57 @@ class MemoryBackendModuleTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSerials() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||||
|
QSession session = new QSession();
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// do an insert //
|
||||||
|
//////////////////
|
||||||
|
InsertInput insertInput = new InsertInput(qInstance);
|
||||||
|
insertInput.setSession(session);
|
||||||
|
insertInput.setTableName(table.getName());
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 1")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 2")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 3")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput(qInstance);
|
||||||
|
queryInput.setSession(new QSession());
|
||||||
|
queryInput.setTableName(table.getName());
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||||
|
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 4).withValue("name", "Shape 4")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(4)));
|
||||||
|
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("id", 6).withValue("name", "Shape 6")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(6)));
|
||||||
|
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withTableName(table.getName()).withValue("name", "Shape 7")));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -215,7 +286,7 @@ class MemoryBackendModuleTest
|
|||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// add a customizer to the table //
|
// add a customizer to the table //
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
table.withCustomizer(Customizers.POST_QUERY_RECORD, new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
|
table.withCustomizer(TableCustomizers.POST_QUERY_RECORD.getRole(), new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
|
||||||
|
|
||||||
//////////////////
|
//////////////////
|
||||||
// do an insert //
|
// do an insert //
|
||||||
|
@ -206,12 +206,10 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
|||||||
{
|
{
|
||||||
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record ->
|
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record ->
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// since the CSV adapter is the one responsible for putting records into the pipe (rather than the queryOutput), //
|
// Before the records go into the pipe, make sure their backend details are added to them //
|
||||||
// we must do some of QueryOutput's normal job here - and run the runPostQueryRecordCustomizer //
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
addBackendDetailsToRecord(record, file);
|
addBackendDetailsToRecord(record, file);
|
||||||
queryOutput.runPostQueryRecordCustomizer(record);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -308,7 +306,7 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
|
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
|
||||||
{
|
{
|
||||||
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemCustomizers.POST_READ_FILE);
|
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole());
|
||||||
if(optionalCustomizer.isEmpty())
|
if(optionalCustomizer.isEmpty())
|
||||||
{
|
{
|
||||||
return (fileContents);
|
return (fileContents);
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.module.filesystem.base.actions;
|
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Standard place where the names of QQQ Customization points for filesystem-based
|
|
||||||
** backends are defined.
|
|
||||||
*******************************************************************************/
|
|
||||||
public interface FilesystemCustomizers extends Customizers
|
|
||||||
{
|
|
||||||
String POST_READ_FILE = "postReadFile";
|
|
||||||
}
|
|
@ -0,0 +1,72 @@
|
|||||||
|
package com.kingsrook.qqq.backend.module.filesystem.base.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum FilesystemTableCustomizers
|
||||||
|
{
|
||||||
|
POST_READ_FILE(new TableCustomizer("postReadFile", Function.class, ((Object x) ->
|
||||||
|
{
|
||||||
|
Function<String, String> function = (Function<String, String>) x;
|
||||||
|
String output = function.apply(new String());
|
||||||
|
})));
|
||||||
|
|
||||||
|
private final TableCustomizer tableCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
FilesystemTableCustomizers(TableCustomizer tableCustomizer)
|
||||||
|
{
|
||||||
|
this.tableCustomizer = tableCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get the FilesystemTableCustomer for a given role (e.g., the role used in meta-data, not
|
||||||
|
** the enum-constant name).
|
||||||
|
*******************************************************************************/
|
||||||
|
public static FilesystemTableCustomizers forRole(String name)
|
||||||
|
{
|
||||||
|
for(FilesystemTableCustomizers value : values())
|
||||||
|
{
|
||||||
|
if(value.tableCustomizer.getRole().equals(name))
|
||||||
|
{
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for tableCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public TableCustomizer getTableCustomizer()
|
||||||
|
{
|
||||||
|
return tableCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** get the role from the tableCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRole()
|
||||||
|
{
|
||||||
|
return (tableCustomizer.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -91,7 +91,7 @@ public class FilesystemSyncStep implements BackendStep
|
|||||||
String sourceFileName = sourceEntry.getKey();
|
String sourceFileName = sourceEntry.getKey();
|
||||||
if(!archiveFiles.contains(sourceFileName))
|
if(!archiveFiles.contains(sourceFileName))
|
||||||
{
|
{
|
||||||
LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable.getName() + "] and [" + processingTable.getName() + "]");
|
LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable + "] and [" + processingTable + "]");
|
||||||
InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue());
|
InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue());
|
||||||
byte[] bytes = inputStream.readAllBytes();
|
byte[] bytes = inputStream.readAllBytes();
|
||||||
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.module.filesystem;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public class FilesystemModuleJunitExtension // implements Extension, BeforeAllCallback, AfterAllCallback
|
|
||||||
{
|
|
||||||
}
|
|
@ -22,22 +22,16 @@
|
|||||||
package com.kingsrook.qqq.backend.module.filesystem.local.actions;
|
package com.kingsrook.qqq.backend.module.filesystem.local.actions;
|
||||||
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemCustomizers;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** Unit test for FilesystemCountAction
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class FilesystemCountActionTest extends FilesystemActionTest
|
public class FilesystemCountActionTest extends FilesystemActionTest
|
||||||
{
|
{
|
||||||
@ -55,35 +49,4 @@ public class FilesystemCountActionTest extends FilesystemActionTest
|
|||||||
Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
|
Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
public void testCountWithFileCustomizer() throws QException
|
|
||||||
{
|
|
||||||
CountInput countInput = new CountInput();
|
|
||||||
QInstance instance = TestUtils.defineInstance();
|
|
||||||
|
|
||||||
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
|
||||||
table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
|
|
||||||
|
|
||||||
countInput.setInstance(instance);
|
|
||||||
countInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
|
||||||
CountOutput countOutput = new FilesystemCountAction().execute(countInput);
|
|
||||||
Assertions.assertEquals(3, countOutput.getCount(), "Unfiltered count should find all rows");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class ValueUpshifter implements Function<String, String>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public String apply(String s)
|
|
||||||
{
|
|
||||||
return (s.replaceAll("kingsrook.com", "KINGSROOK.COM"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemCustomizers;
|
import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemTableCustomizers;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ public class FilesystemQueryActionTest extends FilesystemActionTest
|
|||||||
QInstance instance = TestUtils.defineInstance();
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
|
||||||
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
||||||
table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
|
table.withCustomizer(FilesystemTableCustomizers.POST_READ_FILE.getRole(), new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
|
||||||
|
|
||||||
queryInput.setInstance(instance);
|
queryInput.setInstance(instance);
|
||||||
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
||||||
|
@ -197,6 +197,12 @@ public class QJavalinImplementation
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
QInstance newQInstance = qInstanceHotSwapSupplier.get();
|
QInstance newQInstance = qInstanceHotSwapSupplier.get();
|
||||||
|
if(newQInstance == null)
|
||||||
|
{
|
||||||
|
LOG.warn("Got a null qInstance from hotSwapSupplier. Not hot-swapping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
new QInstanceValidator().validate(newQInstance);
|
new QInstanceValidator().validate(newQInstance);
|
||||||
QJavalinImplementation.qInstance = newQInstance;
|
QJavalinImplementation.qInstance = newQInstance;
|
||||||
LOG.info("Swapped qInstance");
|
LOG.info("Swapped qInstance");
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
package com.kingsrook.sampleapp;
|
package com.kingsrook.sampleapp;
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.frontend.picocli.QPicoCliImplementation;
|
import com.kingsrook.qqq.frontend.picocli.QPicoCliImplementation;
|
||||||
|
|
||||||
@ -37,7 +36,8 @@ public class SampleCli
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
new SampleCli().run(args);
|
int exitCode = new SampleCli().run(args);
|
||||||
|
System.exit(exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -45,31 +45,20 @@ public class SampleCli
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void run(String[] args)
|
int run(String[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int exitCode = runForExitCode(args);
|
QInstance qInstance = SampleMetaDataProvider.defineInstance();
|
||||||
System.exit(exitCode);
|
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
||||||
|
|
||||||
|
return (qPicoCliImplementation.runCli("my-sample-cli", args));
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.exit(-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
int runForExitCode(String[] args) throws QException
|
|
||||||
{
|
|
||||||
QInstance qInstance = SampleMetaDataProvider.defineInstance();
|
|
||||||
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
|
||||||
int exitCode = qPicoCliImplementation.runCli("my-sample-cli", args);
|
|
||||||
return exitCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
package com.kingsrook.sampleapp;
|
package com.kingsrook.sampleapp;
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||||
import io.javalin.Javalin;
|
import io.javalin.Javalin;
|
||||||
@ -71,6 +70,24 @@ public class SampleJavalinServer
|
|||||||
config.enableCorsForAllOrigins();
|
config.enableCorsForAllOrigins();
|
||||||
}).start(PORT);
|
}).start(PORT);
|
||||||
javalinService.routes(qJavalinImplementation.getRoutes());
|
javalinService.routes(qJavalinImplementation.getRoutes());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// set the server to hot-swap the q instance before all routes //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
QJavalinImplementation.setQInstanceHotSwapSupplier(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (SampleMetaDataProvider.defineInstance());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error hot-swapping meta data", e);
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
javalinService.before(QJavalinImplementation::hotSwapQInstance);
|
||||||
|
|
||||||
javalinService.after(ctx ->
|
javalinService.after(ctx ->
|
||||||
ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
|
ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ package com.kingsrook.sampleapp;
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -37,10 +38,21 @@ class SampleCliTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void test() throws QException
|
void testExitSuccess() throws QException
|
||||||
{
|
{
|
||||||
int exitCode = new SampleCli().runForExitCode(new String[] { "--meta-data" });
|
int exitCode = new SampleCli().run(new String[] { "--meta-data" });
|
||||||
assertEquals(0, exitCode);
|
assertEquals(0, exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNotExitSuccess() throws QException
|
||||||
|
{
|
||||||
|
int exitCode = new SampleCli().run(new String[] { "asdfasdf" });
|
||||||
|
assertNotEquals(0, exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user