QQQ-21 feedback from code review; add more types to ValueUtils

This commit is contained in:
2022-07-12 07:46:13 -05:00
parent d4657989dd
commit 86adccddd4
21 changed files with 409 additions and 21 deletions

View File

@ -106,7 +106,6 @@ public class RunProcessAction
case SKIP ->
{
LOG.info("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
processState.setNextStepName(step.getName());
//////////////////////////////////////////////////////////////////////
// much less error prone in case this code changes in the future... //
@ -166,9 +165,10 @@ public class RunProcessAction
/*******************************************************************************
**
** When we start running a process (or resuming it), get data in the RunProcessRequest
** either from the state provider (if they're found, for a resume).
*******************************************************************************/
private ProcessState primeProcessState(RunProcessRequest runProcessRequest, UUIDAndTypeStateKey stateKey) throws QException
ProcessState primeProcessState(RunProcessRequest runProcessRequest, UUIDAndTypeStateKey stateKey) throws QException
{
Optional<ProcessState> optionalProcessState = loadState(stateKey);
if(optionalProcessState.isEmpty())
@ -222,7 +222,7 @@ public class RunProcessAction
/*******************************************************************************
** return true if 'ok', false if error (and time to break loop)
** Run a single backend step.
*******************************************************************************/
private void runBackendStep(RunProcessRequest runProcessRequest, QProcessMetaData process, RunProcessResult runProcessResult, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, ProcessState processState) throws Exception
{
@ -231,6 +231,7 @@ public class RunProcessAction
runBackendStepRequest.setStepName(backendStep.getName());
runBackendStepRequest.setSession(runProcessRequest.getSession());
runBackendStepRequest.setCallback(runProcessRequest.getCallback());
runBackendStepRequest.setAsyncJobCallback(runProcessRequest.getAsyncJobCallback());
RunBackendStepResult lastFunctionResult = new RunBackendStepAction().execute(runBackendStepRequest);
storeState(stateKey, lastFunctionResult.getProcessState());
@ -296,6 +297,16 @@ public class RunProcessAction
/*******************************************************************************
** public method to get a process state just by UUID.
*******************************************************************************/
public static Optional<ProcessState> getState(String processUUID)
{
return (getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS)));
}
/*******************************************************************************
** Store the process state from a function result to the state provider
**

View File

@ -90,7 +90,7 @@ public class AsyncJobCallback
/*******************************************************************************
**
*******************************************************************************/
private void storeUpdatedStatus()
protected void storeUpdatedStatus()
{
AsyncJobManager.getStateProvider().put(new UUIDAndTypeStateKey(jobUUID, StateType.ASYNC_JOB_STATUS), asyncJobStatus);
}

View File

@ -113,7 +113,7 @@ public class AsyncJobManager
** Load an instance of the appropriate state provider
**
*******************************************************************************/
protected static StateProviderInterface getStateProvider()
static StateProviderInterface getStateProvider()
{
// TODO - read this from somewhere in meta data eh?
return InMemoryStateProvider.getInstance();

View File

@ -39,6 +39,23 @@ public class AsyncJobStatus implements Serializable
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "AsyncJobStatus{"
+ "state=" + state
+ ", message='" + message + '\''
+ ", current=" + current
+ ", total=" + total
+ ", caughtException=" + caughtException
+ '}';
}
/*******************************************************************************
** Getter for state
**

View File

@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.async;
/*******************************************************************************
** Exception thrown by AsyncJobManager, not to indicate an error, per se, but
** rather to indicate that a job has taken too long, as is now "going async".
**
** So, this exception contains the jobUUID.
*******************************************************************************/
public class JobGoingAsyncException extends Exception
{

View File

@ -25,6 +25,9 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQRequest;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -43,6 +46,7 @@ public class RunBackendStepRequest extends AbstractQRequest
private String processName;
private String stepName;
private QProcessCallback callback;
private AsyncJobCallback asyncJobCallback;
@ -312,4 +316,31 @@ public class RunBackendStepRequest extends AbstractQRequest
{
return processState;
}
/*******************************************************************************
**
*******************************************************************************/
public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback)
{
this.asyncJobCallback = asyncJobCallback;
}
/*******************************************************************************
**
*******************************************************************************/
public AsyncJobCallback getAsyncJobCallback()
{
if (asyncJobCallback == null)
{
/////////////////////////////////////////////////////////////////////////
// avoid NPE in case we didn't have one of these! create a new one... //
/////////////////////////////////////////////////////////////////////////
asyncJobCallback = new AsyncJobCallback(UUID.randomUUID(), new AsyncJobStatus());
}
return (asyncJobCallback);
}
}

View File

@ -23,9 +23,11 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
@ -227,7 +229,7 @@ public class QRecord implements Serializable
*******************************************************************************/
public String getValueString(String fieldName)
{
return ((String) values.get(fieldName));
return (ValueUtils.getValueAsString(values.get(fieldName)));
}
@ -238,7 +240,28 @@ public class QRecord implements Serializable
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return ((Integer) values.get(fieldName));
return (ValueUtils.getValueAsInteger(values.get(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getValueBigDecimal(String fieldName)
{
return (ValueUtils.getValueAsBigDecimal(values.get(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Boolean getValueBoolean(String fieldName)
{
return (ValueUtils.getValueAsBoolean(values.get(fieldName)));
}
@ -249,6 +272,7 @@ public class QRecord implements Serializable
*******************************************************************************/
public LocalDate getValueDate(String fieldName)
{
// todo - rewrite using ValueUtils...
return ((LocalDate) values.get(fieldName));
}
@ -309,6 +333,7 @@ public class QRecord implements Serializable
}
/*******************************************************************************
** Get one backendDetail from this record as a String
**

View File

@ -35,7 +35,7 @@ public class QFieldMetaData
private String label;
private String backendName;
private QFieldType type;
private boolean isRequired;
private boolean isRequired = false;
private Serializable defaultValue;
private String possibleValueSourceName;

View File

@ -170,7 +170,7 @@ public class QBackendStepMetaData extends QStepMetaData
/*******************************************************************************
** Get a list of all of the input fields used by this function
*******************************************************************************/
@JsonIgnore
@JsonIgnore // because this is a computed property - we don't want it in our json.
@Override
public List<QFieldMetaData> getInputFields()
{
@ -187,7 +187,7 @@ public class QBackendStepMetaData extends QStepMetaData
/*******************************************************************************
** Get a list of all of the output fields used by this function
*******************************************************************************/
@JsonIgnore
@JsonIgnore // because this is a computed property - we don't want it in our json.
@Override
public List<QFieldMetaData> getOutputFields()
{

View File

@ -212,7 +212,7 @@ public class QProcessMetaData
/*******************************************************************************
** Wrapper to getStep, that internally casts t0 BackendStepMetaData
** Wrapper to getStep, that internally casts to BackendStepMetaData
*******************************************************************************/
public QBackendStepMetaData getBackendStep(String name)
{

View File

@ -33,9 +33,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.serialization.QStepMetaData
/*******************************************************************************
** Meta-Data to define a step in a process in a QQQ instance.
**
** Specifically, this is a base-class for QFrontendStepMetaData and QBackendStepMetaData.
**
*******************************************************************************/
@JsonDeserialize(using = QStepMetaDataDeserializer.class)
public class QStepMetaData
public abstract class QStepMetaData
{
private String name;
private String label;

View File

@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
@ -47,7 +48,7 @@ public class QStepMetaDataDeserializer extends JsonDeserializer<QStepMetaData>
Class<? extends QStepMetaData> targetClass = switch(stepType)
{
case "backend" -> QBackendStepMetaData.class;
case "frontend" -> QBackendStepMetaData.class;
case "frontend" -> QFrontendStepMetaData.class;
default -> throw new IllegalArgumentException("Unsupported StepType " + stepType + " for deserialization");
};
return (DeserializerUtils.reflectivelyDeserialize(targetClass, treeNode));

View File

@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.state;
/*******************************************************************************
** Possible types of states to be stored.
**
** Idea: could these have the corresponding Classes that they support?
** ala PROCESS_STATUS(ProcessStatus.class) ?
*******************************************************************************/
public enum StateType
{

View File

@ -35,7 +35,7 @@ public class ExceptionUtils
/*******************************************************************************
** Find a specific exception class in an exception's caused-by chain. Returns
** null if not found. Be aware, uses class.isInstaance (so sub-classes get found).
** null if not found. Be aware, uses class.isInstance (so sub-classes get found).
**
*******************************************************************************/
public static <T extends Throwable> T findClassInRootChain(Throwable e, Class<T> targetClass)

View File

@ -33,7 +33,59 @@ public class ValueUtils
{
/*******************************************************************************
**
** Type-safely make a String from any Object.
*******************************************************************************/
public static String getValueAsString(Object value)
{
if(value == null)
{
return (null);
}
else if(value instanceof String s)
{
return (s);
}
else
{
return (String.valueOf(value));
}
}
/*******************************************************************************
** Returns null for null input;
** Returns the input object for Boolean-typed inputs.
** Then, follows Boolean.parseBoolean, returning true iff value is a case-insensitive
** match for "true", for String.valueOf the input
*******************************************************************************/
public static Boolean getValueAsBoolean(Object value)
{
if(value == null)
{
return (null);
}
else if(value instanceof Boolean b)
{
return (b);
}
else if(value instanceof String s)
{
return (Boolean.parseBoolean(s));
}
else
{
return (Boolean.parseBoolean(String.valueOf(value)));
}
}
/*******************************************************************************
** Type-safely make an Integer from any Object.
** null and empty-string inputs return null.
** We try to strip away commas and decimals (as long as they are exactly equal to the int value)
** We may throw if the input can't be converted to an integer.
*******************************************************************************/
public static Integer getValueAsInteger(Object value) throws QValueException
{
@ -53,7 +105,7 @@ public class ValueUtils
}
else if(value instanceof Float f)
{
if (f.intValue() != f)
if(f.intValue() != f)
{
throw (new QValueException(f + " does not have an exact integer representation."));
}
@ -61,7 +113,7 @@ public class ValueUtils
}
else if(value instanceof Double d)
{
if (d.intValue() != d)
if(d.intValue() != d)
{
throw (new QValueException(d + " does not have an exact integer representation."));
}
@ -122,4 +174,78 @@ public class ValueUtils
}
}
/*******************************************************************************
** Type-safely make a BigDecimal from any Object.
** null and empty-string inputs return null.
** We may throw if the input can't be converted to a BigDecimal
*******************************************************************************/
public static BigDecimal getValueAsBigDecimal(Object value) throws QValueException
{
try
{
if(value == null)
{
return (null);
}
else if(value instanceof BigDecimal bd)
{
return (bd);
}
else if(value instanceof Integer i)
{
return new BigDecimal(i);
}
else if(value instanceof Long l)
{
return new BigDecimal(l);
}
else if(value instanceof Float f)
{
return new BigDecimal(f);
}
else if(value instanceof Double d)
{
return new BigDecimal(d);
}
else if(value instanceof String s)
{
if(!StringUtils.hasContent(s))
{
return (null);
}
try
{
return (new BigDecimal(s));
}
catch(NumberFormatException nfe)
{
if(s.contains(","))
{
String sWithoutCommas = s.replaceAll(",", "");
try
{
return (getValueAsBigDecimal(sWithoutCommas));
}
catch(Exception ignore)
{
throw (nfe);
}
}
throw (nfe);
}
}
else
{
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal."));
}
}
catch(Exception e)
{
throw (new QValueException("Value [" + value + "] could not be converted to an BigDecimal.", e));
}
}
}