mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Merged feature/meta-data-producing-annotations into dev
This commit is contained in:
@ -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.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
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.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.joins.QJoinMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
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()))
|
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();
|
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
|
||||||
|
boolean recognizedTable = false;
|
||||||
if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix))
|
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))
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Abstract class that knows how to produce meta data objects. Useful with
|
** 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.
|
** your whole QInstance.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>
|
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
|||||||
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@ -31,10 +32,23 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
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.dashboard.QWidgetMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
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.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.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;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
@ -90,6 +104,9 @@ public class MetaDataProducerHelper
|
|||||||
}
|
}
|
||||||
List<MetaDataProducerInterface<?>> producers = new ArrayList<>();
|
List<MetaDataProducerInterface<?>> producers = new ArrayList<>();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// loop over classes, processing them based on either their type or their annotations //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(Class<?> aClass : classesInPackage)
|
for(Class<?> aClass : classesInPackage)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -101,23 +118,27 @@ public class MetaDataProducerHelper
|
|||||||
|
|
||||||
if(MetaDataProducerInterface.class.isAssignableFrom(aClass))
|
if(MetaDataProducerInterface.class.isAssignableFrom(aClass))
|
||||||
{
|
{
|
||||||
boolean foundValidConstructor = false;
|
CollectionUtils.addIfNotNull(producers, processMetaDataProducer(aClass));
|
||||||
for(Constructor<?> constructor : aClass.getConstructors())
|
}
|
||||||
{
|
|
||||||
if(constructor.getParameterCount() == 0)
|
|
||||||
{
|
|
||||||
Object o = constructor.newInstance();
|
|
||||||
producers.add((MetaDataProducerInterface<?>) o);
|
|
||||||
foundValidConstructor = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@ -168,7 +189,176 @@ public class MetaDataProducerHelper
|
|||||||
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
|
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T extends PossibleValueEnum<T>> 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<T>(aClass.getSimpleName(), (PossibleValueEnum<T>[]) values));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> aClass) throws Exception
|
||||||
|
{
|
||||||
|
List<MetaDataProducerInterface<?>> 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<? extends QRecordEntity> 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<? extends QRecordEntity> 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<? extends QRecordEntity> 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<? extends QRecordEntity> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
** 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.
|
** your whole QInstance.
|
||||||
**
|
**
|
||||||
** See also MetaDataProducer - an implementer of this interface, which actually
|
** 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
|
** 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
|
** 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
|
** defines a process step, and also produces the meta-data for that process, so
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** An actual possible value - an id and label.
|
** An actual possible value - an id and label.
|
||||||
**
|
**
|
||||||
@ -76,4 +79,37 @@ public class QPossibleValue<T>
|
|||||||
{
|
{
|
||||||
return label;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
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.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
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.TopLevelMetaDataInterface;
|
||||||
@ -97,7 +100,7 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
|||||||
** Create a new possible value source, for an enum, with default settings.
|
** 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
|
** e.g., type=ENUM; name from param values from the param; LABEL_ONLY format
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static <T extends PossibleValueEnum<?>> QPossibleValueSource newForEnum(String name, T[] values)
|
public static <I, T extends PossibleValueEnum<I>> QPossibleValueSource newForEnum(String name, T[] values)
|
||||||
{
|
{
|
||||||
return new QPossibleValueSource()
|
return new QPossibleValueSource()
|
||||||
.withName(name)
|
.withName(name)
|
||||||
@ -553,11 +556,25 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
|||||||
** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
|
** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public <T extends PossibleValueEnum<?>> QPossibleValueSource withValuesFromEnum(T[] values)
|
public <I, T extends PossibleValueEnum<I>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||||
{
|
{
|
||||||
|
Set<I> usedIds = new HashSet<>();
|
||||||
|
List<I> duplicatedIds = new ArrayList<>();
|
||||||
|
|
||||||
for(T t : values)
|
for(T t : values)
|
||||||
{
|
{
|
||||||
|
if(usedIds.contains(t.getPossibleValueId()))
|
||||||
|
{
|
||||||
|
duplicatedIds.add(t.getPossibleValueId());
|
||||||
|
}
|
||||||
|
|
||||||
addEnumValue(new QPossibleValue<>(t.getPossibleValueId(), t.getPossibleValueLabel()));
|
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);
|
return (this);
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
** <code>
|
||||||
|
@QMetaDataProducingEntity(
|
||||||
|
childTables = { @ChildTable(
|
||||||
|
childTableEntityClass = LineItem.class,
|
||||||
|
childJoin = @ChildJoin(enabled = true),
|
||||||
|
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class Order extends QRecordEntity
|
||||||
|
** </code>
|
||||||
|
**
|
||||||
|
** 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<QJoinMetaData>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
** <code>
|
||||||
|
@QMetaDataProducingEntity( childTables = { @ChildTable(
|
||||||
|
childTableEntityClass = LineItem.class,
|
||||||
|
childJoin = @ChildJoin(enabled = true),
|
||||||
|
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||||
|
})
|
||||||
|
public class Order extends QRecordEntity
|
||||||
|
** </code>
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implements MetaDataProducerInterface<QWidgetMetaData>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<T extends PossibleValueEnum<T>> implements MetaDataProducerInterface<QPossibleValueSource>
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final PossibleValueEnum<T>[] values;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public PossibleValueSourceOfEnumGenericMetaDataProducer(String name, PossibleValueEnum<T>[] values)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QPossibleValueSource produce(QInstance qInstance)
|
||||||
|
{
|
||||||
|
return (QPossibleValueSource.newForEnum(name, values));
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<QPossibleValueSource>
|
||||||
|
{
|
||||||
|
private final String tableName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public PossibleValueSourceOfTableGenericMetaDataProducer(String tableName)
|
||||||
|
{
|
||||||
|
this.tableName = tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QPossibleValueSource produce(QInstance qInstance)
|
||||||
|
{
|
||||||
|
return (QPossibleValueSource.newForTable(tableName));
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 "";
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<? extends QRecordEntity> childTableEntityClass();
|
||||||
|
|
||||||
|
String joinFieldName() default "";
|
||||||
|
|
||||||
|
ChildJoin childJoin() default @ChildJoin(enabled = false);
|
||||||
|
|
||||||
|
ChildRecordListWidget childRecordListWidget() default @ChildRecordListWidget(enabled = false);
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 { };
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
@ -1329,6 +1329,21 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for an association by name
|
||||||
|
*******************************************************************************/
|
||||||
|
public Optional<Association> getAssociationByName(String name)
|
||||||
|
{
|
||||||
|
if(associations == null)
|
||||||
|
{
|
||||||
|
return (Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (getAssociations().stream().filter(a -> a.getName().equals(name)).findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for associations
|
** Setter for associations
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -700,4 +700,16 @@ public class CollectionUtils
|
|||||||
return (map.containsKey(key) && map.get(key) != null);
|
return (map.containsKey(key) && map.get(key) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** add an element to a collection, but, only if the element isn't null
|
||||||
|
***************************************************************************/
|
||||||
|
public static <T, E extends T> void addIfNotNull(Collection<T> c, E element)
|
||||||
|
{
|
||||||
|
if(element != null)
|
||||||
|
{
|
||||||
|
c.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,4 +460,19 @@ public class StringUtils
|
|||||||
return (Pattern.matches("[a-f0-9]{8}(?:-[a-f0-9]{4}){4}[a-f0-9]{8}", s));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2054,16 +2054,41 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
|
|
||||||
assertValidationFailureReasons((qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withAssociation(new Association().withName("myAssociation"))),
|
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 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))),
|
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"))),
|
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();
|
int noOfReasons = actualReasons == null ? 0 : actualReasons.size();
|
||||||
assertEquals(expectedReasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", expectedReasons)
|
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)
|
for(String reason : expectedReasons)
|
||||||
@ -2451,6 +2476,7 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {}
|
public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
@ -2468,6 +2494,7 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
**
|
**
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
@ -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.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.TestAbstractMetaDataProducer;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.TestDisabledMetaDataProducer;
|
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.TestImplementsMetaDataProducer;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducer;
|
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.TestNoInterfacesExtendsObject;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoValidConstructorMetaDataProducer;
|
import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoValidConstructorMetaDataProducer;
|
||||||
import org.junit.jupiter.api.Test;
|
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.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +66,48 @@ class MetaDataProducerHelperTest
|
|||||||
assertFalse(qInstance.getTables().containsKey(TestNoInterfacesExtendsObject.NAME));
|
assertFalse(qInstance.getTables().containsKey(TestNoInterfacesExtendsObject.NAME));
|
||||||
assertFalse(qInstance.getTables().containsKey(TestAbstractMetaDataProducer.NAME));
|
assertFalse(qInstance.getTables().containsKey(TestAbstractMetaDataProducer.NAME));
|
||||||
assertFalse(qInstance.getTables().containsKey(TestDisabledMetaDataProducer.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"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Integer>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<QTableMetaData>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<QTableMetaData>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Integer>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -26,10 +26,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
@ -618,4 +620,23 @@ class CollectionUtilsTest extends BaseTest
|
|||||||
4, Map.of("B", "B4")), output);
|
4, Map.of("B", "B4")), output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAddIfNotNull()
|
||||||
|
{
|
||||||
|
HashSet<String> 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -318,4 +318,19 @@ class StringUtilsTest extends BaseTest
|
|||||||
assertEquals("Apples were eaten", StringUtils.pluralFormat(2, "Apple{,s} {was,were} eaten"));
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user