diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/QProcessPayload.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/QProcessPayload.java
new file mode 100644
index 00000000..aad533bb
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/QProcessPayload.java
@@ -0,0 +1,200 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.processes;
+
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
+import com.kingsrook.qqq.backend.core.utils.ListingHash;
+import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
+
+
+/*******************************************************************************
+ ** base-class for bean-like classes to represent the fields of a process.
+ ** similar in spirit to QRecordEntity, but for processes.
+ *******************************************************************************/
+public class QProcessPayload
+{
+ private static final QLogger LOG = QLogger.getLogger(QProcessPayload.class);
+
+ private static final ListingHash, QRecordEntityField> fieldMapping = new ListingHash<>();
+
+
+
+ /*******************************************************************************
+ ** Build an entity of this QRecord type from a QRecord
+ **
+ *******************************************************************************/
+ public static T fromProcessState(Class c, ProcessState processState) throws QException
+ {
+ try
+ {
+ T entity = c.getConstructor().newInstance();
+ entity.populateFromProcessState(processState);
+ return (entity);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error building process payload from state.", e));
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ protected void populateFromProcessState(ProcessState processState)
+ {
+ try
+ {
+ List fieldList = getFieldList(this.getClass());
+ // originalRecordValues = new HashMap<>();
+
+ for(QRecordEntityField qRecordEntityField : fieldList)
+ {
+ Serializable value = processState.getValues().get(qRecordEntityField.getFieldName());
+ Object typedValue = qRecordEntityField.convertValueType(value);
+ qRecordEntityField.getSetter().invoke(this, typedValue);
+ // originalRecordValues.put(qRecordEntityField.getFieldName(), value);
+ }
+
+ // for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
+ // {
+ // List associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
+ // if(associatedRecords == null)
+ // {
+ // qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
+ // }
+ // else
+ // {
+ // List associatedEntityList = new ArrayList<>();
+ // for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
+ // {
+ // associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
+ // }
+ // qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
+ // }
+ // }
+
+ // for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
+ // {
+ // List associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
+ // if(associatedRecords == null)
+ // {
+ // qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
+ // }
+ // else
+ // {
+ // List associatedEntityList = new ArrayList<>();
+ // for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
+ // {
+ // associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
+ // }
+ // qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
+ // }
+ // }
+ }
+ catch(Exception e)
+ {
+ throw (new QRuntimeException("Error building process payload from process state.", e));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Copy the values from this payload into the given process state.
+ ** ALL fields in the entity will be set in the process state.
+ **
+ *******************************************************************************/
+ public void toProcessState(ProcessState processState) throws QRuntimeException
+ {
+ try
+ {
+ for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
+ {
+ processState.getValues().put(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
+ }
+ }
+ catch(Exception e)
+ {
+ throw (new QRuntimeException("Error populating process state from process payload.", e));
+ }
+ }
+
+
+
+ /***************************************************************************
+ *
+ ***************************************************************************/
+ public static Set> allowedFieldTypes()
+ {
+ HashSet> classes = new HashSet<>(ReflectiveBeanLikeClassUtils.defaultAllowedTypes());
+ classes.add(Map.class);
+ classes.add(List.class);
+ return (classes);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static List getFieldList(Class extends QProcessPayload> c)
+ {
+ if(!fieldMapping.containsKey(c))
+ {
+ List fieldList = new ArrayList<>();
+ for(Method possibleGetter : c.getMethods())
+ {
+ if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, false, allowedFieldTypes()))
+ {
+ Optional setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
+
+ if(setter.isPresent())
+ {
+ String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
+ fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), null));
+ }
+ else
+ {
+ LOG.debug("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
+ }
+ }
+ }
+ fieldMapping.put(c, fieldList);
+ }
+ return (fieldMapping.get(c));
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepInput.java
index ca066eb3..dad1a92d 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepInput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepInput.java
@@ -628,4 +628,15 @@ public class RunBackendStepInput extends AbstractActionInput
{
return (QContext.getQInstance().getProcess(getProcessName()));
}
+
+
+
+ /***************************************************************************
+ ** return a QProcessPayload subclass instance, with values populated from
+ ** the current process state.
+ ***************************************************************************/
+ public T getProcessPayload(Class payloadClass) throws QException
+ {
+ return QProcessPayload.fromProcessState(payloadClass, getProcessState());
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java
index ba6b87c5..5f212788 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java
@@ -445,4 +445,14 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
}
+
+
+ /***************************************************************************
+ ** Update the process state with values from the input processPayload
+ ** subclass instance.
+ ***************************************************************************/
+ public void setProcessPayload(QProcessPayload processPayload)
+ {
+ processPayload.toProcessState(getProcessState());
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java
index c07446a0..880f4993 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntity.java
@@ -24,20 +24,11 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedParameterizedType;
-import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalTime;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -49,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
+import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@@ -325,13 +317,13 @@ public abstract class QRecordEntity
List fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
- if(isGetter(possibleGetter))
+ if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
{
- Optional setter = getSetterForGetter(c, possibleGetter);
+ Optional setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
- String fieldName = getFieldNameFromGetter(possibleGetter);
+ String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
Optional fieldAnnotation = getQFieldAnnotation(c, fieldName);
if(fieldAnnotation.isPresent())
@@ -378,19 +370,19 @@ public abstract class QRecordEntity
List associationList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
- if(isGetter(possibleGetter))
+ if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
{
- Optional setter = getSetterForGetter(c, possibleGetter);
+ Optional setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
- String fieldName = getFieldNameFromGetter(possibleGetter);
+ String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
Optional associationAnnotation = getQAssociationAnnotation(c, fieldName);
if(associationAnnotation.isPresent())
{
@SuppressWarnings("unchecked")
- Class extends QRecordEntity> listTypeParam = (Class extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
+ Class extends QRecordEntity> listTypeParam = (Class extends QRecordEntity>) ReflectiveBeanLikeClassUtils.getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
}
}
@@ -457,130 +449,4 @@ public abstract class QRecordEntity
}
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public static String getFieldNameFromGetter(Method getter)
- {
- String nameWithoutGet = getter.getName().replaceFirst("^get", "");
- if(nameWithoutGet.length() == 1)
- {
- return (nameWithoutGet.toLowerCase(Locale.ROOT));
- }
- return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static boolean isGetter(Method method)
- {
- if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
- {
- if(isSupportedFieldType(method.getReturnType()) || isSupportedAssociation(method.getReturnType(), method.getAnnotatedReturnType()))
- {
- return (true);
- }
- else
- {
- if(!method.getName().equals("getClass") && method.getAnnotation(QIgnore.class) == null)
- {
- LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
- }
- }
- }
- return (false);
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static Optional getSetterForGetter(Class extends QRecordEntity> c, Method getter)
- {
- String setterName = getter.getName().replaceFirst("^get", "set");
- for(Method method : c.getMethods())
- {
- if(method.getName().equals(setterName))
- {
- if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
- {
- return (Optional.of(method));
- }
- else
- {
- LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
- }
- }
- }
- return (Optional.empty());
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static boolean isSupportedFieldType(Class> returnType)
- {
- // todo - more types!!
- return (returnType.equals(String.class)
- || returnType.equals(Integer.class)
- || returnType.equals(Long.class)
- || returnType.equals(int.class)
- || returnType.equals(Boolean.class)
- || returnType.equals(boolean.class)
- || returnType.equals(BigDecimal.class)
- || returnType.equals(Instant.class)
- || returnType.equals(LocalDate.class)
- || returnType.equals(LocalTime.class)
- || returnType.equals(byte[].class));
- /////////////////////////////////////////////
- // note - this list has implications upon: //
- // - QFieldType.fromClass //
- // - QRecordEntityField.convertValueType //
- /////////////////////////////////////////////
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static boolean isSupportedAssociation(Class> returnType, AnnotatedType annotatedType)
- {
- Class> listTypeParam = getListTypeParam(returnType, annotatedType);
- return (listTypeParam != null && QRecordEntity.class.isAssignableFrom(listTypeParam));
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static Class> getListTypeParam(Class> listType, AnnotatedType annotatedType)
- {
- if(listType.equals(List.class))
- {
- if(annotatedType instanceof AnnotatedParameterizedType apt)
- {
- AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
- for(AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments)
- {
- Type type = annotatedActualTypeArgument.getType();
- if(type instanceof Class> c)
- {
- return (c);
- }
- }
- }
- }
-
- return (null);
- }
-
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java
index a9999b25..91293930 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecordEntityField.java
@@ -28,6 +28,7 @@ import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
+import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@@ -170,6 +171,11 @@ public class QRecordEntityField
{
return (ValueUtils.getValueAsByteArray(value));
}
+
+ if(type.equals(Map.class))
+ {
+ return (ValueUtils.getValueAsMap(value));
+ }
}
catch(Exception e)
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
index 12eb5454..cac0bd22 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
@@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
+import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@@ -188,7 +189,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
{
try
{
- this.name = QRecordEntity.getFieldNameFromGetter(getter);
+ this.name = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(getter);
this.type = QFieldType.fromClass(getter.getReturnType());
@SuppressWarnings("unchecked")
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ReflectiveBeanLikeClassUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ReflectiveBeanLikeClassUtils.java
new file mode 100644
index 00000000..94614769
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ReflectiveBeanLikeClassUtils.java
@@ -0,0 +1,188 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.utils;
+
+
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.data.QIgnore;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+
+
+/*******************************************************************************
+ ** Utilities for bean-like classes (e.g., QRecordEntity, QProcessPayload) that
+ ** use reflection to understand their bean-fields
+ *******************************************************************************/
+public class ReflectiveBeanLikeClassUtils
+{
+ private static final QLogger LOG = QLogger.getLogger(ReflectiveBeanLikeClassUtils.class);
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String getFieldNameFromGetter(Method getter)
+ {
+ String nameWithoutGet = getter.getName().replaceFirst("^get", "");
+ if(nameWithoutGet.length() == 1)
+ {
+ return (nameWithoutGet.toLowerCase(Locale.ROOT));
+ }
+ return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static boolean isGetter(Method method, boolean allowAssociations)
+ {
+ return isGetter(method, allowAssociations, defaultAllowedTypes());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static boolean isGetter(Method method, boolean allowAssociations, Collection> allowedTypes)
+ {
+ if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
+ {
+ if(allowedTypes.contains(method.getReturnType()) || (allowAssociations && isSupportedAssociation(method.getReturnType(), method.getAnnotatedReturnType())))
+ {
+ return (true);
+ }
+ else
+ {
+ if(!method.getName().equals("getClass") && method.getAnnotation(QIgnore.class) == null)
+ {
+ LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
+ }
+ }
+ }
+ return (false);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Optional getSetterForGetter(Class> c, Method getter)
+ {
+ String setterName = getter.getName().replaceFirst("^get", "set");
+ for(Method method : c.getMethods())
+ {
+ if(method.getName().equals(setterName))
+ {
+ if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
+ {
+ return (Optional.of(method));
+ }
+ else
+ {
+ LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
+ }
+ }
+ }
+ return (Optional.empty());
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static Collection> defaultAllowedTypes()
+ {
+ /////////////////////////////////////////////
+ // note - this list has implications upon: //
+ // - QFieldType.fromClass //
+ // - QRecordEntityField.convertValueType //
+ /////////////////////////////////////////////
+ return (Set.of(String.class,
+ Integer.class,
+ Long.class,
+ int.class,
+ Boolean.class,
+ boolean.class,
+ BigDecimal.class,
+ Instant.class,
+ LocalDate.class,
+ LocalTime.class,
+ byte[].class));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static boolean isSupportedAssociation(Class> returnType, AnnotatedType annotatedType)
+ {
+ Class> listTypeParam = getListTypeParam(returnType, annotatedType);
+ return (listTypeParam != null && QRecordEntity.class.isAssignableFrom(listTypeParam));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Class> getListTypeParam(Class> listType, AnnotatedType annotatedType)
+ {
+ if(listType.equals(List.class))
+ {
+ if(annotatedType instanceof AnnotatedParameterizedType apt)
+ {
+ AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
+ for(AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments)
+ {
+ Type type = annotatedActualTypeArgument.getType();
+ if(type instanceof Class> c)
+ {
+ return (c);
+ }
+ }
+ }
+ }
+
+ return (null);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
index 1e791abd..4bdc405c 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
@@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.utils;
+import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -38,6 +39,7 @@ import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.List;
+import java.util.Map;
import java.util.TimeZone;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
@@ -1012,4 +1014,37 @@ public class ValueUtils
return defaultIfCannotInfer;
}
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static Map getValueAsMap(Serializable value)
+ {
+ if(value == null)
+ {
+ return (null);
+ }
+ else if(value instanceof Map, ?> map)
+ {
+ return (map);
+ }
+ else if(value instanceof String string && string.startsWith("{") && string.endsWith("}"))
+ {
+ try
+ {
+ Map map = JsonUtils.toObject(string, Map.class);
+ return (map);
+ }
+ catch(IOException e)
+ {
+ throw new QValueException("Error parsing string to map", e);
+ }
+ }
+ else
+ {
+ throw new QValueException("Unrecognized object type in getValueAsMap: " + value.getClass().getSimpleName());
+ }
+ }
}