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 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 listTypeParam = (Class) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType()); + Class listTypeParam = (Class) 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 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()); + } + } }