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.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)
{

View File

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

View File

@ -17,5 +17,5 @@ public interface FunctionBody
/*******************************************************************************
** 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
{
@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);
}
}

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

View File

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

View File

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

View File

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

View File

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

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

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.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)))));
}
/*******************************************************************************
**
*******************************************************************************/