mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Checkpoint - version of processes that pass a unit test - basic version of stateProviderInterface with memory & temp-file implementations
This commit is contained in:
@ -21,6 +21,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -61,9 +62,7 @@ public class RunFunctionAction
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// load and run the user-defined code that actually does the work //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
RunFunctionResult runFunctionResult = runFunctionBodyCode(function.getCode(), runFunctionRequest);
|
||||
|
||||
return (runFunctionResult);
|
||||
return (runFunctionBodyCode(function.getCode(), runFunctionRequest));
|
||||
}
|
||||
|
||||
|
||||
@ -88,11 +87,14 @@ public class RunFunctionAction
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Serializable> fieldValues = runFunctionRequest.getCallback().getFieldValues(fieldsToGet);
|
||||
for(Map.Entry<String, Serializable> entry : fieldValues.entrySet())
|
||||
if(!fieldsToGet.isEmpty())
|
||||
{
|
||||
runFunctionRequest.addValue(entry.getKey(), entry.getValue());
|
||||
// todo - check to make sure got values back?
|
||||
Map<String, Serializable> fieldValues = runFunctionRequest.getCallback().getFieldValues(fieldsToGet);
|
||||
for(Map.Entry<String, Serializable> entry : fieldValues.entrySet())
|
||||
{
|
||||
runFunctionRequest.addValue(entry.getKey(), entry.getValue());
|
||||
// todo - check to make sure got values back?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +110,7 @@ public class RunFunctionAction
|
||||
QRecordListMetaData recordListMetaData = inputMetaData.getRecordListMetaData();
|
||||
if(recordListMetaData != null)
|
||||
{
|
||||
if(runFunctionRequest.getRecords() == null)
|
||||
if(CollectionUtils.nullSafeIsEmpty(runFunctionRequest.getRecords()))
|
||||
{
|
||||
QueryRequest queryRequest = new QueryRequest(runFunctionRequest.getInstance());
|
||||
queryRequest.setSession(runFunctionRequest.getSession());
|
||||
@ -133,18 +135,19 @@ public class RunFunctionAction
|
||||
*******************************************************************************/
|
||||
private RunFunctionResult runFunctionBodyCode(QCodeReference code, RunFunctionRequest runFunctionRequest)
|
||||
{
|
||||
RunFunctionResult runFunctionResult;
|
||||
RunFunctionResult runFunctionResult = new RunFunctionResult();
|
||||
try
|
||||
{
|
||||
runFunctionResult.seedFromRequest(runFunctionRequest);
|
||||
|
||||
Class<?> codeClass = Class.forName(code.getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof FunctionBody))
|
||||
if(!(codeObject instanceof FunctionBody functionBodyCodeObject))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of FunctionBody"));
|
||||
}
|
||||
|
||||
FunctionBody functionBodyCodeObject = (FunctionBody) codeObject;
|
||||
runFunctionResult = functionBodyCodeObject.run(runFunctionRequest);
|
||||
functionBodyCodeObject.run(runFunctionRequest, runFunctionResult);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -7,12 +7,16 @@ package com.kingsrook.qqq.backend.core.actions;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDStateKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,23 +44,67 @@ public class RunProcessAction
|
||||
|
||||
RunProcessResult runProcessResult = new RunProcessResult();
|
||||
|
||||
UUIDStateKey stateKey = new UUIDStateKey();
|
||||
RunFunctionResult lastFunctionResult = null;
|
||||
|
||||
// todo - custom routing?
|
||||
List<QFunctionMetaData> functionList = process.getFunctionList();
|
||||
for(QFunctionMetaData function : functionList)
|
||||
{
|
||||
RunFunctionRequest runFunctionRequest = new RunFunctionRequest(runProcessRequest.getInstance());
|
||||
|
||||
if(lastFunctionResult != null)
|
||||
{
|
||||
loadState(stateKey, runFunctionRequest);
|
||||
}
|
||||
|
||||
runFunctionRequest.setProcessName(process.getName());
|
||||
runFunctionRequest.setFunctionName(function.getName());
|
||||
// todo - how does this work again? runFunctionRequest.setCallback(?);
|
||||
RunFunctionResult functionResult = new RunFunctionAction().execute(runFunctionRequest);
|
||||
if(functionResult.getError() != null)
|
||||
runFunctionRequest.setSession(runProcessRequest.getSession());
|
||||
runFunctionRequest.setCallback(runProcessRequest.getCallback());
|
||||
lastFunctionResult = new RunFunctionAction().execute(runFunctionRequest);
|
||||
if(lastFunctionResult.getError() != null)
|
||||
{
|
||||
runProcessResult.setError(functionResult.getError());
|
||||
runProcessResult.setError(lastFunctionResult.getError());
|
||||
break;
|
||||
}
|
||||
|
||||
storeState(stateKey, lastFunctionResult);
|
||||
}
|
||||
|
||||
if(lastFunctionResult != null)
|
||||
{
|
||||
runProcessResult.seedFromLastFunctionResult(lastFunctionResult);
|
||||
}
|
||||
|
||||
return (runProcessResult);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private StateProviderInterface getStateProvider()
|
||||
{
|
||||
// TODO - read this from somewhere in meta data eh?
|
||||
// return InMemoryStateProvider.getInstance();
|
||||
return TempFileStateProvider.getInstance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void storeState(UUIDStateKey stateKey, RunFunctionResult runFunctionResult)
|
||||
{
|
||||
getStateProvider().put(stateKey, runFunctionResult.getProcessState());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void loadState(UUIDStateKey stateKey, RunFunctionRequest runFunctionRequest)
|
||||
{
|
||||
ProcessState processState = getStateProvider().get(ProcessState.class, stateKey);
|
||||
runFunctionRequest.seedFromProcessState(processState);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,5 +17,5 @@ public interface FunctionBody
|
||||
/*******************************************************************************
|
||||
** TODO - document!
|
||||
*******************************************************************************/
|
||||
RunFunctionResult run(RunFunctionRequest runFunctionRequest);
|
||||
void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult);
|
||||
}
|
||||
|
@ -18,16 +18,11 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
|
||||
public class MockFunctionBody implements FunctionBody
|
||||
{
|
||||
@Override
|
||||
public RunFunctionResult run(RunFunctionRequest runFunctionRequest)
|
||||
public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult)
|
||||
{
|
||||
RunFunctionResult runFunctionResult = new RunFunctionResult();
|
||||
runFunctionResult.setRecords(runFunctionRequest.getRecords());
|
||||
|
||||
runFunctionResult.getRecords().forEach(r -> r.setValue("mockValue", "Ha ha!"));
|
||||
|
||||
runFunctionResult.setValues(runFunctionRequest.getValues());
|
||||
runFunctionResult.addValue("mockValue", "You so silly");
|
||||
|
||||
return (runFunctionResult);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProcessState implements Serializable
|
||||
{
|
||||
private List<QRecord> records = new ArrayList<>();
|
||||
private Map<String, Serializable> values = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for records
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for records
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for values
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getValues()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
|
||||
@ -22,10 +21,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData
|
||||
*******************************************************************************/
|
||||
public class RunFunctionRequest extends AbstractQRequest
|
||||
{
|
||||
private ProcessState processState;
|
||||
private String processName;
|
||||
private String functionName;
|
||||
private List<QRecord> records;
|
||||
private Map<String, Serializable> values;
|
||||
private QProcessCallback callback;
|
||||
|
||||
|
||||
@ -35,6 +33,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public RunFunctionRequest()
|
||||
{
|
||||
processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +44,19 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
public RunFunctionRequest(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** e.g., for steps after the first step in a process, seed the data in a run
|
||||
** function request from a process state.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void seedFromProcessState(ProcessState processState)
|
||||
{
|
||||
this.processState = processState;
|
||||
}
|
||||
|
||||
|
||||
@ -133,7 +145,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
return processState.getRecords();
|
||||
}
|
||||
|
||||
|
||||
@ -144,7 +156,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public void setRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
}
|
||||
|
||||
|
||||
@ -155,7 +167,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public RunFunctionRequest withRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -167,7 +179,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getValues()
|
||||
{
|
||||
return values;
|
||||
return processState.getValues();
|
||||
}
|
||||
|
||||
|
||||
@ -178,7 +190,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public void setValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
}
|
||||
|
||||
|
||||
@ -189,7 +201,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public RunFunctionRequest withValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -201,11 +213,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public RunFunctionRequest addValue(String fieldName, Serializable value)
|
||||
{
|
||||
if(this.values == null)
|
||||
{
|
||||
this.values = new HashMap<>();
|
||||
}
|
||||
this.values.put(fieldName, value);
|
||||
this.processState.getValues().put(fieldName, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -251,11 +259,7 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
*******************************************************************************/
|
||||
public Serializable getValue(String fieldName)
|
||||
{
|
||||
if(values == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return (values.get(fieldName));
|
||||
return (processState.getValues().get(fieldName));
|
||||
}
|
||||
|
||||
|
||||
@ -280,4 +284,15 @@ public class RunFunctionRequest extends AbstractQRequest
|
||||
return ((Integer) getValue(fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Accessor for processState - protected, because we generally want to access
|
||||
** its members through wrapper methods, we think
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected ProcessState getProcessState()
|
||||
{
|
||||
return processState;
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractQResult;
|
||||
@ -19,8 +18,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
public class RunFunctionResult extends AbstractQResult
|
||||
{
|
||||
private List<QRecord> records;
|
||||
private Map<String, Serializable> values;
|
||||
private ProcessState processState;
|
||||
private String error;
|
||||
|
||||
|
||||
@ -30,6 +28,18 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunFunctionResult()
|
||||
{
|
||||
this.processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** e.g., populate the process state (records, values) in this result object.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void seedFromRequest(RunFunctionRequest runFunctionRequest)
|
||||
{
|
||||
this.processState = runFunctionRequest.getProcessState();
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +50,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
return processState.getRecords();
|
||||
}
|
||||
|
||||
|
||||
@ -51,7 +61,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public void setRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +72,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunFunctionResult withRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -74,7 +84,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getValues()
|
||||
{
|
||||
return values;
|
||||
return processState.getValues();
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +95,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public void setValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
}
|
||||
|
||||
|
||||
@ -96,7 +106,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunFunctionResult withValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -108,11 +118,7 @@ public class RunFunctionResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunFunctionResult addValue(String fieldName, Serializable value)
|
||||
{
|
||||
if(this.values == null)
|
||||
{
|
||||
this.values = new HashMap<>();
|
||||
}
|
||||
this.values.put(fieldName, value);
|
||||
this.processState.getValues().put(fieldName, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -137,4 +143,15 @@ public class RunFunctionResult extends AbstractQResult
|
||||
{
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Accessor for processState
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessState getProcessState()
|
||||
{
|
||||
return processState;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractQResult;
|
||||
@ -19,8 +18,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
public class RunProcessResult extends AbstractQResult
|
||||
{
|
||||
private List<QRecord> records;
|
||||
private Map<String, Serializable> values;
|
||||
private ProcessState processState;
|
||||
private String error;
|
||||
|
||||
|
||||
@ -30,6 +28,19 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunProcessResult()
|
||||
{
|
||||
processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** e.g., populate the process state (records, values) in this result object from
|
||||
** the final function result
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void seedFromLastFunctionResult(RunFunctionResult runFunctionResult)
|
||||
{
|
||||
this.processState = runFunctionResult.getProcessState();
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +51,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
return processState.getRecords();
|
||||
}
|
||||
|
||||
|
||||
@ -51,7 +62,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public void setRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +73,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunProcessResult withRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
this.processState.setRecords(records);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -74,7 +85,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getValues()
|
||||
{
|
||||
return values;
|
||||
return processState.getValues();
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +96,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public void setValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
}
|
||||
|
||||
|
||||
@ -96,7 +107,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunProcessResult withValues(Map<String, Serializable> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.processState.setValues(values);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -108,11 +119,7 @@ public class RunProcessResult extends AbstractQResult
|
||||
*******************************************************************************/
|
||||
public RunProcessResult addValue(String fieldName, Serializable value)
|
||||
{
|
||||
if(this.values == null)
|
||||
{
|
||||
this.values = new HashMap<>();
|
||||
}
|
||||
this.values.put(fieldName, value);
|
||||
this.processState.getValues().put(fieldName, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -19,7 +20,7 @@ import java.util.Map;
|
||||
* "Display values" (e.g., labels for possible values, or formatted numbers
|
||||
* (e.g., quantities with commas)) are in the displayValues map.
|
||||
*******************************************************************************/
|
||||
public class QRecord
|
||||
public class QRecord implements Serializable
|
||||
{
|
||||
private String tableName;
|
||||
//x private Serializable primaryKey;
|
||||
@ -102,8 +103,6 @@ public class QRecord
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//x /*******************************************************************************
|
||||
//x ** Getter for primaryKey
|
||||
//x **
|
||||
@ -113,8 +112,6 @@ public class QRecord
|
||||
//x return primaryKey;
|
||||
//x }
|
||||
|
||||
|
||||
|
||||
//x /*******************************************************************************
|
||||
//x ** Setter for primaryKey
|
||||
//x **
|
||||
@ -124,8 +121,6 @@ public class QRecord
|
||||
//x this.primaryKey = primaryKey;
|
||||
//x }
|
||||
|
||||
|
||||
|
||||
//x /*******************************************************************************
|
||||
//x ** Setter for primaryKey
|
||||
//x **
|
||||
@ -224,4 +219,15 @@ public class QRecord
|
||||
return ((Integer) values.get(fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalDate getValueDate(String fieldName)
|
||||
{
|
||||
return ((LocalDate) values.get(fieldName));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
public class QFunctionInputMetaData
|
||||
{
|
||||
private QRecordListMetaData recordListMetaData;
|
||||
private List<QFieldMetaData> fieldList;
|
||||
private List<QFieldMetaData> fieldList = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
|
@ -96,17 +96,6 @@ public class QRecordListMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecordListMetaData withFields(Map<String, QFieldMetaData> fields)
|
||||
{
|
||||
this.fields = fields;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -5,6 +5,11 @@
|
||||
package com.kingsrook.qqq.backend.core.modules.mock;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -41,7 +46,20 @@ public class MockQueryAction implements QueryInterface
|
||||
|
||||
for(String field : table.getFields().keySet())
|
||||
{
|
||||
record.setValue(field, "1");
|
||||
Serializable value = switch (table.getField(field).getType())
|
||||
{
|
||||
case STRING -> "Foo";
|
||||
case INTEGER -> 42;
|
||||
case DECIMAL -> new BigDecimal("3.14159");
|
||||
case DATE -> LocalDate.of(1970, Month.JANUARY, 1);
|
||||
case DATE_TIME -> LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0);
|
||||
case TEXT -> "Four score and seven years ago...";
|
||||
case HTML -> "<b>BOLD</b>";
|
||||
case PASSWORD -> "abc***234";
|
||||
default -> throw new IllegalStateException("Unexpected value: " + table.getField(field).getType());
|
||||
};
|
||||
|
||||
record.setValue(field, value);
|
||||
}
|
||||
|
||||
return rs;
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.state;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractStateKey
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Require all state keys to implement the equals method
|
||||
*
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public abstract boolean equals(Object that);
|
||||
|
||||
/*******************************************************************************
|
||||
** Require all state keys to implement the hashCode method
|
||||
*
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
/*******************************************************************************
|
||||
** Require all state keys to implement the toString method
|
||||
*
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.state;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton class that provides a (non-persistent!!) in-memory state provider.
|
||||
*******************************************************************************/
|
||||
public class InMemoryStateProvider implements StateProviderInterface
|
||||
{
|
||||
private static InMemoryStateProvider instance;
|
||||
|
||||
private final Map<AbstractStateKey, Object> map;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Private constructor for singleton.
|
||||
*******************************************************************************/
|
||||
private InMemoryStateProvider()
|
||||
{
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static InMemoryStateProvider getInstance()
|
||||
{
|
||||
if(instance == null)
|
||||
{
|
||||
instance = new InMemoryStateProvider();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public <T extends Serializable> void put(AbstractStateKey key, T data)
|
||||
{
|
||||
map.put(key, data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public <T extends Serializable> T get(Class<? extends T> type, AbstractStateKey key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return type.cast(map.get(key));
|
||||
}
|
||||
catch(ClassCastException cce)
|
||||
{
|
||||
throw new RuntimeException("Stored state value could not be cast to desired type", cce);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.state;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface StateProviderInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Put a block of data, under a key, into the state store.
|
||||
*******************************************************************************/
|
||||
<T extends Serializable> void put(AbstractStateKey key, T data);
|
||||
|
||||
/*******************************************************************************
|
||||
** Get a block of data, under a key, from the state store.
|
||||
*******************************************************************************/
|
||||
<T extends Serializable> T get(Class<? extends T> type, AbstractStateKey key);
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.state;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton class that provides a (non-persistent!!) in-memory state provider.
|
||||
*******************************************************************************/
|
||||
public class TempFileStateProvider implements StateProviderInterface
|
||||
{
|
||||
private static TempFileStateProvider instance;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Private constructor for singleton.
|
||||
*******************************************************************************/
|
||||
private TempFileStateProvider()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static TempFileStateProvider getInstance()
|
||||
{
|
||||
if(instance == null)
|
||||
{
|
||||
instance = new TempFileStateProvider();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public <T extends Serializable> void put(AbstractStateKey key, T data)
|
||||
{
|
||||
try
|
||||
{
|
||||
String json = JsonUtils.toJson(data);
|
||||
FileUtils.writeStringToFile(new File("/tmp/" + key.toString()), json);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
// todo better
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public <T extends Serializable> T get(Class<? extends T> type, AbstractStateKey key)
|
||||
{
|
||||
try
|
||||
{
|
||||
String json = FileUtils.readFileToString(new File("/tmp/" + key.toString()));
|
||||
return JsonUtils.toObject(json, type);
|
||||
}
|
||||
catch(ClassCastException cce)
|
||||
{
|
||||
throw new RuntimeException("Stored state value could not be cast to desired type", cce);
|
||||
}
|
||||
catch(IOException ie)
|
||||
{
|
||||
throw new RuntimeException("Error loading state from file", ie);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.state;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class UUIDStateKey extends AbstractStateKey
|
||||
{
|
||||
private final UUID uuid;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor - assigns a random UUID.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UUIDStateKey()
|
||||
{
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that lets you supply a UUID.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UUIDStateKey(UUID uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for uuid
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UUID getUuid()
|
||||
{
|
||||
return uuid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UUIDStateKey that = (UUIDStateKey) o;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return uuid.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RunProcessTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test() throws QException
|
||||
{
|
||||
TestCallback callback = new TestCallback();
|
||||
RunProcessRequest request = new RunProcessRequest(TestUtils.defineInstance());
|
||||
request.setSession(TestUtils.getMockSession());
|
||||
request.setProcessName("addToPeoplesAge");
|
||||
request.setCallback(callback);
|
||||
RunProcessResult result = new RunProcessAction().execute(request);
|
||||
assertNotNull(result);
|
||||
assertNull(result.getError());
|
||||
assertTrue(result.getRecords().stream().allMatch(r -> r.getValues().containsKey("age")), "records should have a value set by the process");
|
||||
assertTrue(result.getValues().containsKey("maxAge"), "process result object should have a value set by the first function in the process");
|
||||
assertTrue(result.getValues().containsKey("totalYearsAdded"), "process result object should have a value set by the second function in the process");
|
||||
assertTrue(callback.wasCalledForQueryFilter, "callback was used for query filter");
|
||||
assertTrue(callback.wasCalledForFieldValues, "callback was used for field values");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static class TestCallback implements QProcessCallback
|
||||
{
|
||||
private boolean wasCalledForQueryFilter = false;
|
||||
private boolean wasCalledForFieldValues = false;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
wasCalledForQueryFilter = true;
|
||||
return (new QQueryFilter());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields)
|
||||
{
|
||||
wasCalledForFieldValues = true;
|
||||
Map<String, Serializable> rs = new HashMap<>();
|
||||
if (fields.stream().anyMatch(f -> f.getName().equals("yearsToAdd")))
|
||||
{
|
||||
rs.put("yearsToAdd", 42);
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.interfaces.FunctionBody;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class AddAge implements FunctionBody
|
||||
{
|
||||
@Override
|
||||
public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult)
|
||||
{
|
||||
int totalYearsAdded = 0;
|
||||
Integer yearsToAdd = runFunctionRequest.getValueInteger("yearsToAdd");
|
||||
for(QRecord record : runFunctionRequest.getRecords())
|
||||
{
|
||||
Integer age = record.getValueInteger("age");
|
||||
age += yearsToAdd;
|
||||
totalYearsAdded += yearsToAdd;
|
||||
record.setValue("age", age);
|
||||
}
|
||||
runFunctionResult.addValue("totalYearsAdded", totalYearsAdded);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage;
|
||||
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import com.kingsrook.qqq.backend.core.interfaces.FunctionBody;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GetAgeStatistics implements FunctionBody
|
||||
{
|
||||
@Override
|
||||
public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult)
|
||||
{
|
||||
Integer min = null;
|
||||
Integer max = null;
|
||||
LocalDate now = LocalDate.now();
|
||||
for(QRecord record : runFunctionRequest.getRecords())
|
||||
{
|
||||
LocalDate birthDate = record.getValueDate("birthDate");
|
||||
Period until = birthDate.until(now);
|
||||
int age = until.getYears();
|
||||
record.setValue("age", age);
|
||||
System.out.println(birthDate + " -> " + age);
|
||||
min = (min == null || age < min) ? age : min;
|
||||
max = (max == null || age > max) ? age : max;
|
||||
}
|
||||
|
||||
runFunctionResult.addValue("minAge", min);
|
||||
runFunctionResult.addValue("maxAge", max);
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ public class TestUtils
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
|
||||
qInstance.addProcess(defineProcessGreetPeople());
|
||||
qInstance.addProcess(defineProcessAddToPeoplesAge());
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
@ -146,6 +147,53 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the "add to people's age" process
|
||||
**
|
||||
** Works on a list of rows from the person table.
|
||||
** - first function reports the current min & max age for all input rows.
|
||||
** - user is then prompted for how much they want to add to everyone.
|
||||
** - then the second function adds that value to their age, and shows the results.
|
||||
*******************************************************************************/
|
||||
private static QProcessMetaData defineProcessAddToPeoplesAge()
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName("addToPeoplesAge")
|
||||
.withTableName("person")
|
||||
.addFunction(new QFunctionMetaData()
|
||||
.withName("getAgeStatistics")
|
||||
.withCode(new QCodeReference()
|
||||
.withName("com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics")
|
||||
.withCodeType(QCodeType.JAVA)
|
||||
.withCodeUsage(QCodeUsage.FUNCTION))
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withRecordListMetaData(new QRecordListMetaData().withTableName("person")))
|
||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||
.withRecordListMetaData(new QRecordListMetaData()
|
||||
.withTableName("person")
|
||||
.addField(new QFieldMetaData("age", QFieldType.INTEGER)))
|
||||
.withFieldList(List.of(
|
||||
new QFieldMetaData("minAge", QFieldType.INTEGER),
|
||||
new QFieldMetaData("maxAge", QFieldType.INTEGER))))
|
||||
.withOutputView(new QOutputView()
|
||||
.withMessageField("outputMessage"))) // todo - wut?
|
||||
.addFunction(new QFunctionMetaData()
|
||||
.withName("addAge")
|
||||
.withCode(new QCodeReference()
|
||||
.withName("com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge")
|
||||
.withCodeType(QCodeType.JAVA)
|
||||
.withCodeUsage(QCodeUsage.FUNCTION))
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withFieldList(List.of(new QFieldMetaData("yearsToAdd", QFieldType.INTEGER))))
|
||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||
.withRecordListMetaData(new QRecordListMetaData()
|
||||
.withTableName("person")
|
||||
.addField(new QFieldMetaData("newAge", QFieldType.INTEGER)))));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user