From edec6d64e30ab20f13650d46bd89020fb8457bda Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:54:29 -0600 Subject: [PATCH 1/7] Add more validation of the join and associated table, in table associations. --- .../core/instances/QInstanceValidator.java | 28 ++++++++++++- .../instances/QInstanceValidatorTest.java | 41 +++++++++++++++---- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 58618c3e..4d5e8c51 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -70,6 +70,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; @@ -780,14 +781,37 @@ public class QInstanceValidator if(assertCondition(StringUtils.hasContent(association.getName()), "missing a name for an Association on table " + table.getName())) { String messageSuffix = " for Association " + association.getName() + " on table " + table.getName(); + boolean recognizedTable = false; if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix)) { - assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix); + if(assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix)) + { + recognizedTable = true; + } } if(assertCondition(StringUtils.hasContent(association.getJoinName()), "missing joinName" + messageSuffix)) { - assertCondition(qInstance.getJoin(association.getJoinName()) != null, "unrecognized joinName " + association.getJoinName() + messageSuffix); + QJoinMetaData join = qInstance.getJoin(association.getJoinName()); + if(assertCondition(join != null, "unrecognized joinName " + association.getJoinName() + messageSuffix)) + { + assert join != null; // covered by the assertCondition + + if(recognizedTable) + { + boolean isLeftToRight = join.getLeftTable().equals(table.getName()) && join.getRightTable().equals(association.getAssociatedTableName()); + boolean isRightToLeft = join.getRightTable().equals(table.getName()) && join.getLeftTable().equals(association.getAssociatedTableName()); + assertCondition(isLeftToRight || isRightToLeft, "join [" + association.getJoinName() + "] does not connect tables [" + table.getName() + "] and [" + association.getAssociatedTableName() + "]" + messageSuffix); + if(isLeftToRight) + { + assertCondition(join.getType().equals(JoinType.ONE_TO_MANY) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side side (left)" + messageSuffix); + } + else if(isRightToLeft) + { + assertCondition(join.getType().equals(JoinType.MANY_TO_ONE) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side (right)" + messageSuffix); + } + } + } } } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index 89c69734..65198111 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -2054,16 +2054,41 @@ public class QInstanceValidatorTest extends BaseTest assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation"))), "missing joinName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER, - "missing associatedTableName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "missing associatedTableName for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation").withJoinName("notAJoin").withAssociatedTableName(TestUtils.TABLE_NAME_LINE_ITEM))), - "unrecognized joinName notAJoin for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "unrecognized joinName notAJoin for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation").withJoinName("orderLineItem").withAssociatedTableName("notATable"))), - "unrecognized associatedTableName notATable for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER - ); + "unrecognized associatedTableName notATable for Association myAssociation on table " + TestUtils.TABLE_NAME_ORDER); + + ////////////////////////////////// + // wrong join on an association // + ////////////////////////////////// + assertValidationFailureReasons((qInstance -> + { + Association association = qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getAssociationByName("orderLine").orElseThrow(); + association.setJoinName("orderOrderExtrinsic"); + }), + "join [orderOrderExtrinsic] does not connect tables [order] and [orderLine]"); + + ////////////////////////////////////////// + // wrong table (doesn't match the join) // + ////////////////////////////////////////// + assertValidationFailureReasons((qInstance -> + { + Association association = qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getAssociationByName("orderLine").orElseThrow(); + association.setAssociatedTableName(TestUtils.TABLE_NAME_ORDER_EXTRINSIC); + }), + "join [orderLineItem] does not connect tables [order] and [orderExtrinsic]"); + + ////////////////////////////// + // invalid type on the join // + ////////////////////////////// + assertValidationFailureReasons((qInstance -> qInstance.getJoin("orderLineItem").setType(JoinType.MANY_TO_MANY)), + "Join type does not have 'one' on this table's side side (left)"); + assertValidationFailureReasons((qInstance -> qInstance.getJoin("orderLineItem").setType(JoinType.MANY_TO_ONE)), + "Join type does not have 'one' on this table's side side (left)"); } @@ -2323,7 +2348,7 @@ public class QInstanceValidatorTest extends BaseTest { int noOfReasons = actualReasons == null ? 0 : actualReasons.size(); assertEquals(expectedReasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", expectedReasons) - + "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", actualReasons) : "--")); + + "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", actualReasons) : "--")); } for(String reason : expectedReasons) @@ -2451,6 +2476,7 @@ public class QInstanceValidatorTest extends BaseTest public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {} + /*************************************************************************** ** ***************************************************************************/ @@ -2468,6 +2494,7 @@ public class QInstanceValidatorTest extends BaseTest } + /*************************************************************************** ** ***************************************************************************/ From 21a5c98376b50e7f2d5634ee2049c198d965f7a8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:56:46 -0600 Subject: [PATCH 2/7] add method addIfNotNull --- .../backend/core/utils/CollectionUtils.java | 12 +++++++++++ .../core/utils/CollectionUtilsTest.java | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java index f3b3e6ee..90e2756d 100755 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java @@ -700,4 +700,16 @@ public class CollectionUtils return (map.containsKey(key) && map.get(key) != null); } + + + /*************************************************************************** + ** add an element to a collection, but, only if the element isn't null + ***************************************************************************/ + public static void addIfNotNull(Collection c, E element) + { + if(element != null) + { + c.add(element); + } + } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java index e6de12b3..a243c775 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java @@ -26,10 +26,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import com.google.gson.reflect.TypeToken; @@ -618,4 +620,23 @@ class CollectionUtilsTest extends BaseTest 4, Map.of("B", "B4")), output); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testAddIfNotNull() + { + HashSet s = new HashSet<>(); + CollectionUtils.addIfNotNull(s, null); + assertEquals(Set.of(), s); + + CollectionUtils.addIfNotNull(s, ""); + assertEquals(Set.of(""), s); + + CollectionUtils.addIfNotNull(s, "1"); + assertEquals(Set.of("", "1"), s); + } + } From 5070f0a738ee679862a2133568066afc5ff8d20a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:56:58 -0600 Subject: [PATCH 3/7] add method emptyToNull --- .../qqq/backend/core/utils/StringUtils.java | 15 +++++++++++++++ .../qqq/backend/core/utils/StringUtilsTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java index a8348756..9382e5fc 100755 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java @@ -460,4 +460,19 @@ public class StringUtils return (Pattern.matches("[a-f0-9]{8}(?:-[a-f0-9]{4}){4}[a-f0-9]{8}", s)); } + + + /*************************************************************************** + ** + ***************************************************************************/ + public static String emptyToNull(String s) + { + if(!hasContent(s)) + { + return (null); + } + + return (s); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/StringUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/StringUtilsTest.java index b2cad605..d0aae185 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/StringUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/StringUtilsTest.java @@ -318,4 +318,19 @@ class StringUtilsTest extends BaseTest assertEquals("Apples were eaten", StringUtils.pluralFormat(2, "Apple{,s} {was,were} eaten")); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testEmptyToNull() + { + assertNull(StringUtils.emptyToNull(null)); + assertNull(StringUtils.emptyToNull("")); + assertNull(StringUtils.emptyToNull(" ")); + assertNull(StringUtils.emptyToNull(" ")); + assertEquals("a", StringUtils.emptyToNull("a")); + } + } From d2c0ad498f9e67ac2fedcedae567a7602e9bbd74 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:59:20 -0600 Subject: [PATCH 4/7] add method getAssociationByName --- .../model/metadata/tables/QTableMetaData.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 5bbba5b6..e6078562 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 @@ -1329,6 +1329,21 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData + /******************************************************************************* + ** Getter for an association by name + *******************************************************************************/ + public Optional getAssociationByName(String name) + { + if(associations == null) + { + return (Optional.empty()); + } + + return (getAssociations().stream().filter(a -> a.getName().equals(name)).findFirst()); + } + + + /******************************************************************************* ** Setter for associations *******************************************************************************/ From 16f931cd5c36e7c0da0d8335ad335c0665657e29 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 10:59:44 -0600 Subject: [PATCH 5/7] javadoc cleanup --- .../qqq/backend/core/model/metadata/MetaDataProducer.java | 2 +- .../core/model/metadata/MetaDataProducerInterface.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 index 15ae31d7..a4dbe375 100644 --- 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 @@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata; /******************************************************************************* ** Abstract class that knows how to produce meta data objects. Useful with - ** MetaDataProducerHelper, to put point at a package full of these, and populate + ** MetaDataProducerHelper, to point at a package full of these, and populate ** your whole QInstance. *******************************************************************************/ public abstract class MetaDataProducer implements MetaDataProducerInterface diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerInterface.java index e91073e5..fa725451 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerInterface.java @@ -27,12 +27,12 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; /******************************************************************************* ** Interface for classes that know how to produce meta data objects. Useful with - ** MetaDataProducerHelper, to put point at a package full of these, and populate + ** MetaDataProducerHelper, to point at a package full of these, and populate ** your whole QInstance. ** ** See also MetaDataProducer - an implementer of this interface, which actually ** came first, and is fine to extend if producing a meta-data class is all your - ** clas means to do (nice and "Single-responsibility principle"). + ** class means to do (nice and "Single-responsibility principle"). ** ** But, in some applications you may want to, for example, have one class that ** defines a process step, and also produces the meta-data for that process, so From ec74649c96e41b8cac89e390c27afe2312c48556 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 11:23:38 -0600 Subject: [PATCH 6/7] Introduce annotations that can be found by MetaDataProducerHelper, to make more meta-data, with less code. Specifically: - PVS from PossibleValueEnum - PVS from RecordEntity - Joins from a parent-entity to child-entities - ChildRecordList Widgets from a parent-entity to child-entities --- .../metadata/MetaDataProducerHelper.java | 217 ++++++++++++++++-- .../possiblevalues/QPossibleValue.java | 36 +++ ...omRecordEntityGenericMetaDataProducer.java | 105 +++++++++ ...omRecordEntityGenericMetaDataProducer.java | 100 ++++++++ ...ueSourceOfEnumGenericMetaDataProducer.java | 64 ++++++ ...eSourceOfTableGenericMetaDataProducer.java | 61 +++++ .../producers/annotations/ChildJoin.java | 38 +++ .../annotations/ChildRecordListWidget.java | 46 ++++ .../producers/annotations/ChildTable.java | 45 ++++ .../annotations/QMetaDataProducingEntity.java | 48 ++++ .../QMetaDataProducingPossibleValueEnum.java | 42 ++++ .../metadata/MetaDataProducerHelperTest.java | 54 +++++ .../TestMetaDataProducingChildEntity.java | 140 +++++++++++ .../TestMetaDataProducingEntity.java | 119 ++++++++++ ...estMetaDataProducingPossibleValueEnum.java | 74 ++++++ 15 files changed, 1175 insertions(+), 14 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildJoinFromRecordEntityGenericMetaDataProducer.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfTableGenericMetaDataProducer.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildJoin.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildRecordListWidget.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildTable.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingEntity.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingPossibleValueEnum.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingChildEntity.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingEntity.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingPossibleValueEnum.java 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 index ac68cfd2..4fd45277 100644 --- 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 @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Comparator; @@ -31,10 +32,23 @@ import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.data.QField; +import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum; +import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildJoinFromRecordEntityGenericMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfEnumGenericMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfTableGenericMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum; import com.kingsrook.qqq.backend.core.utils.ClassPathUtils; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -90,6 +104,9 @@ public class MetaDataProducerHelper } List> producers = new ArrayList<>(); + //////////////////////////////////////////////////////////////////////////////////////// + // loop over classes, processing them based on either their type or their annotations // + //////////////////////////////////////////////////////////////////////////////////////// for(Class aClass : classesInPackage) { try @@ -101,23 +118,27 @@ public class MetaDataProducerHelper if(MetaDataProducerInterface.class.isAssignableFrom(aClass)) { - boolean foundValidConstructor = false; - for(Constructor constructor : aClass.getConstructors()) - { - if(constructor.getParameterCount() == 0) - { - Object o = constructor.newInstance(); - producers.add((MetaDataProducerInterface) o); - foundValidConstructor = true; - break; - } - } + CollectionUtils.addIfNotNull(producers, processMetaDataProducer(aClass)); + } - if(!foundValidConstructor) + if(aClass.isAnnotationPresent(QMetaDataProducingEntity.class)) + { + QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class); + if(qMetaDataProducingEntity.producePossibleValueSource()) { - LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName())); + producers.addAll(processMetaDataProducingEntity(aClass)); } } + + if(aClass.isAnnotationPresent(QMetaDataProducingPossibleValueEnum.class)) + { + QMetaDataProducingPossibleValueEnum qMetaDataProducingPossibleValueEnum = aClass.getAnnotation(QMetaDataProducingPossibleValueEnum.class); + if(qMetaDataProducingPossibleValueEnum.producePossibleValueSource()) + { + CollectionUtils.addIfNotNull(producers, processMetaDataProducingPossibleValueEnum(aClass)); + } + } + } catch(Exception e) { @@ -168,7 +189,175 @@ public class MetaDataProducerHelper LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName())); } } - } + + + /*************************************************************************** + ** + ***************************************************************************/ + private static MetaDataProducerInterface processMetaDataProducingPossibleValueEnum(Class aClass) + { + String warningPrefix = "Found a class annotated as @" + QMetaDataProducingPossibleValueEnum.class.getSimpleName(); + if(!PossibleValueEnum.class.isAssignableFrom(aClass)) + { + LOG.warn(warningPrefix + ", but which is not a " + PossibleValueEnum.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName())); + return null; + } + + PossibleValueEnum[] values = (PossibleValueEnum[]) aClass.getEnumConstants(); + return (new PossibleValueSourceOfEnumGenericMetaDataProducer<>(aClass.getSimpleName(), values)); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static List> processMetaDataProducingEntity(Class aClass) throws Exception + { + List> rs = new ArrayList<>(); + + String warningPrefix = "Found a class annotated as @" + QMetaDataProducingEntity.class.getSimpleName(); + if(!QRecordEntity.class.isAssignableFrom(aClass)) + { + LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName())); + return (rs); + } + + Field tableNameField = aClass.getDeclaredField("TABLE_NAME"); + if(!tableNameField.getType().equals(String.class)) + { + LOG.warn(warningPrefix + ", but whose TABLE_NAME field is not a String, so it will not be used.", logPair("class", aClass.getSimpleName())); + return (rs); + } + + String tableNameValue = (String) tableNameField.get(null); + rs.add(new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue)); + + ////////////////////////// + // process child tables // + ////////////////////////// + QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class); + for(ChildTable childTable : qMetaDataProducingEntity.childTables()) + { + Class childEntityClass = childTable.childTableEntityClass(); + if(childTable.childJoin().enabled()) + { + CollectionUtils.addIfNotNull(rs, processChildJoin(aClass, childTable)); + + if(childTable.childRecordListWidget().enabled()) + { + CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(aClass, childTable)); + } + } + else + { + if(childTable.childRecordListWidget().enabled()) + { + ////////////////////////////////////////////////////////////////////////// + // if not doing the join, can't do the child-widget, so warn about that // + ////////////////////////////////////////////////////////////////////////// + LOG.warn(warningPrefix + " requested to produce a ChildRecordListWidget, but not produce a Join - which is not allowed (must do join to do widget). ", logPair("class", aClass.getSimpleName()), logPair("childEntityClass", childEntityClass.getSimpleName())); + } + } + } + + return (rs); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static MetaDataProducerInterface processChildRecordListWidget(Class aClass, ChildTable childTable) throws Exception + { + Class childEntityClass = childTable.childTableEntityClass(); + String parentTableName = getTableNameStaticFieldValue(aClass); + String childTableName = getTableNameStaticFieldValue(childEntityClass); + + ChildRecordListWidget childRecordListWidget = childTable.childRecordListWidget(); + return (new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget)); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static String findPossibleValueField(Class entityClass, String possibleValueSourceName) + { + for(Field field : entityClass.getDeclaredFields()) + { + if(field.isAnnotationPresent(QField.class)) + { + QField qField = field.getAnnotation(QField.class); + if(qField.possibleValueSourceName().equals(possibleValueSourceName)) + { + return field.getName(); + } + } + } + + return (null); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static MetaDataProducerInterface processChildJoin(Class aClass, ChildTable childTable) throws Exception + { + Class childEntityClass = childTable.childTableEntityClass(); + + String parentTableName = getTableNameStaticFieldValue(aClass); + String childTableName = getTableNameStaticFieldValue(childEntityClass); + String possibleValueFieldName = findPossibleValueField(childEntityClass, parentTableName); + if(!StringUtils.hasContent(possibleValueFieldName)) + { + LOG.warn("Could not find field in [" + childEntityClass.getSimpleName() + "] with possibleValueSource referencing table [" + aClass.getSimpleName() + "]"); + return (null); + } + + return (new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName)); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static MetaDataProducerInterface processMetaDataProducer(Class aClass) throws Exception + { + for(Constructor constructor : aClass.getConstructors()) + { + if(constructor.getParameterCount() == 0) + { + Object o = constructor.newInstance(); + return (MetaDataProducerInterface) o; + } + } + + LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName())); + return null; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private static String getTableNameStaticFieldValue(Class aClass) throws NoSuchFieldException, IllegalAccessException + { + Field tableNameField = aClass.getDeclaredField("TABLE_NAME"); + if(!tableNameField.getType().equals(String.class)) + { + return (null); + } + + String tableNameValue = (String) tableNameField.get(null); + return (tableNameValue); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValue.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValue.java index a36fb981..40a9dde2 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValue.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValue.java @@ -22,6 +22,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues; +import java.util.Objects; + + /******************************************************************************* ** An actual possible value - an id and label. ** @@ -76,4 +79,37 @@ public class QPossibleValue { return label; } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + + if(o == null || getClass() != o.getClass()) + { + return false; + } + + QPossibleValue that = (QPossibleValue) o; + return Objects.equals(id, that.id) && Objects.equals(label, that.label); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public int hashCode() + { + return Objects.hash(id, label); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildJoinFromRecordEntityGenericMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildJoinFromRecordEntityGenericMetaDataProducer.java new file mode 100644 index 00000000..76c892cd --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildJoinFromRecordEntityGenericMetaDataProducer.java @@ -0,0 +1,105 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 java.util.Objects; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** Generic meta-data-producer, which should be instantiated (e.g., by + ** MetaDataProducer Helper), to produce a QJoinMetaData, based on a + ** QRecordEntity and a ChildTable sub-annotation. + ** + ** e.g., Orders & LineItems - on the Order entity + ** + @QMetaDataProducingEntity( + childTables = { @ChildTable( + childTableEntityClass = LineItem.class, + childJoin = @ChildJoin(enabled = true), + childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines")) + } + ) + public class Order extends QRecordEntity + ** + ** + ** A Join will be made: + ** - left: Order + ** - right: LineItem + ** - type: ONE_TO_MANY (one order (parent table) has mny lines (child table)) + ** - joinOn: order's primary key, lineItem's orderId field + ** - name: inferred, based on the table names orderJoinLineItem) + *******************************************************************************/ +public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDataProducerInterface +{ + private String childTableName; // e.g., lineItem + private String parentTableName; // e.g., order + private String foreignKeyFieldName; // e.g., orderId + + + + /*************************************************************************** + ** + ***************************************************************************/ + public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName) + { + Objects.requireNonNull(childTableName, "childTableName cannot be null"); + Objects.requireNonNull(parentTableName, "parentTableName cannot be null"); + Objects.requireNonNull(foreignKeyFieldName, "foreignKeyFieldName cannot be null"); + + this.childTableName = childTableName; + this.parentTableName = parentTableName; + this.foreignKeyFieldName = foreignKeyFieldName; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QJoinMetaData produce(QInstance qInstance) throws QException + { + QTableMetaData possibleValueTable = qInstance.getTable(parentTableName); + if(possibleValueTable == null) + { + throw (new QException("Could not find tableMetaData " + parentTableName)); + } + + QJoinMetaData join = new QJoinMetaData() + .withLeftTable(parentTableName) + .withRightTable(childTableName) + .withInferredName() + .withType(JoinType.ONE_TO_MANY) + .withJoinOn(new JoinOn(possibleValueTable.getPrimaryKeyField(), foreignKeyFieldName)); + + return (join); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer.java new file mode 100644 index 00000000..cb8d451f --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer.java @@ -0,0 +1,100 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.actions.dashboard.widgets.ChildRecordListRenderer; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget; +import com.kingsrook.qqq.backend.core.utils.StringUtils; + + +/******************************************************************************* + ** Generic meta-data-producer, which should be instantiated (e.g., by + ** MetaDataProducer Helper), to produce a ChildRecordList QWidgetMetaData, to + ** produce a QJoinMetaData, based on a QRecordEntity and a ChildTable sub-annotation. + ** + ** e.g., Orders & LineItems - on the Order entity + ** + @QMetaDataProducingEntity( childTables = { @ChildTable( + childTableEntityClass = LineItem.class, + childJoin = @ChildJoin(enabled = true), + childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines")) + }) + public class Order extends QRecordEntity + ** + ** + *******************************************************************************/ +public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implements MetaDataProducerInterface +{ + private String childTableName; // e.g., lineItem + private String parentTableName; // e.g., order + + private ChildRecordListWidget childRecordListWidget; + + + + /*************************************************************************** + ** + ***************************************************************************/ + public ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, ChildRecordListWidget childRecordListWidget) + { + this.childTableName = childTableName; + this.parentTableName = parentTableName; + this.childRecordListWidget = childRecordListWidget; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QWidgetMetaData produce(QInstance qInstance) throws QException + { + String name = QJoinMetaData.makeInferredJoinName(parentTableName, childTableName); + QJoinMetaData join = qInstance.getJoin(name); + + QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(join) + .withName(name) + .withLabel(childRecordListWidget.label()) + .withCanAddChildRecord(childRecordListWidget.canAddChildRecords()) + .getWidgetMetaData(); + + if(StringUtils.hasContent(childRecordListWidget.manageAssociationName())) + { + widget.withDefaultValue("manageAssociationName", childRecordListWidget.manageAssociationName()); + } + + if(childRecordListWidget.maxRows() > 0) + { + widget.withDefaultValue("maxRows", childRecordListWidget.maxRows()); + } + + return (widget); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java new file mode 100644 index 00000000..9d617b4f --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java @@ -0,0 +1,64 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; + + +/*************************************************************************** + ** Generic meta-data-producer, which should be instantiated (e.g., by + ** MetaDataProducer Helper), to produce a QPossibleValueSource meta-data + ** based on a PossibleValueEnum + ** + ***************************************************************************/ +public class PossibleValueSourceOfEnumGenericMetaDataProducer> implements MetaDataProducerInterface +{ + private final String name; + private final PossibleValueEnum[] values; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public PossibleValueSourceOfEnumGenericMetaDataProducer(String name, PossibleValueEnum[] values) + { + this.name = name; + this.values = values; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QPossibleValueSource produce(QInstance qInstance) + { + return (QPossibleValueSource.newForEnum(name, values)); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfTableGenericMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfTableGenericMetaDataProducer.java new file mode 100644 index 00000000..c1656f35 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfTableGenericMetaDataProducer.java @@ -0,0 +1,61 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; + + +/*************************************************************************** + ** Generic meta-data-producer, which should be instantiated (e.g., by + ** MetaDataProducer Helper), to produce a QPossibleValueSource meta-data + ** based on a QRecordEntity class (which has corresponding QTableMetaData). + ** + ***************************************************************************/ +public class PossibleValueSourceOfTableGenericMetaDataProducer implements MetaDataProducerInterface +{ + private final String tableName; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public PossibleValueSourceOfTableGenericMetaDataProducer(String tableName) + { + this.tableName = tableName; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QPossibleValueSource produce(QInstance qInstance) + { + return (QPossibleValueSource.newForTable(tableName)); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildJoin.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildJoin.java new file mode 100644 index 00000000..2266679e --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildJoin.java @@ -0,0 +1,38 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.annotations; + + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/*************************************************************************** + ** value that goes inside a QMetadataProducingEntity annotation, to control + ** the generation of a QJoinMetaData + ***************************************************************************/ +@Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface ChildJoin +{ + boolean enabled(); +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildRecordListWidget.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildRecordListWidget.java new file mode 100644 index 00000000..9a5299c5 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildRecordListWidget.java @@ -0,0 +1,46 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.annotations; + + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/*************************************************************************** + ** value that goes inside a QMetadataProducingEntity annotation, to control + ** the generation of a QWidgetMetaData - for a ChildRecordList widget. + ***************************************************************************/ +@Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface ChildRecordListWidget +{ + boolean enabled(); + + String label() default ""; + + int maxRows() default 20; + + boolean canAddChildRecords() default false; + + String manageAssociationName() default ""; +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildTable.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildTable.java new file mode 100644 index 00000000..79a965ea --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/ChildTable.java @@ -0,0 +1,45 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.annotations; + + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; + + +/*************************************************************************** + ** value that goes inside a QMetadataProducingEntity annotation, to define + ** child-tables, e.g., for producing joins and childRecordList widgets + ***************************************************************************/ +@Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface ChildTable +{ + Class childTableEntityClass(); + + String joinFieldName() default ""; + + ChildJoin childJoin() default @ChildJoin(enabled = false); + + ChildRecordListWidget childRecordListWidget() default @ChildRecordListWidget(enabled = false); +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingEntity.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingEntity.java new file mode 100644 index 00000000..4e755163 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingEntity.java @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.annotations; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/******************************************************************************* + ** annotation to go on a QRecordEntity class, which you would like to be + ** processed by MetaDataProducerHelper, to automatically produce some meta-data + ** objects. Specifically supports: + ** + ** - Making a possible-value-source out of the table. + ** - Processing child tables to create joins and childRecordList widgets + *******************************************************************************/ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface QMetaDataProducingEntity +{ + boolean producePossibleValueSource() default true; + + ChildTable[] childTables() default { }; + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingPossibleValueEnum.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingPossibleValueEnum.java new file mode 100644 index 00000000..7d7edc95 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/annotations/QMetaDataProducingPossibleValueEnum.java @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.annotations; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/******************************************************************************* + ** annotation to go on a PossibleValueEnum class, which you would like to be + ** processed by MetaDataProducerHelper, to automatically produce possible-value- + ** source meta-data based on the enum. + *******************************************************************************/ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:MissingJavadocMethod") +public @interface QMetaDataProducingPossibleValueEnum +{ + boolean producePossibleValueSource() default true; +} 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 index 84f70562..61ba8d2b 100644 --- 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 @@ -23,14 +23,26 @@ package com.kingsrook.qqq.backend.core.model.metadata; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestAbstractMetaDataProducer; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestDisabledMetaDataProducer; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestImplementsMetaDataProducer; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducingChildEntity; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducingEntity; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducingPossibleValueEnum; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoInterfacesExtendsObject; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoValidConstructorMetaDataProducer; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -54,6 +66,48 @@ class MetaDataProducerHelperTest assertFalse(qInstance.getTables().containsKey(TestNoInterfacesExtendsObject.NAME)); assertFalse(qInstance.getTables().containsKey(TestAbstractMetaDataProducer.NAME)); assertFalse(qInstance.getTables().containsKey(TestDisabledMetaDataProducer.NAME)); + + ///////////////////////////////////////////// + // annotation on PVS enum -> PVS meta data // + ///////////////////////////////////////////// + assertTrue(qInstance.getPossibleValueSources().containsKey(TestMetaDataProducingPossibleValueEnum.class.getSimpleName())); + QPossibleValueSource enumPVS = qInstance.getPossibleValueSource(TestMetaDataProducingPossibleValueEnum.class.getSimpleName()); + assertEquals(QPossibleValueSourceType.ENUM, enumPVS.getType()); + assertEquals(2, enumPVS.getEnumValues().size()); + assertEquals(new QPossibleValue<>(1, "One"), enumPVS.getEnumValues().get(0)); + + ////////////////////////////////////////////// + // annotation on PVS table -> PVS meta data // + ////////////////////////////////////////////// + assertTrue(qInstance.getPossibleValueSources().containsKey(TestMetaDataProducingEntity.TABLE_NAME)); + QPossibleValueSource tablePVS = qInstance.getPossibleValueSource(TestMetaDataProducingEntity.TABLE_NAME); + assertEquals(QPossibleValueSourceType.TABLE, tablePVS.getType()); + assertEquals(TestMetaDataProducingEntity.TABLE_NAME, tablePVS.getTableName()); + + ////////////////////////////////////////////////////////////////// + // annotation on parent table w/ joined child -> join meta data // + ////////////////////////////////////////////////////////////////// + String joinName = QJoinMetaData.makeInferredJoinName(TestMetaDataProducingEntity.TABLE_NAME, TestMetaDataProducingChildEntity.TABLE_NAME); + assertTrue(qInstance.getJoins().containsKey(joinName)); + QJoinMetaData join = qInstance.getJoin(joinName); + assertEquals(TestMetaDataProducingEntity.TABLE_NAME, join.getLeftTable()); + assertEquals(TestMetaDataProducingChildEntity.TABLE_NAME, join.getRightTable()); + assertEquals(JoinType.ONE_TO_MANY, join.getType()); + assertEquals("id", join.getJoinOns().get(0).getLeftField()); + assertEquals("parentId", join.getJoinOns().get(0).getRightField()); + + ////////////////////////////////////////////////////////////////////////////////////// + // annotation on parent table w/ joined child -> child record list widget meta data // + ////////////////////////////////////////////////////////////////////////////////////// + assertTrue(qInstance.getWidgets().containsKey(joinName)); + QWidgetMetaDataInterface widget = qInstance.getWidget(joinName); + assertEquals(WidgetType.CHILD_RECORD_LIST.getType(), widget.getType()); + assertEquals("Test Children", widget.getLabel()); + assertEquals(joinName, widget.getDefaultValues().get("joinName")); + assertEquals(false, widget.getDefaultValues().get("canAddChildRecord")); + assertNull(widget.getDefaultValues().get("manageAssociationName")); + assertEquals(15, widget.getDefaultValues().get("maxRows")); + } } \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingChildEntity.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingChildEntity.java new file mode 100644 index 00000000..7b1ce205 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingChildEntity.java @@ -0,0 +1,140 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.data.QField; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** QRecord Entity for TestMetaDataProducingEntity table + *******************************************************************************/ +public class TestMetaDataProducingChildEntity extends QRecordEntity implements MetaDataProducerInterface +{ + public static final String TABLE_NAME = "testMetaDataProducingChildEntity"; + + @QField(isEditable = false, isPrimaryKey = true) + private Integer id; + + @QField(possibleValueSourceName = TestMetaDataProducingEntity.TABLE_NAME) + private Integer parentId; + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData() + .withName(TABLE_NAME) + .withFieldsFromEntity(TestMetaDataProducingChildEntity.class); + } + + + + /******************************************************************************* + ** Default constructor + *******************************************************************************/ + public TestMetaDataProducingChildEntity() + { + } + + + + /******************************************************************************* + ** Constructor that takes a QRecord + *******************************************************************************/ + public TestMetaDataProducingChildEntity(QRecord record) + { + populateFromQRecord(record); + } + + + /******************************************************************************* + ** Getter for id + *******************************************************************************/ + public Integer getId() + { + return (this.id); + } + + + + /******************************************************************************* + ** Setter for id + *******************************************************************************/ + public void setId(Integer id) + { + this.id = id; + } + + + + /******************************************************************************* + ** Fluent setter for id + *******************************************************************************/ + public TestMetaDataProducingChildEntity withId(Integer id) + { + this.id = id; + return (this); + } + + + + /******************************************************************************* + ** Getter for parentId + *******************************************************************************/ + public Integer getParentId() + { + return (this.parentId); + } + + + + /******************************************************************************* + ** Setter for parentId + *******************************************************************************/ + public void setParentId(Integer parentId) + { + this.parentId = parentId; + } + + + + /******************************************************************************* + ** Fluent setter for parentId + *******************************************************************************/ + public TestMetaDataProducingChildEntity withParentId(Integer parentId) + { + this.parentId = parentId; + return (this); + } + + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingEntity.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingEntity.java new file mode 100644 index 00000000..519d7946 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingEntity.java @@ -0,0 +1,119 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.data.QField; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildJoin; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** QRecord Entity for TestMetaDataProducingEntity table + *******************************************************************************/ +@QMetaDataProducingEntity(producePossibleValueSource = true, + childTables = + { + @ChildTable(childTableEntityClass = TestMetaDataProducingChildEntity.class, + childJoin = @ChildJoin(enabled = true), + childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Test Children", maxRows = 15)) + } +) +public class TestMetaDataProducingEntity extends QRecordEntity implements MetaDataProducerInterface +{ + public static final String TABLE_NAME = "testMetaDataProducingEntity"; + + @QField(isEditable = false, isPrimaryKey = true) + private Integer id; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData() + .withName(TABLE_NAME) + .withFieldsFromEntity(TestMetaDataProducingEntity.class); + } + + + + /******************************************************************************* + ** Default constructor + *******************************************************************************/ + public TestMetaDataProducingEntity() + { + } + + + + /******************************************************************************* + ** Constructor that takes a QRecord + *******************************************************************************/ + public TestMetaDataProducingEntity(QRecord record) + { + populateFromQRecord(record); + } + + + + /******************************************************************************* + ** Getter for id + *******************************************************************************/ + public Integer getId() + { + return (this.id); + } + + + + /******************************************************************************* + ** Setter for id + *******************************************************************************/ + public void setId(Integer id) + { + this.id = id; + } + + + + /******************************************************************************* + ** Fluent setter for id + *******************************************************************************/ + public TestMetaDataProducingEntity withId(Integer id) + { + this.id = id; + return (this); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingPossibleValueEnum.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingPossibleValueEnum.java new file mode 100644 index 00000000..05b32a9b --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestMetaDataProducingPossibleValueEnum.java @@ -0,0 +1,74 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.model.metadata.possiblevalues.PossibleValueEnum; +import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum; + + +/******************************************************************************* + ** + *******************************************************************************/ +@QMetaDataProducingPossibleValueEnum(producePossibleValueSource = true) +public enum TestMetaDataProducingPossibleValueEnum implements PossibleValueEnum +{ + ONE(1, "One"), + TWO(2, "Two"); + + + private final int id; + private final String label; + + + + /*************************************************************************** + ** + ***************************************************************************/ + TestMetaDataProducingPossibleValueEnum(int id, String label) + { + this.id = id; + this.label = label; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public String getPossibleValueLabel() + { + return label; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Integer getPossibleValueId() + { + return id; + } +} From 23e730f566257bf7216e7a2bd35afad153c5241a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 13 Dec 2024 15:18:04 -0600 Subject: [PATCH 7/7] Add an exception in PossibleValueSource.withValuesFromEnum if duplicated id values are given --- .../metadata/MetaDataProducerHelper.java | 5 +- .../possiblevalues/QPossibleValueSource.java | 21 +++- ...ueSourceOfEnumGenericMetaDataProducer.java | 4 +- .../QPossibleValueSourceTest.java | 96 +++++++++++++++++++ 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSourceTest.java 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 index 4fd45277..1ada252c 100644 --- 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 @@ -196,7 +196,8 @@ public class MetaDataProducerHelper /*************************************************************************** ** ***************************************************************************/ - private static MetaDataProducerInterface processMetaDataProducingPossibleValueEnum(Class aClass) + @SuppressWarnings("unchecked") + private static > MetaDataProducerInterface processMetaDataProducingPossibleValueEnum(Class aClass) { String warningPrefix = "Found a class annotated as @" + QMetaDataProducingPossibleValueEnum.class.getSimpleName(); if(!PossibleValueEnum.class.isAssignableFrom(aClass)) @@ -206,7 +207,7 @@ public class MetaDataProducerHelper } PossibleValueEnum[] values = (PossibleValueEnum[]) aClass.getEnumConstants(); - return (new PossibleValueSourceOfEnumGenericMetaDataProducer<>(aClass.getSimpleName(), values)); + return (new PossibleValueSourceOfEnumGenericMetaDataProducer(aClass.getSimpleName(), (PossibleValueEnum[]) values)); } 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 e8fc860b..84a24914 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 @@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; 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; @@ -97,7 +100,7 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface ** Create a new possible value source, for an enum, with default settings. ** e.g., type=ENUM; name from param values from the param; LABEL_ONLY format *******************************************************************************/ - public static > QPossibleValueSource newForEnum(String name, T[] values) + public static > QPossibleValueSource newForEnum(String name, T[] values) { return new QPossibleValueSource() .withName(name) @@ -553,11 +556,25 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface ** myPossibleValueSource.withValuesFromEnum(MyEnum.values())); ** *******************************************************************************/ - public > QPossibleValueSource withValuesFromEnum(T[] values) + public > QPossibleValueSource withValuesFromEnum(T[] values) { + Set usedIds = new HashSet<>(); + List duplicatedIds = new ArrayList<>(); + for(T t : values) { + if(usedIds.contains(t.getPossibleValueId())) + { + duplicatedIds.add(t.getPossibleValueId()); + } + addEnumValue(new QPossibleValue<>(t.getPossibleValueId(), t.getPossibleValueLabel())); + usedIds.add(t.getPossibleValueId()); + } + + if(!duplicatedIds.isEmpty()) + { + throw (new QRuntimeException("Error: Duplicated id(s) found in enum values: " + duplicatedIds)); } return (this); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java index 9d617b4f..52fbdffa 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/producers/PossibleValueSourceOfEnumGenericMetaDataProducer.java @@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal public class PossibleValueSourceOfEnumGenericMetaDataProducer> implements MetaDataProducerInterface { private final String name; - private final PossibleValueEnum[] values; + private final PossibleValueEnum[] values; @@ -45,7 +45,7 @@ public class PossibleValueSourceOfEnumGenericMetaDataProducer[] values) + public PossibleValueSourceOfEnumGenericMetaDataProducer(String name, PossibleValueEnum[] values) { this.name = name; this.values = values; diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSourceTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSourceTest.java new file mode 100644 index 00000000..73e7de84 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSourceTest.java @@ -0,0 +1,96 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.possiblevalues; + + +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + + +/******************************************************************************* + ** Unit test for QPossibleValueSource + *******************************************************************************/ +class QPossibleValueSourceTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testWithValuesFromEnum() + { + assertThatThrownBy(() -> new QPossibleValueSource().withValuesFromEnum(DupeIds.values())) + .isInstanceOf(QRuntimeException.class) + .hasMessageContaining("Duplicated id(s)") + .hasMessageMatching(".*: \\[1]$"); + } + + + /*************************************************************************** + ** + ***************************************************************************/ + private enum DupeIds implements PossibleValueEnum + { + ONE_A(1, "A"), + TWO_B(2, "B"), + ONE_C(1, "C"); + + + private final int id; + private final String label; + + + + /*************************************************************************** + ** + ***************************************************************************/ + DupeIds(int id, String label) + { + this.id = id; + this.label = label; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Integer getPossibleValueId() + { + return id; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public String getPossibleValueLabel() + { + return label; + } + } +} \ No newline at end of file