diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java
index c64c8954..de94474e 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java
@@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** a default implementation of MetaDataFilterInterface, that allows all the things
*******************************************************************************/
+@Deprecated(since = "migrated to metaDataCustomizer")
public class AllowAllMetaDataFilter implements MetaDataFilterInterface
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java
new file mode 100644
index 00000000..f7d88faf
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java
@@ -0,0 +1,92 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.actions.metadata;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+
+
+/*******************************************************************************
+ ** a default implementation of MetaDataFilterInterface, that is all noop.
+ *******************************************************************************/
+public class DefaultNoopMetaDataActionCustomizer implements MetaDataActionCustomizerInterface
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowTable(MetaDataInput input, QTableMetaData table)
+ {
+ return (true);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
+ {
+ return (true);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowReport(MetaDataInput input, QReportMetaData report)
+ {
+ return (true);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowApp(MetaDataInput input, QAppMetaData app)
+ {
+ return (true);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
+ {
+ return (true);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
index 56c76928..8ecf4dcf 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java
@@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
@@ -34,6 +36,7 @@ import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
+import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
@@ -65,7 +68,7 @@ public class MetaDataAction
{
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
- private static Memoization metaDataFilterMemoization = new Memoization<>();
+ private static Memoization metaDataActionCustomizerMemoization = new Memoization<>();
@@ -79,7 +82,7 @@ public class MetaDataAction
MetaDataOutput metaDataOutput = new MetaDataOutput();
Map treeNodes = new LinkedHashMap<>();
- MetaDataFilterInterface filter = getMetaDataFilter();
+ MetaDataActionCustomizerInterface customizer = getMetaDataActionCustomizer();
/////////////////////////////////////
// map tables to frontend metadata //
@@ -90,7 +93,7 @@ public class MetaDataAction
String tableName = entry.getKey();
QTableMetaData table = entry.getValue();
- if(!filter.allowTable(metaDataInput, table))
+ if(!customizer.allowTable(metaDataInput, table))
{
continue;
}
@@ -119,7 +122,7 @@ public class MetaDataAction
String processName = entry.getKey();
QProcessMetaData process = entry.getValue();
- if(!filter.allowProcess(metaDataInput, process))
+ if(!customizer.allowProcess(metaDataInput, process))
{
continue;
}
@@ -144,7 +147,7 @@ public class MetaDataAction
String reportName = entry.getKey();
QReportMetaData report = entry.getValue();
- if(!filter.allowReport(metaDataInput, report))
+ if(!customizer.allowReport(metaDataInput, report))
{
continue;
}
@@ -169,7 +172,7 @@ public class MetaDataAction
String widgetName = entry.getKey();
QWidgetMetaDataInterface widget = entry.getValue();
- if(!filter.allowWidget(metaDataInput, widget))
+ if(!customizer.allowWidget(metaDataInput, widget))
{
continue;
}
@@ -206,7 +209,7 @@ public class MetaDataAction
continue;
}
- if(!filter.allowApp(metaDataInput, app))
+ if(!customizer.allowApp(metaDataInput, app))
{
continue;
}
@@ -292,11 +295,22 @@ public class MetaDataAction
metaDataOutput.setBranding(QContext.getQInstance().getBranding());
}
- metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues());
+ metaDataOutput.setEnvironmentValues(Objects.requireNonNullElse(QContext.getQInstance().getEnvironmentValues(), Collections.emptyMap()));
- metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent());
+ metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
- // todo post-customization - can do whatever w/ the result if you want?
+ try
+ {
+ customizer.postProcess(metaDataOutput);
+ }
+ catch(QUserFacingException e)
+ {
+ LOG.debug("User-facing exception thrown in meta-data customizer post-processing", e);
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Unexpected error thrown in meta-data customizer post-processing", e);
+ }
return metaDataOutput;
}
@@ -306,26 +320,36 @@ public class MetaDataAction
/***************************************************************************
**
***************************************************************************/
- private MetaDataFilterInterface getMetaDataFilter()
+ private MetaDataActionCustomizerInterface getMetaDataActionCustomizer()
{
- return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
+ return metaDataActionCustomizerMemoization.getResult(QContext.getQInstance(), i ->
{
- MetaDataFilterInterface filter = null;
- QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
- if(metaDataFilterReference != null)
+ MetaDataActionCustomizerInterface actionCustomizer = null;
+ QCodeReference metaDataActionCustomizerReference = QContext.getQInstance().getMetaDataActionCustomizer();
+ if(metaDataActionCustomizerReference != null)
{
- filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
- LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
+ actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
+ LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
}
- if(filter == null)
+ if(actionCustomizer == null)
{
- filter = new AllowAllMetaDataFilter();
- LOG.debug("Using new default (allow-all) meta-data filter");
+ QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
+ if(metaDataFilterReference != null)
+ {
+ actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
+ LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
+ }
}
- return (filter);
- }).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
+ if(actionCustomizer == null)
+ {
+ actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
+ LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
+ }
+
+ return (actionCustomizer);
+ }).orElseThrow(() -> new QRuntimeException("Error getting MetaDataActionCustomizer"));
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java
new file mode 100644
index 00000000..fb2c108b
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java
@@ -0,0 +1,78 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.actions.metadata;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
+import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+
+
+/*******************************************************************************
+ ** Interface for customizations that can be injected by an application into
+ ** the MetaDataAction - e.g., loading applicable meta-data for a user into a
+ ** frontend.
+ *******************************************************************************/
+public interface MetaDataActionCustomizerInterface
+{
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ boolean allowTable(MetaDataInput input, QTableMetaData table);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ boolean allowProcess(MetaDataInput input, QProcessMetaData process);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ boolean allowReport(MetaDataInput input, QReportMetaData report);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ boolean allowApp(MetaDataInput input, QAppMetaData app);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ default void postProcess(MetaDataOutput metaDataOutput) throws QException
+ {
+ /////////////////////
+ // noop by default //
+ /////////////////////
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java
index a7abb74d..a543764d 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java
@@ -22,43 +22,11 @@
package com.kingsrook.qqq.backend.core.actions.metadata;
-import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
-import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
-import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
-
/*******************************************************************************
**
*******************************************************************************/
-public interface MetaDataFilterInterface
+@Deprecated(since = "migrated to metaDataCustomizer")
+public interface MetaDataFilterInterface extends MetaDataActionCustomizerInterface
{
- /***************************************************************************
- **
- ***************************************************************************/
- boolean allowTable(MetaDataInput input, QTableMetaData table);
-
- /***************************************************************************
- **
- ***************************************************************************/
- boolean allowProcess(MetaDataInput input, QProcessMetaData process);
-
- /***************************************************************************
- **
- ***************************************************************************/
- boolean allowReport(MetaDataInput input, QReportMetaData report);
-
- /***************************************************************************
- **
- ***************************************************************************/
- boolean allowApp(MetaDataInput input, QAppMetaData app);
-
- /***************************************************************************
- **
- ***************************************************************************/
- boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
-
}
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 b123858e..c73f9b8e 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
@@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
+import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataActionCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataFilterInterface;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
@@ -245,6 +246,11 @@ public class QInstanceValidator
{
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
}
+
+ if(qInstance.getMetaDataActionCustomizer() != null)
+ {
+ validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
index def809b9..8e4ce83f 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
@@ -116,8 +116,11 @@ public class QInstance
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
+ @Deprecated(since = "migrated to metaDataCustomizer")
private QCodeReference metaDataFilter = null;
+ private QCodeReference metaDataActionCustomizer = null;
+
//////////////////////////////////////////////////////////////////////////////////////
// todo - lock down the object (no more changes allowed) after it's been validated? //
// if doing so, may need to copy all of the collections into read-only versions... //
@@ -1495,6 +1498,7 @@ public class QInstance
/*******************************************************************************
** Getter for metaDataFilter
*******************************************************************************/
+ @Deprecated(since = "migrated to metaDataCustomizer")
public QCodeReference getMetaDataFilter()
{
return (this.metaDataFilter);
@@ -1505,6 +1509,7 @@ public class QInstance
/*******************************************************************************
** Setter for metaDataFilter
*******************************************************************************/
+ @Deprecated(since = "migrated to metaDataCustomizer")
public void setMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
@@ -1515,6 +1520,7 @@ public class QInstance
/*******************************************************************************
** Fluent setter for metaDataFilter
*******************************************************************************/
+ @Deprecated(since = "migrated to metaDataCustomizer")
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
@@ -1586,4 +1592,35 @@ public class QInstance
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for metaDataActionCustomizer
+ *******************************************************************************/
+ public QCodeReference getMetaDataActionCustomizer()
+ {
+ return (this.metaDataActionCustomizer);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for metaDataActionCustomizer
+ *******************************************************************************/
+ public void setMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
+ {
+ this.metaDataActionCustomizer = metaDataActionCustomizer;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for metaDataActionCustomizer
+ *******************************************************************************/
+ public QInstance withMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
+ {
+ this.metaDataActionCustomizer = metaDataActionCustomizer;
+ return (this);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java
new file mode 100644
index 00000000..299955c6
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java
@@ -0,0 +1,269 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.branding;
+
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/*******************************************************************************
+ ** element of BrandingMetaData - content to send to a frontend for showing a
+ ** user across the whole UI - e.g., what environment you're in, or a message
+ ** about your account - site announcements, etc.
+ *******************************************************************************/
+public class Banner implements Serializable, Cloneable
+{
+ private Severity severity;
+ private String textColor;
+ private String backgroundColor;
+ private String messageText;
+ private String messageHTML;
+
+ private Map additionalStyles;
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public enum Severity
+ {
+ INFO, WARNING, ERROR, SUCCESS
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public Banner clone()
+ {
+ try
+ {
+ Banner clone = (Banner) super.clone();
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // copy mutable state here, so the clone can't change the internals of the original //
+ //////////////////////////////////////////////////////////////////////////////////////
+ if(additionalStyles != null)
+ {
+ clone.setAdditionalStyles(new LinkedHashMap<>(additionalStyles));
+ }
+
+ return clone;
+ }
+ catch(CloneNotSupportedException e)
+ {
+ throw new AssertionError();
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for textColor
+ *******************************************************************************/
+ public String getTextColor()
+ {
+ return (this.textColor);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for textColor
+ *******************************************************************************/
+ public void setTextColor(String textColor)
+ {
+ this.textColor = textColor;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for textColor
+ *******************************************************************************/
+ public Banner withTextColor(String textColor)
+ {
+ this.textColor = textColor;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for backgroundColor
+ *******************************************************************************/
+ public String getBackgroundColor()
+ {
+ return (this.backgroundColor);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for backgroundColor
+ *******************************************************************************/
+ public void setBackgroundColor(String backgroundColor)
+ {
+ this.backgroundColor = backgroundColor;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for backgroundColor
+ *******************************************************************************/
+ public Banner withBackgroundColor(String backgroundColor)
+ {
+ this.backgroundColor = backgroundColor;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for additionalStyles
+ *******************************************************************************/
+ public Map getAdditionalStyles()
+ {
+ return (this.additionalStyles);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for additionalStyles
+ *******************************************************************************/
+ public void setAdditionalStyles(Map additionalStyles)
+ {
+ this.additionalStyles = additionalStyles;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for additionalStyles
+ *******************************************************************************/
+ public Banner withAdditionalStyles(Map additionalStyles)
+ {
+ this.additionalStyles = additionalStyles;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for messageText
+ *******************************************************************************/
+ public String getMessageText()
+ {
+ return (this.messageText);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for messageText
+ *******************************************************************************/
+ public void setMessageText(String messageText)
+ {
+ this.messageText = messageText;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for messageText
+ *******************************************************************************/
+ public Banner withMessageText(String messageText)
+ {
+ this.messageText = messageText;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for messageHTML
+ *******************************************************************************/
+ public String getMessageHTML()
+ {
+ return (this.messageHTML);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for messageHTML
+ *******************************************************************************/
+ public void setMessageHTML(String messageHTML)
+ {
+ this.messageHTML = messageHTML;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for messageHTML
+ *******************************************************************************/
+ public Banner withMessageHTML(String messageHTML)
+ {
+ this.messageHTML = messageHTML;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for severity
+ *******************************************************************************/
+ public Severity getSeverity()
+ {
+ return (this.severity);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for severity
+ *******************************************************************************/
+ public void setSeverity(Severity severity)
+ {
+ this.severity = severity;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for severity
+ *******************************************************************************/
+ public Banner withSeverity(Severity severity)
+ {
+ this.severity = severity;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java
new file mode 100644
index 00000000..162f6758
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java
@@ -0,0 +1,31 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.branding;
+
+
+/*******************************************************************************
+ ** interface to define keys for where banners should be displayed.
+ ** expect frontends to implement this interface with enums of known possible values
+ *******************************************************************************/
+public interface BannerSlot
+{
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java
index 6eb01b58..d354a8e3 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java
@@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.metadata.branding;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
@@ -30,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
** Meta-Data to define branding in a QQQ instance.
**
*******************************************************************************/
-public class QBrandingMetaData implements TopLevelMetaDataInterface
+public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable, Serializable
{
private String companyName;
private String companyUrl;
@@ -39,9 +42,45 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
private String icon;
private String accentColor;
+ @Deprecated(since = "migrate to use banners map instead")
private String environmentBannerText;
+
+ @Deprecated(since = "migrate to use banners map instead")
private String environmentBannerColor;
+ private Map banners;
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public QBrandingMetaData clone()
+ {
+ try
+ {
+ QBrandingMetaData clone = (QBrandingMetaData) super.clone();
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // copy mutable state here, so the clone can't change the internals of the original //
+ //////////////////////////////////////////////////////////////////////////////////////
+ if(banners != null)
+ {
+ clone.banners = new LinkedHashMap<>();
+ for(Map.Entry entry : this.banners.entrySet())
+ {
+ clone.banners.put(entry.getKey(), entry.getValue().clone());
+ }
+ }
+
+ return clone;
+ }
+ catch(CloneNotSupportedException e)
+ {
+ throw new AssertionError();
+ }
+ }
+
/*******************************************************************************
@@ -267,6 +306,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Getter for environmentBannerText
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public String getEnvironmentBannerText()
{
return (this.environmentBannerText);
@@ -277,6 +317,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Setter for environmentBannerText
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public void setEnvironmentBannerText(String environmentBannerText)
{
this.environmentBannerText = environmentBannerText;
@@ -287,6 +328,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for environmentBannerText
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public QBrandingMetaData withEnvironmentBannerText(String environmentBannerText)
{
this.environmentBannerText = environmentBannerText;
@@ -298,6 +340,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Getter for environmentBannerColor
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public String getEnvironmentBannerColor()
{
return (this.environmentBannerColor);
@@ -308,6 +351,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Setter for environmentBannerColor
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public void setEnvironmentBannerColor(String environmentBannerColor)
{
this.environmentBannerColor = environmentBannerColor;
@@ -318,6 +362,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for environmentBannerColor
*******************************************************************************/
+ @Deprecated(since = "migrate to use banners map instead")
public QBrandingMetaData withEnvironmentBannerColor(String environmentBannerColor)
{
this.environmentBannerColor = environmentBannerColor;
@@ -334,4 +379,52 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
{
qInstance.setBranding(this);
}
+
+
+ /*******************************************************************************
+ ** Getter for banners
+ *******************************************************************************/
+ public Map getBanners()
+ {
+ return (this.banners);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for banners
+ *******************************************************************************/
+ public void setBanners(Map banners)
+ {
+ this.banners = banners;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for banners
+ *******************************************************************************/
+ public QBrandingMetaData withBanners(Map banners)
+ {
+ this.banners = banners;
+ return (this);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public QBrandingMetaData withBanner(BannerSlot slot, Banner banner)
+ {
+ if(this.banners == null)
+ {
+ this.banners = new LinkedHashMap<>();
+ }
+ this.banners.put(slot, banner);
+
+ return (this);
+ }
+
+
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
index 58d85069..4d75312d 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java
@@ -364,6 +364,7 @@ class MetaDataActionTest extends BaseTest
**
*******************************************************************************/
@Test
+ @Deprecated(since = "migrated to metaDataCustomizer")
void testFilter() throws QException
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
@@ -397,7 +398,7 @@ class MetaDataActionTest extends BaseTest
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new MetaDataAction().execute(new MetaDataInput());
- assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("filter of type: DenyAllFilter")).hasSize(1);
+ assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("actionCustomizer (via metaDataFilter reference) of type: DenyAllFilter")).hasSize(1);
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
@@ -413,6 +414,59 @@ class MetaDataActionTest extends BaseTest
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testCustomizer() throws QException
+ {
+ QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
+
+ //////////////////////////////////////////////////////
+ // run default version, and assert tables are found //
+ //////////////////////////////////////////////////////
+ MetaDataOutput result = new MetaDataAction().execute(new MetaDataInput());
+ assertFalse(result.getTables().isEmpty(), "should be some tables");
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ new MetaDataAction().execute(new MetaDataInput());
+ assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("Using new default")).hasSize(1);
+
+ /////////////////////////////////////////////////////////////
+ // set up new instance to use a custom filter, to deny all //
+ /////////////////////////////////////////////////////////////
+ QInstance instance = TestUtils.defineInstance();
+ instance.setMetaDataActionCustomizer(new QCodeReference(DenyAllFilteringCustomizer.class));
+ reInitInstanceInContext(instance);
+
+ /////////////////////////////////////////////////////
+ // re-run, and assert all tables are filtered away //
+ /////////////////////////////////////////////////////
+ result = new MetaDataAction().execute(new MetaDataInput());
+ assertTrue(result.getTables().isEmpty(), "should be no tables");
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ new MetaDataAction().execute(new MetaDataInput());
+ assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("meta-data actionCustomizer of type: DenyAllFilteringCustomizer")).hasSize(1);
+
+ QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
+
+ /////////////////////////////////////////////////////////////////////////////////
+ // run now with the DefaultNoopMetaDataActionCustomizer, confirm we get tables //
+ /////////////////////////////////////////////////////////////////////////////////
+ instance = TestUtils.defineInstance();
+ instance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class));
+ reInitInstanceInContext(instance);
+ result = new MetaDataAction().execute(new MetaDataInput());
+ assertFalse(result.getTables().isEmpty(), "should be some tables");
+ }
+
+
+
/***************************************************************************
**
***************************************************************************/
@@ -462,6 +516,67 @@ class MetaDataActionTest extends BaseTest
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
+ {
+ return false;
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ public static class DenyAllFilteringCustomizer implements MetaDataActionCustomizerInterface
+ {
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowTable(MetaDataInput input, QTableMetaData table)
+ {
+ return false;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
+ {
+ return false;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowReport(MetaDataInput input, QReportMetaData report)
+ {
+ return false;
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean allowApp(MetaDataInput input, QAppMetaData app)
+ {
+ return false;
+ }
+
+
+
/***************************************************************************
**
***************************************************************************/
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
index 8419b05a..d790ca59 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java
@@ -40,6 +40,7 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarCh
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ParentWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.metadata.AllowAllMetaDataFilter;
+import com.kingsrook.qqq.backend.core.actions.metadata.DefaultNoopMetaDataActionCustomizer;
import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessActionTest;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
@@ -160,6 +161,20 @@ public class QInstanceValidatorTest extends BaseTest
}
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testMetaDataActionCustomizer()
+ {
+ assertValidationFailureReasons((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(QInstanceValidator.class)),
+ "Instance metaDataActionCustomizer CodeReference is not of the expected type");
+
+ assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class)));
+ assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(null));
+ }
+
+
/*******************************************************************************
** Test an instance with null backends - should throw.