Checkpoint - version of processes that pass a unit test - basic version of stateProviderInterface with memory & temp-file implementations

This commit is contained in:
Darin Kelkhoff
2022-05-03 19:12:58 -05:00
parent 49403dd6be
commit 909798436e
21 changed files with 785 additions and 90 deletions

View File

@ -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.QFunctionMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; 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.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 // // load and run the user-defined code that actually does the work //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
RunFunctionResult runFunctionResult = runFunctionBodyCode(function.getCode(), runFunctionRequest); return (runFunctionBodyCode(function.getCode(), runFunctionRequest));
return (runFunctionResult);
} }
@ -88,6 +87,8 @@ public class RunFunctionAction
} }
} }
if(!fieldsToGet.isEmpty())
{
Map<String, Serializable> fieldValues = runFunctionRequest.getCallback().getFieldValues(fieldsToGet); Map<String, Serializable> fieldValues = runFunctionRequest.getCallback().getFieldValues(fieldsToGet);
for(Map.Entry<String, Serializable> entry : fieldValues.entrySet()) for(Map.Entry<String, Serializable> entry : fieldValues.entrySet())
{ {
@ -95,6 +96,7 @@ public class RunFunctionAction
// todo - check to make sure got values back? // todo - check to make sure got values back?
} }
} }
}
@ -108,7 +110,7 @@ public class RunFunctionAction
QRecordListMetaData recordListMetaData = inputMetaData.getRecordListMetaData(); QRecordListMetaData recordListMetaData = inputMetaData.getRecordListMetaData();
if(recordListMetaData != null) if(recordListMetaData != null)
{ {
if(runFunctionRequest.getRecords() == null) if(CollectionUtils.nullSafeIsEmpty(runFunctionRequest.getRecords()))
{ {
QueryRequest queryRequest = new QueryRequest(runFunctionRequest.getInstance()); QueryRequest queryRequest = new QueryRequest(runFunctionRequest.getInstance());
queryRequest.setSession(runFunctionRequest.getSession()); queryRequest.setSession(runFunctionRequest.getSession());
@ -133,18 +135,19 @@ public class RunFunctionAction
*******************************************************************************/ *******************************************************************************/
private RunFunctionResult runFunctionBodyCode(QCodeReference code, RunFunctionRequest runFunctionRequest) private RunFunctionResult runFunctionBodyCode(QCodeReference code, RunFunctionRequest runFunctionRequest)
{ {
RunFunctionResult runFunctionResult; RunFunctionResult runFunctionResult = new RunFunctionResult();
try try
{ {
runFunctionResult.seedFromRequest(runFunctionRequest);
Class<?> codeClass = Class.forName(code.getName()); Class<?> codeClass = Class.forName(code.getName());
Object codeObject = codeClass.getConstructor().newInstance(); 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")); throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of FunctionBody"));
} }
FunctionBody functionBodyCodeObject = (FunctionBody) codeObject; functionBodyCodeObject.run(runFunctionRequest, runFunctionResult);
runFunctionResult = functionBodyCodeObject.run(runFunctionRequest);
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -7,12 +7,16 @@ package com.kingsrook.qqq.backend.core.actions;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.RunFunctionRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult; 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.RunProcessRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult; 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.QFunctionMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; 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(); RunProcessResult runProcessResult = new RunProcessResult();
UUIDStateKey stateKey = new UUIDStateKey();
RunFunctionResult lastFunctionResult = null;
// todo - custom routing? // todo - custom routing?
List<QFunctionMetaData> functionList = process.getFunctionList(); List<QFunctionMetaData> functionList = process.getFunctionList();
for(QFunctionMetaData function : functionList) for(QFunctionMetaData function : functionList)
{ {
RunFunctionRequest runFunctionRequest = new RunFunctionRequest(runProcessRequest.getInstance()); RunFunctionRequest runFunctionRequest = new RunFunctionRequest(runProcessRequest.getInstance());
if(lastFunctionResult != null)
{
loadState(stateKey, runFunctionRequest);
}
runFunctionRequest.setProcessName(process.getName()); runFunctionRequest.setProcessName(process.getName());
runFunctionRequest.setFunctionName(function.getName()); runFunctionRequest.setFunctionName(function.getName());
// todo - how does this work again? runFunctionRequest.setCallback(?); runFunctionRequest.setSession(runProcessRequest.getSession());
RunFunctionResult functionResult = new RunFunctionAction().execute(runFunctionRequest); runFunctionRequest.setCallback(runProcessRequest.getCallback());
if(functionResult.getError() != null) lastFunctionResult = new RunFunctionAction().execute(runFunctionRequest);
if(lastFunctionResult.getError() != null)
{ {
runProcessResult.setError(functionResult.getError()); runProcessResult.setError(lastFunctionResult.getError());
break; break;
} }
storeState(stateKey, lastFunctionResult);
}
if(lastFunctionResult != null)
{
runProcessResult.seedFromLastFunctionResult(lastFunctionResult);
} }
return (runProcessResult); 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);
}
} }

View File

@ -17,5 +17,5 @@ public interface FunctionBody
/******************************************************************************* /*******************************************************************************
** TODO - document! ** TODO - document!
*******************************************************************************/ *******************************************************************************/
RunFunctionResult run(RunFunctionRequest runFunctionRequest); void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult);
} }

View File

@ -18,16 +18,11 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
public class MockFunctionBody implements FunctionBody public class MockFunctionBody implements FunctionBody
{ {
@Override @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.getRecords().forEach(r -> r.setValue("mockValue", "Ha ha!"));
runFunctionResult.setValues(runFunctionRequest.getValues()); runFunctionResult.setValues(runFunctionRequest.getValues());
runFunctionResult.addValue("mockValue", "You so silly"); runFunctionResult.addValue("mockValue", "You so silly");
return (runFunctionResult);
} }
} }

View File

@ -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;
}
}

View File

@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; 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 public class RunFunctionRequest extends AbstractQRequest
{ {
private ProcessState processState;
private String processName; private String processName;
private String functionName; private String functionName;
private List<QRecord> records;
private Map<String, Serializable> values;
private QProcessCallback callback; private QProcessCallback callback;
@ -35,6 +33,7 @@ public class RunFunctionRequest extends AbstractQRequest
*******************************************************************************/ *******************************************************************************/
public RunFunctionRequest() public RunFunctionRequest()
{ {
processState = new ProcessState();
} }
@ -45,6 +44,19 @@ public class RunFunctionRequest extends AbstractQRequest
public RunFunctionRequest(QInstance instance) public RunFunctionRequest(QInstance instance)
{ {
super(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() public List<QRecord> getRecords()
{ {
return records; return processState.getRecords();
} }
@ -144,7 +156,7 @@ public class RunFunctionRequest extends AbstractQRequest
*******************************************************************************/ *******************************************************************************/
public void setRecords(List<QRecord> records) 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) public RunFunctionRequest withRecords(List<QRecord> records)
{ {
this.records = records; this.processState.setRecords(records);
return (this); return (this);
} }
@ -167,7 +179,7 @@ public class RunFunctionRequest extends AbstractQRequest
*******************************************************************************/ *******************************************************************************/
public Map<String, Serializable> getValues() 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) 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) public RunFunctionRequest withValues(Map<String, Serializable> values)
{ {
this.values = values; this.processState.setValues(values);
return (this); return (this);
} }
@ -201,11 +213,7 @@ public class RunFunctionRequest extends AbstractQRequest
*******************************************************************************/ *******************************************************************************/
public RunFunctionRequest addValue(String fieldName, Serializable value) public RunFunctionRequest addValue(String fieldName, Serializable value)
{ {
if(this.values == null) this.processState.getValues().put(fieldName, value);
{
this.values = new HashMap<>();
}
this.values.put(fieldName, value);
return (this); return (this);
} }
@ -251,11 +259,7 @@ public class RunFunctionRequest extends AbstractQRequest
*******************************************************************************/ *******************************************************************************/
public Serializable getValue(String fieldName) public Serializable getValue(String fieldName)
{ {
if(values == null) return (processState.getValues().get(fieldName));
{
return (null);
}
return (values.get(fieldName));
} }
@ -280,4 +284,15 @@ public class RunFunctionRequest extends AbstractQRequest
return ((Integer) getValue(fieldName)); 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;
}
} }

View File

@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQResult; 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 public class RunFunctionResult extends AbstractQResult
{ {
private List<QRecord> records; private ProcessState processState;
private Map<String, Serializable> values;
private String error; private String error;
@ -30,6 +28,18 @@ public class RunFunctionResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public RunFunctionResult() 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() public List<QRecord> getRecords()
{ {
return records; return processState.getRecords();
} }
@ -51,7 +61,7 @@ public class RunFunctionResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public void setRecords(List<QRecord> records) 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) public RunFunctionResult withRecords(List<QRecord> records)
{ {
this.records = records; this.processState.setRecords(records);
return (this); return (this);
} }
@ -74,7 +84,7 @@ public class RunFunctionResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public Map<String, Serializable> getValues() 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) 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) public RunFunctionResult withValues(Map<String, Serializable> values)
{ {
this.values = values; this.processState.setValues(values);
return (this); return (this);
} }
@ -108,11 +118,7 @@ public class RunFunctionResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public RunFunctionResult addValue(String fieldName, Serializable value) public RunFunctionResult addValue(String fieldName, Serializable value)
{ {
if(this.values == null) this.processState.getValues().put(fieldName, value);
{
this.values = new HashMap<>();
}
this.values.put(fieldName, value);
return (this); return (this);
} }
@ -137,4 +143,15 @@ public class RunFunctionResult extends AbstractQResult
{ {
this.error = error; this.error = error;
} }
/*******************************************************************************
** Accessor for processState
**
*******************************************************************************/
public ProcessState getProcessState()
{
return processState;
}
} }

View File

@ -6,7 +6,6 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQResult; 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 public class RunProcessResult extends AbstractQResult
{ {
private List<QRecord> records; private ProcessState processState;
private Map<String, Serializable> values;
private String error; private String error;
@ -30,6 +28,19 @@ public class RunProcessResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public RunProcessResult() 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() public List<QRecord> getRecords()
{ {
return records; return processState.getRecords();
} }
@ -51,7 +62,7 @@ public class RunProcessResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public void setRecords(List<QRecord> records) 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) public RunProcessResult withRecords(List<QRecord> records)
{ {
this.records = records; this.processState.setRecords(records);
return (this); return (this);
} }
@ -74,7 +85,7 @@ public class RunProcessResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public Map<String, Serializable> getValues() 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) 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) public RunProcessResult withValues(Map<String, Serializable> values)
{ {
this.values = values; this.processState.setValues(values);
return (this); return (this);
} }
@ -108,11 +119,7 @@ public class RunProcessResult extends AbstractQResult
*******************************************************************************/ *******************************************************************************/
public RunProcessResult addValue(String fieldName, Serializable value) public RunProcessResult addValue(String fieldName, Serializable value)
{ {
if(this.values == null) this.processState.getValues().put(fieldName, value);
{
this.values = new HashMap<>();
}
this.values.put(fieldName, value);
return (this); return (this);
} }

View File

@ -6,6 +6,7 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -19,7 +20,7 @@ import java.util.Map;
* "Display values" (e.g., labels for possible values, or formatted numbers * "Display values" (e.g., labels for possible values, or formatted numbers
* (e.g., quantities with commas)) are in the displayValues map. * (e.g., quantities with commas)) are in the displayValues map.
*******************************************************************************/ *******************************************************************************/
public class QRecord public class QRecord implements Serializable
{ {
private String tableName; private String tableName;
//x private Serializable primaryKey; //x private Serializable primaryKey;
@ -102,8 +103,6 @@ public class QRecord
return (this); return (this);
} }
//x /******************************************************************************* //x /*******************************************************************************
//x ** Getter for primaryKey //x ** Getter for primaryKey
//x ** //x **
@ -113,8 +112,6 @@ public class QRecord
//x return primaryKey; //x return primaryKey;
//x } //x }
//x /******************************************************************************* //x /*******************************************************************************
//x ** Setter for primaryKey //x ** Setter for primaryKey
//x ** //x **
@ -124,8 +121,6 @@ public class QRecord
//x this.primaryKey = primaryKey; //x this.primaryKey = primaryKey;
//x } //x }
//x /******************************************************************************* //x /*******************************************************************************
//x ** Setter for primaryKey //x ** Setter for primaryKey
//x ** //x **
@ -224,4 +219,15 @@ public class QRecord
return ((Integer) values.get(fieldName)); return ((Integer) values.get(fieldName));
} }
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public LocalDate getValueDate(String fieldName)
{
return ((LocalDate) values.get(fieldName));
}
} }

View File

@ -17,7 +17,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
public class QFunctionInputMetaData public class QFunctionInputMetaData
{ {
private QRecordListMetaData recordListMetaData; private QRecordListMetaData recordListMetaData;
private List<QFieldMetaData> fieldList; private List<QFieldMetaData> fieldList = new ArrayList<>();

View File

@ -96,17 +96,6 @@ public class QRecordListMetaData
/*******************************************************************************
**
*******************************************************************************/
public QRecordListMetaData withFields(Map<String, QFieldMetaData> fields)
{
this.fields = fields;
return (this);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -5,6 +5,11 @@
package com.kingsrook.qqq.backend.core.modules.mock; 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.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -41,7 +46,20 @@ public class MockQueryAction implements QueryInterface
for(String field : table.getFields().keySet()) 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; return rs;

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -46,6 +46,7 @@ public class TestUtils
qInstance.addTable(defineTablePerson()); qInstance.addTable(defineTablePerson());
qInstance.addPossibleValueSource(defineStatesPossibleValueSource()); qInstance.addPossibleValueSource(defineStatesPossibleValueSource());
qInstance.addProcess(defineProcessGreetPeople()); qInstance.addProcess(defineProcessGreetPeople());
qInstance.addProcess(defineProcessAddToPeoplesAge());
return (qInstance); 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)))));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/