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);
+ }
+}