From 5453e2e08125b855a15a2ac7e189484f55aed0ed Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 12 Apr 2023 11:18:32 -0500 Subject: [PATCH] Initial add of MetaDataProducer --- .../core/model/metadata/MetaDataProducer.java | 53 ++++++ .../metadata/MetaDataProducerHelper.java | 154 ++++++++++++++++++ .../metadata/TopLevelMetaDataInterface.java | 37 +++++ .../model/metadata/joins/QJoinMetaData.java | 15 +- .../possiblevalues/QPossibleValueSource.java | 15 +- .../metadata/processes/QProcessMetaData.java | 15 +- .../model/metadata/tables/QTableMetaData.java | 14 +- .../metadata/MetaDataProducerHelperTest.java | 47 ++++++ .../producers/TestMetaDataProducer.java | 48 ++++++ 9 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/TopLevelMetaDataInterface.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducer.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java new file mode 100644 index 00000000..9a899e81 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java @@ -0,0 +1,53 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.kingsrook.qqq.backend.core.exceptions.QException; + + +/******************************************************************************* + ** Abstract class that knows how to produce meta data objects. Useful with + ** MetaDataProducerHelper, to put point at a package full of these, and populate + ** your whole QInstance. + *******************************************************************************/ +public abstract class MetaDataProducer +{ + + /******************************************************************************* + ** Produce the metaData object. Generally, you don't want to add it to the instance + ** yourself - but the instance is there in case you need it to get other metaData. + *******************************************************************************/ + public abstract T produce(QInstance qInstance) throws QException; + + + + /******************************************************************************* + ** In case this producer needs to run before (or after) others, this method + ** can help influence that (e.g., if used by MetaDataProducerHelper). + *******************************************************************************/ + public int getSortOrder() + { + return (500); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java new file mode 100644 index 00000000..c310570c --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java @@ -0,0 +1,154 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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 java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; + + +/******************************************************************************* + ** Help work with MetaDataProducers. + *******************************************************************************/ +public class MetaDataProducerHelper +{ + private static final QLogger LOG = QLogger.getLogger(MetaDataProducerHelper.class); + + + + /******************************************************************************* + ** Recursively find all classes in the given package, that extend MetaDataProducer, + ** run them, and add their output to the given qInstance. + ** + ** Note - they'll be sorted by the sortOrder they provide. + *******************************************************************************/ + public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) + { + //////////////////////////////////////////////////////////// + // find all the meta data producer classes in the package // + //////////////////////////////////////////////////////////// + List> classesInPackage = getClassesInPackage(packageName); + List> producers = new ArrayList<>(); + for(Class aClass : classesInPackage) + { + try + { + if(Modifier.isAbstract(aClass.getModifiers())) + { + continue; + } + + for(Constructor constructor : aClass.getConstructors()) + { + if(constructor.getParameterCount() == 0) + { + Object o = constructor.newInstance(); + if(o instanceof MetaDataProducer metaDataProducer) + { + producers.add(metaDataProducer); + } + break; + } + } + } + catch(Exception e) + { + LOG.info("Error adding metaData from producer", logPair("producer", aClass.getSimpleName()), e); + } + } + + ///////////////////////////// + // sort them by sort order // + ///////////////////////////// + producers.sort(Comparator.comparing(p -> p.getSortOrder())); + + ////////////////////////////////////////////////////////////// + // execute each one, adding their meta data to the instance // + ////////////////////////////////////////////////////////////// + for(MetaDataProducer producer : producers) + { + try + { + TopLevelMetaDataInterface metaData = producer.produce(instance); + if(metaData != null) + { + metaData.addSelfToInstance(instance); + } + } + catch(Exception e) + { + LOG.warn("error executing metaDataProducer", logPair("producer", producer.getClass().getSimpleName()), e); + } + } + + } + + + + /******************************************************************************* + ** Thanks, Chat GPT. + *******************************************************************************/ + private static List> getClassesInPackage(String packageName) + { + List> classes = new ArrayList<>(); + + String path = packageName.replace('.', '/'); + File directory = new File(Thread.currentThread().getContextClassLoader().getResource(path).getFile()); + + if(directory.exists()) + { + File[] files = directory.listFiles(); + if(files != null) + { + for(File file : files) + { + if(file.isFile() && file.getName().endsWith(".class")) + { + String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6); + try + { + classes.add(Class.forName(className)); + } + catch(ClassNotFoundException e) + { + // Ignore, class not found + } + } + else if(file.isDirectory()) + { + List> subClasses = getClassesInPackage(packageName + "." + file.getName()); + classes.addAll(subClasses); + } + } + } + } + + return (classes); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/TopLevelMetaDataInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/TopLevelMetaDataInterface.java new file mode 100644 index 00000000..7be8db54 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/TopLevelMetaDataInterface.java @@ -0,0 +1,37 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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; + + +/******************************************************************************* + ** Interface for meta-data classes that can be added directly (e.g, at the top + ** level) to a QInstance (such as a QTableMetaData - not a QFieldMetaData). + *******************************************************************************/ +public interface TopLevelMetaDataInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + void addSelfToInstance(QInstance qInstance); + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java index 21791bc4..1f35d165 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java @@ -25,13 +25,15 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; import com.kingsrook.qqq.backend.core.utils.StringUtils; /******************************************************************************* ** Definition of how 2 tables join together within a QQQ Instance. *******************************************************************************/ -public class QJoinMetaData +public class QJoinMetaData implements TopLevelMetaDataInterface { private String name; private JoinType type; @@ -317,4 +319,15 @@ public class QJoinMetaData } return (leftTable + "Join" + StringUtils.ucFirst(rightTable)); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void addSelfToInstance(QInstance qInstance) + { + qInstance.addJoin(this); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java index c4fe0551..76a03b90 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java @@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; @@ -34,7 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; ** table or an enum). ** *******************************************************************************/ -public class QPossibleValueSource +public class QPossibleValueSource implements TopLevelMetaDataInterface { private String name; private String label; @@ -587,4 +589,15 @@ public class QPossibleValueSource return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void addSelfToInstance(QInstance qInstance) + { + qInstance.addPossibleValueSource(this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java index 964720cb..4f29ac04 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; @@ -43,7 +45,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; ** Meta-Data to define a process in a QQQ instance. ** *******************************************************************************/ -public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules +public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface { private String name; private String label; @@ -531,4 +533,15 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void addSelfToInstance(QInstance qInstance) + { + qInstance.addProcess(this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java index a50e8c09..77249eef 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java @@ -37,6 +37,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; @@ -53,7 +55,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf; ** Meta-Data to define a table in a QQQ instance. ** *******************************************************************************/ -public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules +public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface { private String name; private String label; @@ -1284,4 +1286,14 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void addSelfToInstance(QInstance qInstance) + { + qInstance.addTable(this); + } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java new file mode 100644 index 00000000..9ee4dee7 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java @@ -0,0 +1,47 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducer; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for MetaDataProducerHelper + *******************************************************************************/ +class MetaDataProducerHelperTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + QInstance qInstance = new QInstance(); + MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, "com.kingsrook.qqq.backend.core.model.metadata.producers"); + assertTrue(qInstance.getTables().containsKey(TestMetaDataProducer.NAME)); + } + +} \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducer.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducer.java new file mode 100644 index 00000000..6e1ab908 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducer.java @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestMetaDataProducer extends MetaDataProducer +{ + public static final String NAME = "BuiltByProducer"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +}