mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Working towards fixing serialization of qstep metadata
This commit is contained in:
@ -26,18 +26,25 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import com.fasterxml.jackson.core.TreeNode;
|
import com.fasterxml.jackson.core.TreeNode;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.NullNode;
|
import com.fasterxml.jackson.databind.node.NullNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||||
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
|
||||||
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -53,32 +60,42 @@ public class DeserializerUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Read a string value, identified by key, from a jackson treeNode.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String readTextValue(TreeNode treeNode, String key) throws IOException
|
||||||
|
{
|
||||||
|
TreeNode valueNode = treeNode.get(key);
|
||||||
|
if(valueNode == null || valueNode instanceof NullNode)
|
||||||
|
{
|
||||||
|
throw new IOException("Missing node named [" + key + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(valueNode instanceof TextNode textNode))
|
||||||
|
{
|
||||||
|
throw new IOException(key + "is not a string value (is: " + valueNode.getClass().getSimpleName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// get the value of the backendType json node, and use it to look up the qBackendModule object //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
return (textNode.asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** For a given (jackson, JSON) treeNode, look at its backendType property,
|
** For a given (jackson, JSON) treeNode, look at its backendType property,
|
||||||
** and return an instance of the corresponding QBackendModule.
|
** and return an instance of the corresponding QBackendModule.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static QBackendModuleInterface getBackendModule(TreeNode treeNode) throws IOException
|
public static QBackendModuleInterface getBackendModule(TreeNode treeNode) throws IOException
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// validate the backendType property is present, as text, in the json treeNode //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
TreeNode backendTypeTreeNode = treeNode.get("backendType");
|
|
||||||
if(backendTypeTreeNode == null || backendTypeTreeNode instanceof NullNode)
|
|
||||||
{
|
|
||||||
throw new IOException("Missing backendType in backendMetaData");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!(backendTypeTreeNode instanceof TextNode textNode))
|
|
||||||
{
|
|
||||||
throw new IOException("backendType is not a string value (is: " + backendTypeTreeNode.getClass().getSimpleName() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// get the value of the backendType json node, and use it to look up the qBackendModule object //
|
// get the value of the backendType json node, and use it to look up the qBackendModule object //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
String backendType = textNode.asText();
|
String backendType = readTextValue(treeNode, "backendType");
|
||||||
return new QBackendModuleDispatcher().getQBackendModule(backendType);
|
return new QBackendModuleDispatcher().getQBackendModule(backendType);
|
||||||
}
|
}
|
||||||
catch(QModuleDispatchException e)
|
catch(QModuleDispatchException e)
|
||||||
@ -108,7 +125,7 @@ public class DeserializerUtils
|
|||||||
// and set it in the output object, doing type conversion as needed. //
|
// and set it in the output object, doing type conversion as needed. //
|
||||||
// do this by iterating over methods on the output class that look like setters. //
|
// do this by iterating over methods on the output class that look like setters. //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
Map<String, Consumer<String>> setterMap = new HashMap<>();
|
Map<String, Consumer<Object>> setterMap = new HashMap<>();
|
||||||
for(Method method : outputClass.getMethods())
|
for(Method method : outputClass.getMethods())
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
@ -125,33 +142,41 @@ public class DeserializerUtils
|
|||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
// put the entry in the map - where the value here is a consumer lambda function //
|
// put the entry in the map - where the value here is a consumer lambda function //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
setterMap.put(fieldName, (String value) ->
|
setterMap.put(fieldName, (Object value) ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if(value == null)
|
||||||
|
{
|
||||||
|
Object[] args = new Object[] { null };
|
||||||
|
method.invoke(output, args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// based on the parameter type, handle it differently - either type-converting (e.g., parseInt) //
|
// based on the parameter type, handle it differently - either type-converting (e.g., parseInt) //
|
||||||
// or gracefully ignoring, or failing. //
|
// or gracefully ignoring, or failing. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
String valueString = String.valueOf(value);
|
||||||
if(parameterType.equals(String.class))
|
if(parameterType.equals(String.class))
|
||||||
{
|
{
|
||||||
method.invoke(output, value);
|
method.invoke(output, String.valueOf(value));
|
||||||
}
|
}
|
||||||
else if(parameterType.equals(Integer.class))
|
else if(parameterType.equals(Integer.class))
|
||||||
{
|
{
|
||||||
method.invoke(output, StringUtils.hasContent(value) ? Integer.parseInt(value) : null);
|
method.invoke(output, ValueUtils.getValueAsInteger(value));
|
||||||
}
|
}
|
||||||
else if(parameterType.equals(Long.class))
|
else if(parameterType.equals(Long.class))
|
||||||
{
|
{
|
||||||
method.invoke(output, StringUtils.hasContent(value) ? Long.parseLong(value) : null);
|
method.invoke(output, StringUtils.hasContent(valueString) ? Long.parseLong(valueString) : null);
|
||||||
}
|
}
|
||||||
else if(parameterType.equals(BigDecimal.class))
|
else if(parameterType.equals(BigDecimal.class))
|
||||||
{
|
{
|
||||||
method.invoke(output, StringUtils.hasContent(value) ? new BigDecimal(value) : null);
|
method.invoke(output, StringUtils.hasContent(valueString) ? new BigDecimal(valueString) : null);
|
||||||
}
|
}
|
||||||
else if(parameterType.equals(Boolean.class))
|
else if(parameterType.equals(Boolean.class))
|
||||||
{
|
{
|
||||||
method.invoke(output, StringUtils.hasContent(value) ? Boolean.parseBoolean(value) : null);
|
method.invoke(output, StringUtils.hasContent(valueString) ? Boolean.parseBoolean(valueString) : null);
|
||||||
}
|
}
|
||||||
else if(parameterType.isEnum())
|
else if(parameterType.isEnum())
|
||||||
{
|
{
|
||||||
@ -160,7 +185,7 @@ public class DeserializerUtils
|
|||||||
// call that method, passing it the string value from the json (the null there is because it's a static method) //
|
// call that method, passing it the string value from the json (the null there is because it's a static method) //
|
||||||
// then pass the num value into the output object, via our method.invoke. //
|
// then pass the num value into the output object, via our method.invoke. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(StringUtils.hasContent(value))
|
if(StringUtils.hasContent(valueString))
|
||||||
{
|
{
|
||||||
Method valueOfMethod = parameterType.getMethod("valueOf", String.class);
|
Method valueOfMethod = parameterType.getMethod("valueOf", String.class);
|
||||||
Object enumValue = valueOfMethod.invoke(null, value);
|
Object enumValue = valueOfMethod.invoke(null, value);
|
||||||
@ -178,6 +203,21 @@ public class DeserializerUtils
|
|||||||
// we hit this when trying to de-serialize a QBackendMetaData, and we found its setBackendType(Class) method //
|
// we hit this when trying to de-serialize a QBackendMetaData, and we found its setBackendType(Class) method //
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
}
|
}
|
||||||
|
else if(parameterType.isAssignableFrom(List.class))
|
||||||
|
{
|
||||||
|
if(value instanceof List<?>)
|
||||||
|
{
|
||||||
|
method.invoke(output, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(parameterType.isAssignableFrom(Map.class))
|
||||||
|
{
|
||||||
|
// TypeVariable<? extends Class<?>> keyType = parameterType.getTypeParameters()[0];
|
||||||
|
// TypeVariable<? extends Class<?>> valueType = parameterType.getTypeParameters()[1];
|
||||||
|
Map<?, ?> map = new LinkedHashMap<>();
|
||||||
|
// todo - recursively process
|
||||||
|
method.invoke(output, map);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -185,10 +225,10 @@ public class DeserializerUtils
|
|||||||
// otherwise, either find some jackson annotation that makes sense, and apply it to the setter method, //
|
// otherwise, either find some jackson annotation that makes sense, and apply it to the setter method, //
|
||||||
// or if no jackson annotation is right, then come up with annotation of our own. //
|
// or if no jackson annotation is right, then come up with annotation of our own. //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
throw (new RuntimeException("Field " + fieldName + " is of an unhandled type " + parameterType.getName() + " when deserializing " + outputClass.getName()));
|
method.invoke(output, reflectivelyDeserialize((Class) parameterType, (TreeNode) value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IllegalAccessException | InvocationTargetException | NoSuchMethodException e)
|
catch(IllegalAccessException | InvocationTargetException | NoSuchMethodException | IOException e)
|
||||||
{
|
{
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -216,7 +256,7 @@ public class DeserializerUtils
|
|||||||
** Note, the consumers in the map all work on strings, so you may need to do
|
** Note, the consumers in the map all work on strings, so you may need to do
|
||||||
** Integer.parseInt, for example, in a lambda in the map.
|
** Integer.parseInt, for example, in a lambda in the map.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static void deserializeBean(TreeNode treeNode, Map<String, Consumer<String>> setterMap) throws IOException
|
private static void deserializeBean(TreeNode treeNode, Map<String, Consumer<Object>> setterMap) throws IOException
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
// iterate over fields in the json object (treeNode) //
|
// iterate over fields in the json object (treeNode) //
|
||||||
@ -244,6 +284,19 @@ public class DeserializerUtils
|
|||||||
{
|
{
|
||||||
setterMap.get(fieldName).accept(textNode.asText());
|
setterMap.get(fieldName).accept(textNode.asText());
|
||||||
}
|
}
|
||||||
|
else if(fieldNode instanceof ObjectNode)
|
||||||
|
{
|
||||||
|
setterMap.get(fieldName).accept(fieldNode);
|
||||||
|
}
|
||||||
|
else if(fieldNode instanceof ArrayNode arrayNode)
|
||||||
|
{
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
for(JsonNode jsonNode : arrayNode)
|
||||||
|
{
|
||||||
|
// todo - actually build the objects...
|
||||||
|
}
|
||||||
|
setterMap.get(fieldName).accept(list);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw (new IOException("Unexpected node type (" + fieldNode.getClass() + ") for field: " + fieldName));
|
throw (new IOException("Unexpected node type (" + fieldNode.getClass() + ") for field: " + fieldName));
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.serialization;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
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.QStepMetaData;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Jackson custom deserialization class, to return an appropriate sub-type of
|
||||||
|
** QTableBackendDetails, based on the backendType of the containing table.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QStepMetaDataDeserializer extends JsonDeserializer<QStepMetaData>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("checkstyle:Indentation")
|
||||||
|
public QStepMetaData deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException
|
||||||
|
{
|
||||||
|
TreeNode treeNode = jsonParser.readValueAsTree();
|
||||||
|
String stepType = DeserializerUtils.readTextValue(treeNode, "stepType");
|
||||||
|
Class<? extends QStepMetaData> targetClass = switch(stepType)
|
||||||
|
{
|
||||||
|
case "backend" -> QBackendStepMetaData.class;
|
||||||
|
case "frontend" -> QBackendStepMetaData.class;
|
||||||
|
default -> throw new IllegalArgumentException("Unsupported StepType " + stepType + " for deserialization");
|
||||||
|
};
|
||||||
|
return (DeserializerUtils.reflectivelyDeserialize(targetClass, treeNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user