diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/DeleteAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/DeleteAction.java index 8b54799d..6278d7f3 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/DeleteAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/DeleteAction.java @@ -43,7 +43,7 @@ public class DeleteAction ActionHelper.validateSession(deleteRequest); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); - QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(deleteRequest.getBackend()); + QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteRequest.getBackend()); // todo pre-customization - just get to modify the request? DeleteResult deleteResult = qModule.getDeleteInterface().execute(deleteRequest); // todo post-customization - can do whatever w/ the result if you want diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java index 922e9449..888712d7 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/InsertAction.java @@ -43,7 +43,7 @@ public class InsertAction ActionHelper.validateSession(insertRequest); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); - QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(insertRequest.getBackend()); + QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(insertRequest.getBackend()); // todo pre-customization - just get to modify the request? InsertResult insertResult = qModule.getInsertInterface().execute(insertRequest); // todo post-customization - can do whatever w/ the result if you want diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java index 8914fb02..d9759929 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/QueryAction.java @@ -43,7 +43,7 @@ public class QueryAction ActionHelper.validateSession(queryRequest); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); - QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(queryRequest.getBackend()); + QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryRequest.getBackend()); // todo pre-customization - just get to modify the request? QueryResult queryResult = qModule.getQueryInterface().execute(queryRequest); // todo post-customization - can do whatever w/ the result if you want diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/UpdateAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/UpdateAction.java index ca4baa63..5a935974 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/UpdateAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/UpdateAction.java @@ -43,7 +43,7 @@ public class UpdateAction ActionHelper.validateSession(updateRequest); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); - QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(updateRequest.getBackend()); + QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateRequest.getBackend()); // todo pre-customization - just get to modify the request? UpdateResult updateResult = qModule.getUpdateInterface().execute(updateRequest); // todo post-customization - can do whatever w/ the result if you want diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java index b718d216..365e802d 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java @@ -22,9 +22,9 @@ package com.kingsrook.qqq.backend.core.model.metadata; -import java.util.HashMap; -import java.util.Map; -import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer; +import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; /******************************************************************************* @@ -32,55 +32,32 @@ import com.fasterxml.jackson.annotation.JsonFilter; ** NoSQL table, etc) within a qqq instance ** *******************************************************************************/ +@JsonDeserialize(using = QBackendMetaDataDeserializer.class) public class QBackendMetaData { private String name; - private String type; - - @JsonFilter("secretsFilter") - private Map values; + private String backendType; + // todo - at some point, we may want to apply this to secret properties on subclasses? + // @JsonFilter("secretsFilter") /******************************************************************************* - ** + ** Default Constructor. *******************************************************************************/ - public String getValue(String key) + public QBackendMetaData() { - if(values == null) - { - return null; - } - return values.get(key); } /******************************************************************************* - ** + ** Copy Constructor. Meant for use by sub-classes. Should copy all fields! *******************************************************************************/ - public void setValue(String key, String value) + protected QBackendMetaData(QBackendMetaData source) { - if(values == null) - { - values = new HashMap<>(); - } - values.put(key, value); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public QBackendMetaData withValue(String key, String value) - { - if(values == null) - { - values = new HashMap<>(); - } - values.put(key, value); - return (this); + this.name = source.name; + this.backendType = source.backendType; } @@ -117,23 +94,42 @@ public class QBackendMetaData /******************************************************************************* - ** Getter for type + ** Getter for backendType ** *******************************************************************************/ - public String getType() + public String getBackendType() { - return type; + return backendType; } /******************************************************************************* - ** Setter for type + ** Setter for backendType ** *******************************************************************************/ - public void setType(String type) + public void setBackendType(String backendType) { - this.type = type; + this.backendType = backendType; + } + + + + /******************************************************************************* + ** Setter for backendType + ** + *******************************************************************************/ + public void setBackendType(Class backendModuleClass) + { + try + { + QBackendModuleInterface qBackendModuleInterface = backendModuleClass.getConstructor().newInstance(); + this.backendType = qBackendModuleInterface.getBackendType(); + } + catch(Exception e) + { + throw new IllegalArgumentException("Error dynamically getting backend type (name) from class [" + backendModuleClass.getName() + "], e)"); + } } @@ -141,9 +137,9 @@ public class QBackendMetaData /******************************************************************************* ** *******************************************************************************/ - public QBackendMetaData withType(String type) + public QBackendMetaData withBackendType(String backendType) { - this.type = type; + this.backendType = backendType; return (this); } @@ -152,29 +148,9 @@ public class QBackendMetaData /******************************************************************************* ** *******************************************************************************/ - public Map getValues() + public QBackendMetaData withBackendType(Class backendModuleClass) { - return values; - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public void setValues(Map values) - { - this.values = values; - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public QBackendMetaData withVales(Map values) - { - this.values = values; + setBackendType(backendModuleClass); return (this); } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableBackendDetails.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableBackendDetails.java new file mode 100644 index 00000000..01943bfc --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableBackendDetails.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.model.metadata; + + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.kingsrook.qqq.backend.core.model.metadata.serialization.QTableBackendDetailsDeserializer; +import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; + + +/******************************************************************************* + ** Base class where backends can specify additional per-table meta-data. + *******************************************************************************/ +@JsonDeserialize(using = QTableBackendDetailsDeserializer.class) +public abstract class QTableBackendDetails +{ + private String backendType; + + + + /******************************************************************************* + ** Getter for backendType + ** + *******************************************************************************/ + public String getBackendType() + { + return backendType; + } + + + + /******************************************************************************* + ** Setter for backendType + ** + *******************************************************************************/ + public void setBackendType(String backendType) + { + this.backendType = backendType; + } + + + + /******************************************************************************* + ** Fluent Setter for backendType + ** + *******************************************************************************/ + public QTableBackendDetails withBackendType(String backendType) + { + this.backendType = backendType; + return (this); + } + + + + /******************************************************************************* + ** Setter for backendType + ** + *******************************************************************************/ + public void setBackendType(Class backendModuleClass) + { + try + { + QBackendModuleInterface qBackendModuleInterface = backendModuleClass.getConstructor().newInstance(); + this.backendType = qBackendModuleInterface.getBackendType(); + } + catch(Exception e) + { + throw new IllegalArgumentException("Error dynamically getting backend type (name) from class [" + backendModuleClass.getName() + "], e)"); + } + } + + /******************************************************************************* + ** Fluent Setter for backendType + ** + *******************************************************************************/ + public QTableBackendDetails withBackendType(Class backendModuleClass) + { + setBackendType(backendModuleClass); + return (this); + } + + + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java index e2b4d620..f88abab4 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QTableMetaData.java @@ -36,8 +36,20 @@ public class QTableMetaData private String label; private String backendName; private String primaryKeyField; + private Map fields; + private QTableBackendDetails backendDetails; + + + + /******************************************************************************* + ** Default constructor. + *******************************************************************************/ + public QTableMetaData() + { + } + /******************************************************************************* @@ -246,4 +258,37 @@ public class QTableMetaData return (this); } + + + /******************************************************************************* + ** Getter for backendDetails + ** + *******************************************************************************/ + public QTableBackendDetails getBackendDetails() + { + return backendDetails; + } + + + + /******************************************************************************* + ** Setter for backendDetails + ** + *******************************************************************************/ + public void setBackendDetails(QTableBackendDetails backendDetails) + { + this.backendDetails = backendDetails; + } + + + /******************************************************************************* + ** Fluent Setter for backendDetails + ** + *******************************************************************************/ + public QTableMetaData withBackendDetails(QTableBackendDetails backendDetails) + { + this.backendDetails = backendDetails; + return (this); + } + } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java new file mode 100644 index 00000000..7862b7cb --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendProcessMetaData.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.frontend; + + +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; + + +/******************************************************************************* + * Version of QProcessMetaData that's meant for transmitting to a frontend. + * e.g., it excludes backend-only details. + * + *******************************************************************************/ +@JsonInclude(Include.NON_NULL) +public class QFrontendProcessMetaData +{ + private String name; + private String label; + private String tableName; + private Map fields; + + ////////////////////////////////////////////////////////////////////////////////// + // do not add setters. take values from the source-object in the constructor!! // + ////////////////////////////////////////////////////////////////////////////////// + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFrontendProcessMetaData(QProcessMetaData processMetaData) + { + this.name = processMetaData.getName(); + // todo? this.label = processMetaData.getLabel(); + this.tableName = processMetaData.getTableName(); + } + + + + /******************************************************************************* + ** Getter for name + ** + *******************************************************************************/ + public String getName() + { + return name; + } + + + + /******************************************************************************* + ** Getter for label + ** + *******************************************************************************/ + public String getLabel() + { + return label; + } + + + + /******************************************************************************* + ** Getter for primaryKeyField + ** + *******************************************************************************/ + public String getTableName() + { + return tableName; + } + + + + /******************************************************************************* + ** Getter for fields + ** + *******************************************************************************/ + public Map getFields() + { + return fields; + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java new file mode 100644 index 00000000..109a8255 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/DeserializerUtils.java @@ -0,0 +1,195 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.serialization; + + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; +import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher; +import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; +import com.kingsrook.qqq.backend.core.utils.StringUtils; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class DeserializerUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public static QBackendModuleInterface getBackendModule(TreeNode treeNode) throws IOException + { + 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() + ")"); + } + else + { + String backendType = textNode.asText(); + + try + { + return new QBackendModuleDispatcher().getQBackendModule(backendType); + } + catch(QModuleDispatchException e) + { + throw (new IOException(e)); + } + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static T reflectivelyDeserialize(Class _class, TreeNode treeNode) throws IOException + { + try + { + T output = _class.getConstructor().newInstance(); + System.out.println("Reflectively deserializing a: " + _class.getName()); + + Map> setterMap = new HashMap<>(); + for(Method method : _class.getMethods()) + { + if(method.getName().startsWith("set") && method.getParameterTypes().length == 1) + { + Class parameterType = method.getParameterTypes()[0]; + String fieldName = method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4); + + setterMap.put(fieldName, (String value) -> + { + try + { + if(parameterType.equals(String.class)) + { + method.invoke(output, value); + } + else if(parameterType.equals(Integer.class)) + { + method.invoke(output, StringUtils.hasContent(value) ? Integer.parseInt(value) : null); + } + else if(parameterType.equals(Long.class)) + { + method.invoke(output, StringUtils.hasContent(value) ? Long.parseLong(value) : null); + } + else if(parameterType.equals(BigDecimal.class)) + { + method.invoke(output, StringUtils.hasContent(value) ? new BigDecimal(value) : null); + } + else if(parameterType.equals(Boolean.class)) + { + method.invoke(output, StringUtils.hasContent(value) ? Boolean.parseBoolean(value) : null); + } + else if(parameterType.equals(Class.class)) + { + //////////////////////////////////////////////////////////// + // specifically do NOT try to handle Class type arguments // + //////////////////////////////////////////////////////////// + } + else if(parameterType.getPackageName().startsWith("java.")) + { + //////////////////////////////////////////////////////////////////////// + // if we hit this, we might want to add an else-if to handle the type // + //////////////////////////////////////////////////////////////////////// + throw (new RuntimeException("Field " + fieldName + " is of an unhandled type " + parameterType.getName() + " when deserializing " + _class.getName())); + } + else + { + //////////////////////////////////// + // gracefully ignore other types. // + //////////////////////////////////// + } + } + catch(IllegalAccessException | InvocationTargetException e) + { + throw new RuntimeException(e); + } + }); + } + } + + DeserializerUtils.deserializeBean(treeNode, setterMap); + + return output; + } + catch(Exception e) + { + throw (new IOException("Error reflectively deserializing table details", e)); + } + + } + + + + /******************************************************************************* + ** Helper for custom jackson serializers - allows the caller to specify a map + ** of field names to setter methods. + ** + ** 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. + *******************************************************************************/ + private static void deserializeBean(TreeNode treeNode, Map> setterMap) throws IOException + { + Iterator fieldNamesIterator = treeNode.fieldNames(); + while(fieldNamesIterator.hasNext()) + { + String fieldName = fieldNamesIterator.next(); + System.out.println("Handling field [" + fieldName + "]"); + + if(!setterMap.containsKey(fieldName)) + { + throw (new IllegalArgumentException("Unexpected value: " + fieldName)); + } + + TreeNode fieldNode = treeNode.get(fieldName); + if(fieldNode instanceof NullNode) + { + setterMap.get(fieldName).accept(null); + } + else + { + setterMap.get(fieldName).accept(((TextNode) fieldNode).asText()); + } + } + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QBackendMetaDataDeserializer.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QBackendMetaDataDeserializer.java new file mode 100644 index 00000000..8efd3d6e --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QBackendMetaDataDeserializer.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +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.QBackendMetaData; +import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; + + +/******************************************************************************* + ** Jackson custom deserialization class, to return an appropriate sub-type of + ** A QBackendMetaData, based on the backendType specified within. + *******************************************************************************/ +public class QBackendMetaDataDeserializer extends JsonDeserializer +{ + @Override + public QBackendMetaData deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException + { + TreeNode treeNode = jsonParser.readValueAsTree(); + QBackendModuleInterface backendModule = DeserializerUtils.getBackendModule(treeNode); + return DeserializerUtils.reflectivelyDeserialize(backendModule.getBackendMetaDataClass(), treeNode); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QTableBackendDetailsDeserializer.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QTableBackendDetailsDeserializer.java new file mode 100644 index 00000000..4f0bca0c --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QTableBackendDetailsDeserializer.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +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.QTableBackendDetails; +import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; + + +/******************************************************************************* + ** Jackson custom deserialization class, to return an appropriate sub-type of + ** QTableBackendDetails, based on the backendType of the containing table. + *******************************************************************************/ +public class QTableBackendDetailsDeserializer extends JsonDeserializer +{ + @Override + public QTableBackendDetails deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException + { + TreeNode treeNode = jsonParser.readValueAsTree(); + QBackendModuleInterface backendModule = DeserializerUtils.getBackendModule(treeNode); + return DeserializerUtils.reflectivelyDeserialize(backendModule.getTableBackendDetailsClass(), treeNode); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcher.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcher.java index 59199112..10eff096 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcher.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcher.java @@ -27,6 +27,8 @@ import java.util.Map; import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /******************************************************************************* @@ -38,6 +40,8 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface *******************************************************************************/ public class QBackendModuleDispatcher { + private static final Logger LOG = LogManager.getLogger(QBackendModuleDispatcher.class); + private Map backendTypeToModuleClassNameMap; @@ -48,9 +52,29 @@ public class QBackendModuleDispatcher public QBackendModuleDispatcher() { backendTypeToModuleClassNameMap = new HashMap<>(); - backendTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.mock.MockBackendModule"); - backendTypeToModuleClassNameMap.put("rdbms", "com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule"); - // todo - let user define custom type -> classes + + String[] moduleClassNames = new String[] + { + // todo - let modules somehow "export" their types here? + // e.g., backend-core shouldn't need to "know" about the modules. + "com.kingsrook.qqq.backend.core.modules.mock.MockBackendModule", + "com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule", + "com.kingsrook.qqq.backend.module.filesystem.FilesystemBackendModule" + }; + + for(String moduleClassName : moduleClassNames) + { + try + { + Class moduleClass = Class.forName(moduleClassName); + QBackendModuleInterface module = (QBackendModuleInterface) moduleClass.getConstructor().newInstance(); + backendTypeToModuleClassNameMap.put(module.getBackendType(), moduleClassName); + } + catch(Exception e) + { + LOG.debug("Backend module [{}] could not be loaded: {}", moduleClassName, e.getMessage()); + } + } } @@ -58,14 +82,24 @@ public class QBackendModuleDispatcher /******************************************************************************* ** *******************************************************************************/ - public QBackendModuleInterface getQModule(QBackendMetaData backend) throws QModuleDispatchException + public QBackendModuleInterface getQBackendModule(QBackendMetaData backend) throws QModuleDispatchException + { + return (getQBackendModule(backend.getBackendType())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QBackendModuleInterface getQBackendModule(String backendType) throws QModuleDispatchException { try { - String className = backendTypeToModuleClassNameMap.get(backend.getType()); - if (className == null) + String className = backendTypeToModuleClassNameMap.get(backendType); + if(className == null) { - throw (new QModuleDispatchException("Unrecognized backend type [" + backend.getType() + "] in dispatcher.")); + throw (new QModuleDispatchException("Unrecognized backend type [" + backendType + "] in dispatcher.")); } Class moduleClass = Class.forName(className); @@ -77,7 +111,7 @@ public class QBackendModuleDispatcher } catch(Exception e) { - throw (new QModuleDispatchException("Error getting backend module of type: " + backend.getType(), e)); + throw (new QModuleDispatchException("Error getting backend module of type: " + backendType, e)); } } } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QBackendModuleInterface.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QBackendModuleInterface.java index 449a0ebc..6ebfddfc 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QBackendModuleInterface.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/interfaces/QBackendModuleInterface.java @@ -22,15 +22,37 @@ package com.kingsrook.qqq.backend.core.modules.interfaces; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails; + + /******************************************************************************* ** Interface that a QBackendModule must implement. ** - ** Note, methods all have a default version, which throws a 'not implemented' + ** Note, some methods all have a default version, which throws a 'not implemented' ** exception. ** *******************************************************************************/ public interface QBackendModuleInterface { + /******************************************************************************* + ** Method where a backend module must be able to provide its type (name). + *******************************************************************************/ + String getBackendType(); + + /******************************************************************************* + ** Method to identify the class used for backend meta data for this module. + *******************************************************************************/ + Class getBackendMetaDataClass(); + + /******************************************************************************* + ** Method to identify the class used for table-backend details for this module. + *******************************************************************************/ + default Class getTableBackendDetailsClass() + { + return QTableBackendDetails.class; + } + /******************************************************************************* ** *******************************************************************************/ @@ -74,4 +96,5 @@ public interface QBackendModuleInterface { throw new IllegalStateException(actionName + " is not implemented in this module: " + this.getClass().getSimpleName()); } + } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/modules/mock/MockBackendModule.java b/src/main/java/com/kingsrook/qqq/backend/core/modules/mock/MockBackendModule.java index 7227c47b..652d7be6 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/modules/mock/MockBackendModule.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/modules/mock/MockBackendModule.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.modules.mock; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; @@ -38,6 +39,28 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; *******************************************************************************/ public class MockBackendModule implements QBackendModuleInterface { + /******************************************************************************* + ** Method where a backend module must be able to provide its type (name). + *******************************************************************************/ + @Override + public String getBackendType() + { + return ("mock"); + } + + + + /******************************************************************************* + ** Method to identify the class used for backend meta data for this module. + *******************************************************************************/ + @Override + public Class getBackendMetaDataClass() + { + return (QBackendMetaData.class); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -59,6 +82,7 @@ public class MockBackendModule implements QBackendModuleInterface } + /******************************************************************************* ** *******************************************************************************/ @@ -69,6 +93,7 @@ public class MockBackendModule implements QBackendModuleInterface } + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java index 5914d0fa..54ca9c0f 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.utils; import java.io.IOException; +import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -58,8 +59,8 @@ public class JsonUtils { try { - ObjectMapper mapper = newObjectMapper(); - String jsonResult = mapper.writeValueAsString(object); + ObjectMapper mapper = newObjectMapper(); + String jsonResult = mapper.writeValueAsString(object); return (jsonResult); } catch(JsonProcessingException e) @@ -81,9 +82,9 @@ public class JsonUtils { try { - ObjectMapper mapper = newObjectMapper(); + ObjectMapper mapper = newObjectMapper(); ObjectWriter objectWriter = mapper.writerWithDefaultPrettyPrinter(); - String jsonResult = objectWriter.writeValueAsString(object); + String jsonResult = objectWriter.writeValueAsString(object); return (jsonResult); } catch(JsonProcessingException e) @@ -94,6 +95,30 @@ public class JsonUtils } + + /******************************************************************************* + ** Serialize any object into a "pretty" / formatted JSON String. + ** + *******************************************************************************/ + public static String prettyPrint(String json) + { + try + { + ObjectMapper mapper = newObjectMapper(); + Object object = mapper.reader().readValue(json, Map.class); + ObjectWriter objectWriter = mapper.writerWithDefaultPrettyPrinter(); + String jsonResult = objectWriter.writeValueAsString(object); + return (jsonResult); + } + catch(Exception e) + { + LOG.error("Error pretty printing JSON string", e); + throw new IllegalArgumentException("Error in pretty printing JSON", e); + } + } + + + /******************************************************************************* ** De-serialize a json string into an object of the specified class. ** @@ -103,8 +128,7 @@ public class JsonUtils public static T toObject(String json, Class targetClass) throws IOException { ObjectMapper objectMapper = newObjectMapper(); - T t = objectMapper.reader().readValue(json, targetClass); - return t; + return objectMapper.reader().readValue(json, targetClass); } @@ -118,8 +142,7 @@ public class JsonUtils public static T toObject(String json, TypeReference typeReference) throws IOException { ObjectMapper objectMapper = newObjectMapper(); - T t = objectMapper.readValue(json, typeReference); - return t; + return objectMapper.readValue(json, typeReference); } @@ -192,4 +215,5 @@ public class JsonUtils { return (json != null && json.matches("(?s)\\s*\\[.*")); } + } diff --git a/src/test/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcherTest.java b/src/test/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcherTest.java index fd81cd0c..c255624a 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcherTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/modules/QBackendModuleDispatcherTest.java @@ -44,7 +44,7 @@ class QBackendModuleDispatcherTest @Test public void test_getQModule_valid() throws QModuleDispatchException { - QBackendModuleInterface qModule = new QBackendModuleDispatcher().getQModule(TestUtils.defineBackend()); + QBackendModuleInterface qModule = new QBackendModuleDispatcher().getQBackendModule(TestUtils.defineBackend()); assertNotNull(qModule); } @@ -60,8 +60,8 @@ class QBackendModuleDispatcherTest assertThrows(QModuleDispatchException.class, () -> { QBackendMetaData qBackendMetaData = TestUtils.defineBackend(); - qBackendMetaData.setType("aTypeThatWontEverExist"); - new QBackendModuleDispatcher().getQModule(qBackendMetaData); + qBackendMetaData.setBackendType("aTypeThatWontEverExist"); + new QBackendModuleDispatcher().getQBackendModule(qBackendMetaData); }); } diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java index 6c6f8a88..2e353096 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java @@ -103,7 +103,7 @@ public class TestUtils { return new QBackendMetaData() .withName("default") - .withType("mock"); + .withBackendType("mock"); } diff --git a/src/test/resources/personQInstanceIncludingBackend.json b/src/test/resources/personQInstanceIncludingBackend.json index 74095833..5502e715 100644 --- a/src/test/resources/personQInstanceIncludingBackend.json +++ b/src/test/resources/personQInstanceIncludingBackend.json @@ -150,9 +150,8 @@ }, "backends": { "default": { - "values": null, "name": "default", - "type": "mock" + "backendType": "mock" } }, "authentication": {