From e25ec6173185a709e25171d7f7592fd40cf2508b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Sat, 8 Feb 2025 20:54:38 -0600 Subject: [PATCH] Add optional additional validation to widget meta datas; implemented at least in part for ChildRecordListWidget --- .../widgets/ChildRecordListRenderer.java | 75 ++++++++++++++++++- .../core/instances/QInstanceValidator.java | 5 ++ .../metadata/dashboard/QWidgetMetaData.java | 33 ++++++++ .../dashboard/QWidgetMetaDataInterface.java | 9 +++ .../annotations/ChildRecordListWidget.java | 2 +- 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java index 1cc01320..a73fcb09 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java @@ -37,6 +37,8 @@ import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; +import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; +import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; @@ -51,12 +53,15 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChildRecordListData; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.AbstractWidgetMetaDataBuilder; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; @@ -83,7 +88,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer .withIsCard(true) .withCodeReference(new QCodeReference(ChildRecordListRenderer.class)) .withType(WidgetType.CHILD_RECORD_LIST.getType()) - .withDefaultValue("joinName", join.getName()))); + .withDefaultValue("joinName", join.getName()) + .withValidatorPlugin(new ChildRecordListWidgetValidator()) + )); } @@ -165,10 +172,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer *******************************************************************************/ public Builder withManageAssociationName(String manageAssociationName) { - // todo - validate association name exists (on the table?) widgetMetaData.withDefaultValue("manageAssociationName", manageAssociationName); return (this); } + } @@ -321,4 +328,68 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer } } + + + /*************************************************************************** + ** + ***************************************************************************/ + private static class ChildRecordListWidgetValidator implements QInstanceValidatorPluginInterface + { + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void validate(QWidgetMetaDataInterface widgetMetaData, QInstance qInstance, QInstanceValidator qInstanceValidator) + { + String prefix = "Widget " + widgetMetaData.getName() + ": "; + + ////////////////////////////////// + // make sure join name is given // + ////////////////////////////////// + String joinName = ValueUtils.getValueAsString(CollectionUtils.nonNullMap(widgetMetaData.getDefaultValues()).get("joinName")); + if(qInstanceValidator.assertCondition(StringUtils.hasContent(joinName), prefix + "defaultValue for joinName must be given")) + { + /////////////////////////// + // make sure join exists // + /////////////////////////// + QJoinMetaData join = qInstance.getJoin(joinName); + if(qInstanceValidator.assertCondition(join != null, prefix + "No join named " + joinName + " exists in the instance")) + { + ////////////////////////////////////////////////////////////////////////////////// + // if there's a manageAssociationName, make sure the table has that association // + ////////////////////////////////////////////////////////////////////////////////// + String manageAssociationName = ValueUtils.getValueAsString(widgetMetaData.getDefaultValues().get("manageAssociationName")); + if(StringUtils.hasContent(manageAssociationName)) + { + validateAssociationName(prefix, manageAssociationName, join, qInstance, qInstanceValidator); + } + } + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private void validateAssociationName(String prefix, String manageAssociationName, QJoinMetaData join, QInstance qInstance, QInstanceValidator qInstanceValidator) + { + /////////////////////////////////// + // make sure join's table exists // + /////////////////////////////////// + QTableMetaData table = qInstance.getTable(join.getLeftTable()); + if(table == null) + { + qInstanceValidator.getErrors().add(prefix + "Unable to validate manageAssociationName, as table [" + join.getLeftTable() + "] on left-side table of join [" + join.getName() + "] does not exist."); + } + else + { + if(CollectionUtils.nonNullList(table.getAssociations()).stream().noneMatch(a -> manageAssociationName.equals(a.getName()))) + { + qInstanceValidator.getErrors().add(prefix + "an association named [" + manageAssociationName + "] does not exist on table [" + join.getLeftTable() + "]"); + } + } + } + } } 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 42565539..dcc06391 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 @@ -2008,6 +2008,11 @@ public class QInstanceValidator } } + if(widget.getValidatorPlugin() != null) + { + widget.getValidatorPlugin().validate(widget, qInstance, this); + } + runPlugins(QWidgetMetaDataInterface.class, widget, qInstance); } ); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java index e7793b03..d5c63d04 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager; +import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole; @@ -69,6 +70,7 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface protected Map defaultValues = new LinkedHashMap<>(); + protected QInstanceValidatorPluginInterface validatorPlugin; /******************************************************************************* @@ -764,4 +766,35 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot); } + + /******************************************************************************* + ** Getter for validatorPlugin + *******************************************************************************/ + public QInstanceValidatorPluginInterface getValidatorPlugin() + { + return (this.validatorPlugin); + } + + + + /******************************************************************************* + ** Setter for validatorPlugin + *******************************************************************************/ + public void setValidatorPlugin(QInstanceValidatorPluginInterface validatorPlugin) + { + this.validatorPlugin = validatorPlugin; + } + + + + /******************************************************************************* + ** Fluent setter for validatorPlugin + *******************************************************************************/ + public QWidgetMetaData withValidatorPlugin(QInstanceValidatorPluginInterface validatorPlugin) + { + this.validatorPlugin = validatorPlugin; + return (this); + } + + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java index ed8af4e3..e9c9b54a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.Set; +import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; @@ -277,5 +278,13 @@ public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules, T qInstance.addWidget(this); } + + /*************************************************************************** + ** let the widget include an instance validator plugin + ***************************************************************************/ + default QInstanceValidatorPluginInterface getValidatorPlugin() + { + return (null); + } } 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 index f0952335..064939f2 100644 --- 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 @@ -37,7 +37,7 @@ public @interface ChildRecordListWidget { boolean enabled(); - String label() default ""; + String label(); int maxRows() default 20;