Compare commits

..

4 Commits

145 changed files with 871 additions and 6495 deletions

View File

@ -48,7 +48,7 @@
</modules>
<properties>
<revision>0.25.0-SNAPSHOT</revision>
<revision>0.24.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View File

@ -83,7 +83,7 @@ public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
}
QRecord scriptRevision = queryOutput.getRecords().get(0);
LOG.debug("Running script against records", logPair("scriptRevisionId", scriptRevision.getValue("id")), logPair("scriptId", scriptRevision.getValue("scriptIdd")));
LOG.info("Running script against records", logPair("scriptRevisionId", scriptRevision.getValue("id")), logPair("scriptId", scriptRevision.getValue("scriptIdd")));
RunAdHocRecordScriptInput input = new RunAdHocRecordScriptInput();
input.setCodeReference(new AdHocScriptCodeReference().withScriptRevisionRecord(scriptRevision));

View File

@ -649,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
input.setRecordList(records);
input.setAction(action);
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
recordAutomationHandler.execute(input);
}
}

View File

@ -24,11 +24,17 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
import java.lang.reflect.Constructor;
import java.util.Optional;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -37,7 +43,9 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Utility to load code for running QQQ customizers.
**
** That memoization causes 1,000,000 such calls to go from ~500ms to ~100ms.
** TODO - redo all to go through method that memoizes class & constructor
** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms
** to ~100ms.
*******************************************************************************/
public class QCodeLoader
{
@ -62,6 +70,84 @@ public class QCodeLoader
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
{
if(codeReference == null)
{
return (null);
}
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
{
///////////////////////////////////////////////////////////////////////////////////////
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
///////////////////////////////////////////////////////////////////////////////////////
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
}
try
{
Class<?> customizerClass = Class.forName(codeReference.getName());
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
}
catch(Exception e)
{
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
// if it finds an invalid code reference //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
return (null);
}
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static <T extends BackendStep> T getBackendStep(Class<T> expectedType, QCodeReference codeReference)
{
if(codeReference == null)
{
return (null);
}
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
{
///////////////////////////////////////////////////////////////////////////////////////
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
///////////////////////////////////////////////////////////////////////////////////////
throw (new IllegalArgumentException("Only JAVA BackendSteps are supported at this time."));
}
try
{
Class<?> customizerClass = Class.forName(codeReference.getName());
return ((T) customizerClass.getConstructor().newInstance());
}
catch(Exception e)
{
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
// if it finds an invalid code reference //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
return (null);
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -91,17 +177,7 @@ public class QCodeLoader
if(constructor.isPresent())
{
T t = (T) constructor.get().newInstance();
////////////////////////////////////////////////////////////////
// if the object is initializable, then, well, initialize it! //
////////////////////////////////////////////////////////////////
if(t instanceof InitializableViaCodeReference initializableViaCodeReference)
{
initializableViaCodeReference.initialize(codeReference);
}
return t;
return ((T) constructor.get().newInstance());
}
else
{
@ -111,7 +187,7 @@ public class QCodeLoader
}
catch(Exception e)
{
LOG.error("Error initializing codeReference", e, logPair("codeReference", codeReference));
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
@ -122,4 +198,67 @@ public class QCodeLoader
}
}
/*******************************************************************************
**
*******************************************************************************/
public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException
{
try
{
QCodeReference codeReference = action.getCodeReference();
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
{
///////////////////////////////////////////////////////////////////////////////////////
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
///////////////////////////////////////////////////////////////////////////////////////
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
}
Class<?> codeClass = Class.forName(codeReference.getName());
Object codeObject = codeClass.getConstructor().newInstance();
if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler))
{
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler"));
}
return (recordAutomationHandler);
}
catch(QException qe)
{
throw (qe);
}
catch(Exception e)
{
throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
{
try
{
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
Object codeObject = codeClass.getConstructor().newInstance();
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
{
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
}
return (customPossibleValueProvider);
}
catch(QException qe)
{
throw (qe);
}
catch(Exception e)
{
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
}
}
}

View File

@ -27,7 +27,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
@ -161,18 +160,4 @@ public interface RecordCustomizerUtilityInterface
return (oldRecordMap);
}
/***************************************************************************
**
***************************************************************************/
static <T extends Serializable> T getValueFromRecordOrOldRecord(String fieldName, QRecord record, Serializable primaryKey, Optional<Map<Serializable, QRecord>> oldRecordMap)
{
T value = (T) record.getValue(fieldName);
if(value == null && primaryKey != null && oldRecordMap.isPresent() && oldRecordMap.get().containsKey(primaryKey))
{
value = (T) oldRecordMap.get().get(primaryKey).getValue(fieldName);
}
return value;
}
}

View File

@ -290,18 +290,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
/*******************************************************************************
**
*******************************************************************************/
@Deprecated(since = "call one that doesn't take input param")
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
{
return linkRecordEdit(tableName, recordId);
}
/*******************************************************************************
**
*******************************************************************************/
public static String linkRecordEdit(String tableName, Serializable recordId) throws QException
{
String tablePath = QContext.getQInstance().getTablePath(tableName);
return (tablePath + "/" + recordId + "/edit");
@ -328,17 +317,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
/*******************************************************************************
**
*******************************************************************************/
@Deprecated(since = "call one that doesn't take input param")
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
{
return linkProcessForFilter(processName, filter);
}
/*******************************************************************************
**
*******************************************************************************/
public static String linkProcessForFilter(String processName, QQueryFilter filter) throws QException
{
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
if(process == null)
@ -358,21 +337,10 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
/*******************************************************************************
**
*******************************************************************************/
@Deprecated(since = "call one that doesn't take input param")
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
{
return linkProcessForRecord(processName, recordId);
}
/*******************************************************************************
**
*******************************************************************************/
public static String linkProcessForRecord(String processName, Serializable recordId) throws QException
{
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
String tableName = process.getTableName();

View File

@ -1,251 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.actions.dashboard.widgets;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
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.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.query.FilterUseCase;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
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.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.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;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Generic widget to display a list of records.
**
** Note, closely related to (and copied from ChildRecordListRenderer.
** opportunity to share more code with that in the future??
*******************************************************************************/
public class RecordListWidgetRenderer extends AbstractWidgetRenderer
{
private static final QLogger LOG = QLogger.getLogger(RecordListWidgetRenderer.class);
/*******************************************************************************
**
*******************************************************************************/
public static Builder widgetMetaDataBuilder(String widgetName)
{
return (new Builder(new QWidgetMetaData()
.withName(widgetName)
.withIsCard(true)
.withCodeReference(new QCodeReference(RecordListWidgetRenderer.class))
.withType(WidgetType.CHILD_RECORD_LIST.getType())
.withValidatorPlugin(new RecordListWidgetValidator())
));
}
/*******************************************************************************
**
*******************************************************************************/
public static class Builder extends AbstractWidgetMetaDataBuilder
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Builder(QWidgetMetaData widgetMetaData)
{
super(widgetMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withLabel(String label)
{
widgetMetaData.setLabel(label);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withMaxRows(Integer maxRows)
{
widgetMetaData.withDefaultValue("maxRows", maxRows);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withTableName(String tableName)
{
widgetMetaData.withDefaultValue("tableName", tableName);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withFilter(QQueryFilter filter)
{
widgetMetaData.withDefaultValue("filter", filter);
return (this);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
try
{
Integer maxRows = null;
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
{
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
}
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
{
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
}
QQueryFilter filter = ((QQueryFilter) input.getWidgetMetaData().getDefaultValues().get("filter")).clone();
filter.interpretValues(new HashMap<>(input.getQueryParams()), FilterUseCase.DEFAULT);
filter.setLimit(maxRows);
String tableName = ValueUtils.getValueAsString(input.getWidgetMetaData().getDefaultValues().get("tableName"));
QTableMetaData table = QContext.getQInstance().getTable(tableName);
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true);
queryInput.setFilter(filter);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
QValueFormatter.setBlobValuesToDownloadUrls(table, queryOutput.getRecords());
int totalRows = queryOutput.getRecords().size();
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
{
/////////////////////////////////////////////////////////////////////////////////////
// if the input said to only do some max, and the # of results we got is that max, //
// then do a count query, for displaying 1-n of <count> //
/////////////////////////////////////////////////////////////////////////////////////
CountInput countInput = new CountInput();
countInput.setTableName(tableName);
countInput.setFilter(filter);
totalRows = new CountAction().execute(countInput).getCount();
}
String tablePath = QContext.getQInstance().getTablePath(tableName);
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
ChildRecordListData widgetData = new ChildRecordListData(input.getQueryParams().get("widgetLabel"), queryOutput, table, tablePath, viewAllLink, totalRows);
return (new RenderWidgetOutput(widgetData));
}
catch(Exception e)
{
LOG.warn("Error rendering record list widget", e, logPair("widgetName", () -> input.getWidgetMetaData().getName()));
throw (e);
}
}
/***************************************************************************
**
***************************************************************************/
private static class RecordListWidgetValidator implements QInstanceValidatorPluginInterface<QWidgetMetaDataInterface>
{
/***************************************************************************
**
***************************************************************************/
@Override
public void validate(QWidgetMetaDataInterface widgetMetaData, QInstance qInstance, QInstanceValidator qInstanceValidator)
{
String prefix = "Widget " + widgetMetaData.getName() + ": ";
//////////////////////////////////////////////
// make sure table name is given and exists //
//////////////////////////////////////////////
QTableMetaData table = null;
String tableName = ValueUtils.getValueAsString(CollectionUtils.nonNullMap(widgetMetaData.getDefaultValues()).get("tableName"));
if(qInstanceValidator.assertCondition(StringUtils.hasContent(tableName), prefix + "defaultValue for tableName must be given"))
{
////////////////////////////
// make sure table exists //
////////////////////////////
table = qInstance.getTable(tableName);
qInstanceValidator.assertCondition(table != null, prefix + "No table named " + tableName + " exists in the instance");
}
////////////////////////////////////////////////////////////////////////////////////
// make sure filter is given and is valid (only check that if table is given too) //
////////////////////////////////////////////////////////////////////////////////////
QQueryFilter filter = ((QQueryFilter) widgetMetaData.getDefaultValues().get("filter"));
if(qInstanceValidator.assertCondition(filter != null, prefix + "defaultValue for filter must be given") && table != null)
{
qInstanceValidator.validateQueryFilter(qInstance, prefix, table, filter, null);
}
}
}
}

View File

@ -33,7 +33,6 @@ 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
{

View File

@ -1,92 +0,0 @@
/*
* 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.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);
}
}

View File

@ -23,12 +23,10 @@ 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;
@ -36,7 +34,6 @@ 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;
@ -68,7 +65,7 @@ public class MetaDataAction
{
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
private static Memoization<QInstance, MetaDataActionCustomizerInterface> metaDataActionCustomizerMemoization = new Memoization<>();
private static Memoization<QInstance, MetaDataFilterInterface> metaDataFilterMemoization = new Memoization<>();
@ -82,7 +79,7 @@ public class MetaDataAction
MetaDataOutput metaDataOutput = new MetaDataOutput();
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
MetaDataActionCustomizerInterface customizer = getMetaDataActionCustomizer();
MetaDataFilterInterface filter = getMetaDataFilter();
/////////////////////////////////////
// map tables to frontend metadata //
@ -93,7 +90,7 @@ public class MetaDataAction
String tableName = entry.getKey();
QTableMetaData table = entry.getValue();
if(!customizer.allowTable(metaDataInput, table))
if(!filter.allowTable(metaDataInput, table))
{
continue;
}
@ -122,7 +119,7 @@ public class MetaDataAction
String processName = entry.getKey();
QProcessMetaData process = entry.getValue();
if(!customizer.allowProcess(metaDataInput, process))
if(!filter.allowProcess(metaDataInput, process))
{
continue;
}
@ -147,7 +144,7 @@ public class MetaDataAction
String reportName = entry.getKey();
QReportMetaData report = entry.getValue();
if(!customizer.allowReport(metaDataInput, report))
if(!filter.allowReport(metaDataInput, report))
{
continue;
}
@ -172,7 +169,7 @@ public class MetaDataAction
String widgetName = entry.getKey();
QWidgetMetaDataInterface widget = entry.getValue();
if(!customizer.allowWidget(metaDataInput, widget))
if(!filter.allowWidget(metaDataInput, widget))
{
continue;
}
@ -209,7 +206,7 @@ public class MetaDataAction
continue;
}
if(!customizer.allowApp(metaDataInput, app))
if(!filter.allowApp(metaDataInput, app))
{
continue;
}
@ -295,22 +292,11 @@ public class MetaDataAction
metaDataOutput.setBranding(QContext.getQInstance().getBranding());
}
metaDataOutput.setEnvironmentValues(Objects.requireNonNullElse(QContext.getQInstance().getEnvironmentValues(), Collections.emptyMap()));
metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues());
metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent());
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);
}
// todo post-customization - can do whatever w/ the result if you want?
return metaDataOutput;
}
@ -320,36 +306,26 @@ public class MetaDataAction
/***************************************************************************
**
***************************************************************************/
private MetaDataActionCustomizerInterface getMetaDataActionCustomizer()
private MetaDataFilterInterface getMetaDataFilter()
{
return metaDataActionCustomizerMemoization.getResult(QContext.getQInstance(), i ->
return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
{
MetaDataActionCustomizerInterface actionCustomizer = null;
QCodeReference metaDataActionCustomizerReference = QContext.getQInstance().getMetaDataActionCustomizer();
if(metaDataActionCustomizerReference != null)
MetaDataFilterInterface filter = null;
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
if(metaDataFilterReference != null)
{
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
}
if(actionCustomizer == null)
if(filter == null)
{
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());
}
filter = new AllowAllMetaDataFilter();
LOG.debug("Using new default (allow-all) meta-data filter");
}
if(actionCustomizer == null)
{
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
}
return (actionCustomizer);
}).orElseThrow(() -> new QRuntimeException("Error getting MetaDataActionCustomizer"));
return (filter);
}).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
}

View File

@ -1,78 +0,0 @@
/*
* 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.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 //
/////////////////////
}
}

View File

@ -22,11 +22,43 @@
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;
/*******************************************************************************
**
*******************************************************************************/
@Deprecated(since = "migrated to metaDataCustomizer")
public interface MetaDataFilterInterface extends MetaDataActionCustomizerInterface
public interface MetaDataFilterInterface
{
/***************************************************************************
**
***************************************************************************/
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);
}

View File

@ -36,8 +36,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -48,7 +46,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -191,40 +188,21 @@ public class RunBackendStepAction
{
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
{
QTableMetaData table = QContext.getQInstance().getTable(inputMetaData.getRecordListMetaData().getTableName());
QueryInput queryInput = new QueryInput();
queryInput.setTableName(table.getName());
QueryInput queryInput = new QueryInput();
queryInput.setTableName(inputMetaData.getRecordListMetaData().getTableName());
//////////////////////////////////////////////////
// look for record ids in the input data values //
//////////////////////////////////////////////////
String recordIds = (String) runBackendStepInput.getValue("recordIds");
if(recordIds == null)
// todo - handle this being async (e.g., http)
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
// then this step can re-run, hopefully with the needed data.
QProcessCallback callback = runBackendStepInput.getCallback();
if(callback == null)
{
recordIds = (String) runBackendStepInput.getValue("recordId");
throw (new QUserFacingException("Missing input records.",
new QException("Function is missing input records, but no callback was present to request fields from a user")));
}
///////////////////////////////////////////////////////////
// if records were found, add as criteria to query input //
///////////////////////////////////////////////////////////
if(recordIds != null)
{
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordIds.split(","))));
}
else
{
// todo - handle this being async (e.g., http)
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
// then this step can re-run, hopefully with the needed data.
QProcessCallback callback = runBackendStepInput.getCallback();
if(callback == null)
{
throw (new QUserFacingException("Missing input records.",
new QException("Function is missing input records, but no callback was present to request fields from a user")));
}
queryInput.setFilter(callback.getQueryFilter());
}
queryInput.setFilter(callback.getQueryFilter());
//////////////////////////////////////////////////////////////////////////////////////////
// if process has a max-no of records, set a limit on the process of that number plus 1 //
@ -232,7 +210,7 @@ public class RunBackendStepAction
//////////////////////////////////////////////////////////////////////////////////////////
if(process.getMaxInputRecords() != null)
{
if(queryInput.getFilter() == null)
if(callback.getQueryFilter() == null)
{
queryInput.setFilter(new QQueryFilter());
}

View File

@ -108,7 +108,7 @@ public class CsvExportStreamer implements ExportStreamerInterface
}
catch(Exception e)
{
throw (new QReportingException("Error starting CSV report", e));
throw (new QReportingException("Error starting CSV report"));
}
}

View File

@ -54,14 +54,6 @@ public interface ExportStreamerInterface
// noop in base class
}
/***************************************************************************
**
***************************************************************************/
default void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
{
// noop in base class
}
/*******************************************************************************
** Called once per sheet, before any rows are available. Meant to write a
** header, for example.

View File

@ -1,35 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
/*******************************************************************************
** interface for classes that can be used to customize visual style aspects of
** exports/reports.
**
** Anticipates very different sub-interfaces based on the file type being generated,
** and the capabilities of each. e.g., excel (bolds, fonts, cell merging) vs
** json (different structure of objects).
*******************************************************************************/
public interface ExportStyleCustomizerInterface
{
}

View File

@ -163,17 +163,6 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
reportStreamer = reportFormat.newReportStreamer();
}
if(reportInput.getExportStyleCustomizer() != null)
{
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
reportStreamer.setExportStyleCustomizer(styleCustomizer);
}
else if(report.getExportStyleCustomizer() != null)
{
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, report.getExportStyleCustomizer());
reportStreamer.setExportStyleCustomizer(styleCustomizer);
}
reportStreamer.preRun(reportInput.getReportDestination(), views);
////////////////////////////////////////////////////////////////////////////////////////////////
@ -222,8 +211,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
/////////////////////////////////////////////////////////////////////////////////////////
if(dataSourceTableView.getViewCustomizer() != null)
{
@SuppressWarnings("unchecked")
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getAdHoc(Function.class, dataSourceTableView.getViewCustomizer());
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer());
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
{
reportViewCustomizer.setReportInput(reportInput);
@ -672,7 +660,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(QReportField column : CollectionUtils.nonNullList(tableView.getColumns()))
for(QReportField column : tableView.getColumns())
{
if(column.getShowPossibleValueLabel())
{

View File

@ -46,7 +46,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
@ -78,7 +77,6 @@ import org.apache.poi.xssf.usermodel.XSSFPivotTable;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -114,10 +112,9 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
public static final String EXCEL_DATE_FORMAT = "yyyy-MM-dd";
public static final String EXCEL_DATE_TIME_FORMAT = "yyyy-MM-dd H:mm:ss";
private ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface;
private PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
private Map<String, String> excelCellFormats;
private Map<String, XSSFCellStyle> styles = new HashMap<>();
private Map<String, XSSFCellStyle> styles = new HashMap<>();
private int rowNo = 0;
private int sheetIndex = 1;
@ -405,7 +402,6 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
styles.put("datetime", dateTimeStyle);
PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
@ -417,11 +413,6 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
styles.put("footer-datetime", footerDateTimeStyle);
if(styleCustomizerInterface != null)
{
styleCustomizerInterface.customizeStyles(styles, workbook, createHelper);
}
}
@ -467,7 +458,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
}
else
{
sheetWriter.beginSheet(view, styleCustomizerInterface);
sheetWriter.beginSheet();
////////////////////////////////////////////////
// put the title and header rows in the sheet //
@ -569,16 +560,6 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
/***************************************************************************
**
***************************************************************************/
public static void setStyleForField(QRecord record, String fieldName, String styleName)
{
record.setDisplayValue(fieldName + ":excelStyle", styleName);
}
/*******************************************************************************
**
*******************************************************************************/
@ -586,12 +567,12 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
{
sheetWriter.insertRow(rowNo++);
int baseStyleIndex = -1;
int styleIndex = -1;
int dateStyleIndex = styles.get("date").getIndex();
int dateTimeStyleIndex = styles.get("datetime").getIndex();
if(isFooter)
{
baseStyleIndex = styles.get("footer").getIndex();
styleIndex = styles.get("footer").getIndex();
dateStyleIndex = styles.get("footer-date").getIndex();
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
}
@ -601,13 +582,6 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
{
Serializable value = qRecord.getValue(field.getName());
String overrideStyleName = qRecord.getDisplayValue(field.getName() + ":excelStyle");
int styleIndex = baseStyleIndex;
if(overrideStyleName != null)
{
styleIndex = styles.get(overrideStyleName).getIndex();
}
if(value != null)
{
if(value instanceof String s)
@ -732,7 +706,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
{
if(!ReportType.PIVOT.equals(currentView.getType()))
{
sheetWriter.endSheet(currentView, styleCustomizerInterface);
sheetWriter.endSheet();
}
activeSheetWriter.flush();
@ -841,29 +815,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
*******************************************************************************/
protected PoiExcelStylerInterface getStylerInterface()
{
if(styleCustomizerInterface != null)
{
return styleCustomizerInterface.getExcelStyler();
}
return (new PlainPoiExcelStyler());
}
/***************************************************************************
**
***************************************************************************/
@Override
public void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
{
if(exportStyleCustomizer instanceof ExcelPoiBasedStreamingStyleCustomizerInterface poiExcelStylerInterface)
{
this.styleCustomizerInterface = poiExcelStylerInterface;
}
else
{
LOG.debug("Supplied export style customizer is not an instance of ExcelPoiStyleCustomizerInterface, so will not be used for an excel export", logPair("exportStyleCustomizerClass", exportStyleCustomizer.getClass().getSimpleName()));
}
}
}

View File

@ -1,81 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/*******************************************************************************
** style customization points for Excel files generated via our streaming POI.
*******************************************************************************/
public interface ExcelPoiBasedStreamingStyleCustomizerInterface extends ExportStyleCustomizerInterface
{
/***************************************************************************
** slightly legacy way we did excel styles - but get an instance of object
** that defaults "default" styles (header, footer, etc).
***************************************************************************/
default PoiExcelStylerInterface getExcelStyler()
{
return (new PlainPoiExcelStyler());
}
/***************************************************************************
** either change "default" styles put in the styles map, or create new ones
** which can then be applied to row/field values (cells) via:
** ExcelPoiBasedStreamingExportStreamer.setStyleForField(row, fieldName, styleName);
***************************************************************************/
default void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
{
//////////////////
// noop default //
//////////////////
}
/***************************************************************************
** for a given view (sheet), return a list of custom column widths.
** any nulls in the list are ignored (so default width is used).
***************************************************************************/
default List<Integer> getColumnWidthsForView(QReportView view)
{
return (null);
}
/***************************************************************************
** for a given view (sheet), return a list of any ranges which should be
** merged, as in "A1:C1" (first three cells in first row).
***************************************************************************/
default List<String> getMergedRangesForView(QReportView view)
{
return (null);
}
}

View File

@ -25,10 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.poi.ss.util.CellReference;
@ -56,33 +53,13 @@ public class StreamedSheetWriter
/*******************************************************************************
**
*******************************************************************************/
public void beginSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
public void beginSheet() throws IOException
{
writer.write("""
<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">""");
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>""");
if(styleCustomizerInterface != null && view != null)
{
List<Integer> columnWidths = styleCustomizerInterface.getColumnWidthsForView(view);
if(CollectionUtils.nullSafeHasContents(columnWidths))
{
writer.write("<cols>");
for(int i = 0; i < columnWidths.size(); i++)
{
Integer width = columnWidths.get(i);
if(width != null)
{
writer.write("""
<col min="%d" max="%d" width="%d" customWidth="1"/>
""".formatted(i + 1, i + 1, width));
}
}
writer.write("</cols>");
}
}
writer.write("<sheetData>");
}
@ -90,25 +67,11 @@ public class StreamedSheetWriter
/*******************************************************************************
**
*******************************************************************************/
public void endSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
public void endSheet() throws IOException
{
writer.write("</sheetData>");
if(styleCustomizerInterface != null && view != null)
{
List<String> mergedRanges = styleCustomizerInterface.getMergedRangesForView(view);
if(CollectionUtils.nullSafeHasContents(mergedRanges))
{
writer.write(String.format("<mergeCells count=\"%d\">", mergedRanges.size()));
for(String range : mergedRanges)
{
writer.write(String.format("<mergeCell ref=\"%s\"/>", range));
}
writer.write("</mergeCells>");
}
}
writer.write("</worksheet>");
writer.write("""
</sheetData>
</worksheet>""");
}
@ -188,7 +151,7 @@ public class StreamedSheetWriter
{
rs.append("&quot;");
}
else if(c < 32 && c != '\t' && c != '\n')
else if (c < 32 && c != '\t' && c != '\n')
{
rs.append(' ');
}

View File

@ -53,8 +53,7 @@ public class QJavaExecutor implements QCodeExecutor
Serializable output;
try
{
@SuppressWarnings("unchecked")
Function<Map<String, Object>, Serializable> function = QCodeLoader.getAdHoc(Function.class, codeReference);
Function<Map<String, Object>, Serializable> function = QCodeLoader.getFunction(codeReference);
output = function.apply(context);
}
catch(Exception e)

View File

@ -32,7 +32,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
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.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
@ -83,22 +82,6 @@ public class CountAction
/*******************************************************************************
** shorthand way to call for the most common use-case, when you just want the
** count to be returned, and you just want to pass in a table name and filter.
*******************************************************************************/
public static Integer execute(String tableName, QQueryFilter filter) throws QException
{
CountAction countAction = new CountAction();
CountInput countInput = new CountInput();
countInput.setTableName(tableName);
countInput.setFilter(filter);
CountOutput countOutput = countAction.execute(countInput);
return (countOutput.getCount());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -238,11 +238,6 @@ public class GetAction
*******************************************************************************/
public static QRecord execute(String tableName, Serializable primaryKey) throws QException
{
if(primaryKey instanceof QQueryFilter)
{
LOG.warn("Unexpected use of QQueryFilter instead of primary key in GetAction call");
}
GetAction getAction = new GetAction();
GetInput getInput = new GetInput(tableName).withPrimaryKey(primaryKey);
return getAction.executeForRecord(getInput);

View File

@ -114,7 +114,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
if(!StringUtils.hasContent(insertInput.getTableName()))
{
throw (new QException("Table name was not specified in insert input"));
throw (new QException("Table name was not specified in update input"));
}
QTableMetaData table = insertInput.getTable();

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
@ -75,31 +74,6 @@ public interface QCustomPossibleValueProvider<T extends Serializable>
}
/***************************************************************************
** meant to be protected (but interface...) - for a custom PVS implementation
** to complete its search (e.g., after it generates the list of PVS objects,
** let this method do the filtering).
***************************************************************************/
default List<QPossibleValue<T>> completeCustomPVSSearch(SearchPossibleValueSourceInput input, List<QPossibleValue<T>> possibleValues)
{
SearchPossibleValueSourceAction.PreparedSearchPossibleValueSourceInput preparedInput = SearchPossibleValueSourceAction.prepareSearchPossibleValueSourceInput(input);
List<QPossibleValue<T>> rs = new ArrayList<>();
for(QPossibleValue<T> possibleValue : possibleValues)
{
if(possibleValue != null && SearchPossibleValueSourceAction.doesPossibleValueMatchSearchInput(possibleValue, preparedInput))
{
rs.add(possibleValue);
}
}
rs.sort(Comparator.nullsLast(Comparator.comparing((QPossibleValue<T> pv) -> pv.getLabel())));
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -341,7 +341,7 @@ public class QPossibleValueTranslator
try
{
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
}
catch(Exception e)

View File

@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -32,6 +34,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -45,7 +48,6 @@ 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.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang3.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -486,8 +488,6 @@ public class QValueFormatter
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
String defaultExtension = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.DEFAULT_EXTENSION));
Boolean downloadUrlDynamic = ValueUtils.getValueAsBoolean(adornmentValues.get(AdornmentType.FileDownloadValues.DOWNLOAD_URL_DYNAMIC));
for(QRecord record : records)
{
if(!doesFieldHaveValue(field, record))
@ -495,11 +495,6 @@ public class QValueFormatter
continue;
}
if(BooleanUtils.isTrue(downloadUrlDynamic))
{
continue;
}
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
String fileName = null;
@ -549,7 +544,10 @@ public class QValueFormatter
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_CODE_REFERENCE)
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_PROCESS_NAME))
{
record.setValue(field.getName(), AdornmentType.FileDownloadValues.makeFieldDownloadUrl(table.getName(), primaryKey, field.getName(), fileName));
record.setValue(field.getName(), "/data/" + table.getName() + "/"
+ URLEncoder.encode(ValueUtils.getValueAsString(primaryKey), StandardCharsets.UTF_8) + "/"
+ field.getName() + "/"
+ URLEncoder.encode(Objects.requireNonNullElse(fileName, ""), StandardCharsets.UTF_8));
}
record.setDisplayValue(field.getName(), fileName);
}

View File

@ -26,7 +26,6 @@ import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -52,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -108,54 +108,60 @@ public class SearchPossibleValueSourceAction
/***************************************************************************
** record to store "computed" values as part of a possible-value search -
** e.g., ids type-convered, and lower-cased labels.
***************************************************************************/
public record PreparedSearchPossibleValueSourceInput(Collection<?> inputIdsAsCorrectType, Collection<String> lowerCaseLabels, String searchTerm) {}
/***************************************************************************
**
***************************************************************************/
public static PreparedSearchPossibleValueSourceInput prepareSearchPossibleValueSourceInput(SearchPossibleValueSourceInput input)
{
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(input.getPossibleValueSourceName());
List<?> inputIdsAsCorrectType = convertInputIdsToPossibleValueSourceIdType(possibleValueSource, input.getIdList());
Set<String> lowerCaseLabels = null;
if(input.getLabelList() != null)
{
lowerCaseLabels = input.getLabelList().stream()
.filter(Objects::nonNull)
.map(l -> l.toLowerCase())
.collect(Collectors.toSet());
}
return (new PreparedSearchPossibleValueSourceInput(inputIdsAsCorrectType, lowerCaseLabels, input.getSearchTerm()));
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput searchPossibleValueEnum(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
{
PreparedSearchPossibleValueSourceInput preparedSearchPossibleValueSourceInput = prepareSearchPossibleValueSourceInput(input);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
List<Serializable> matchingIds = new ArrayList<>();
List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList());
Set<String> labels = null;
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
{
boolean match = doesPossibleValueMatchSearchInput(possibleValue, preparedSearchPossibleValueSourceInput);
boolean match = false;
if(input.getIdList() != null)
{
if(inputIdsAsCorrectType.contains(possibleValue.getId()))
{
match = true;
}
}
else if(input.getLabelList() != null)
{
if(labels == null)
{
labels = input.getLabelList().stream().filter(Objects::nonNull).map(l -> l.toLowerCase()).collect(Collectors.toSet());
}
if(labels.contains(possibleValue.getLabel().toLowerCase()))
{
match = true;
}
}
else
{
if(StringUtils.hasContent(input.getSearchTerm()))
{
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.getSearchTerm().toLowerCase())
|| possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()));
}
else
{
match = true;
}
}
if(match)
{
matchingIds.add(possibleValue.getId());
matchingIds.add((Serializable) possibleValue.getId());
}
// todo - skip & limit?
// todo - default filter
}
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, matchingIds);
@ -166,84 +172,42 @@ public class SearchPossibleValueSourceAction
/***************************************************************************
**
***************************************************************************/
public static boolean doesPossibleValueMatchSearchInput(QPossibleValue<?> possibleValue, PreparedSearchPossibleValueSourceInput input)
{
boolean match = false;
if(input.inputIdsAsCorrectType() != null)
{
if(input.inputIdsAsCorrectType().contains(possibleValue.getId()))
{
match = true;
}
}
else if(input.lowerCaseLabels() != null)
{
if(input.lowerCaseLabels().contains(possibleValue.getLabel().toLowerCase()))
{
match = true;
}
}
else
{
if(StringUtils.hasContent(input.searchTerm()))
{
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.searchTerm().toLowerCase())
|| possibleValue.getLabel().toLowerCase().startsWith(input.searchTerm().toLowerCase()));
}
else
{
match = true;
}
}
return match;
}
/*******************************************************************************
** The input list of ids might come through as a type that isn't the same as
** the type of the ids in the enum (e.g., strings from a frontend, integers
** in an enum). So, this method type-converts them.
** in an enum). So, this method looks at the first id in the enum, and then
** maps all the inputIds to be of the same type.
*******************************************************************************/
private static List<Object> convertInputIdsToPossibleValueSourceIdType(QPossibleValueSource possibleValueSource, List<Serializable> inputIdList)
private List<Object> convertInputIdsToEnumIdType(QPossibleValueSource possibleValueSource, List<Serializable> inputIdList)
{
List<Object> rs = new ArrayList<>();
if(inputIdList == null)
{
return (null);
}
else if(inputIdList.isEmpty())
if(CollectionUtils.nullSafeIsEmpty(inputIdList))
{
return (rs);
}
QFieldType type = possibleValueSource.getIdType();
Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId();
for(Serializable inputId : inputIdList)
{
Object properlyTypedId = null;
try
{
if(type.equals(QFieldType.INTEGER))
if(anIdFromTheEnum instanceof Integer)
{
properlyTypedId = ValueUtils.getValueAsInteger(inputId);
}
else if(type.isStringLike())
else if(anIdFromTheEnum instanceof String)
{
properlyTypedId = ValueUtils.getValueAsString(inputId);
}
else if(type.equals(QFieldType.BOOLEAN))
else if(anIdFromTheEnum instanceof Boolean)
{
properlyTypedId = ValueUtils.getValueAsBoolean(inputId);
}
else
{
LOG.warn("Unexpected type [" + type + "] for ids in enum: " + possibleValueSource.getName());
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
}
}
catch(Exception e)
@ -251,7 +215,7 @@ public class SearchPossibleValueSourceAction
LOG.debug("Error converting possible value id to expected id type", e, logPair("value", inputId));
}
if(properlyTypedId != null)
if (properlyTypedId != null)
{
rs.add(properlyTypedId);
}
@ -424,7 +388,7 @@ public class SearchPossibleValueSourceAction
{
try
{
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
@ -433,9 +397,9 @@ public class SearchPossibleValueSourceAction
}
catch(Exception e)
{
String message = "Error searching custom possible value source [" + input.getPossibleValueSourceName() + "]";
String message = "Error sending searching custom possible value source [" + input.getPossibleValueSourceName() + "]";
LOG.warn(message, e);
throw (new QException(message, e));
throw (new QException(message));
}
}

View File

@ -95,7 +95,6 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.Bulk
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -1425,7 +1424,7 @@ public class QInstanceEnricher
{
try
{
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
Type returnType = getPossibleValueMethod.getGenericReturnType();
@ -1483,31 +1482,6 @@ public class QInstanceEnricher
/***************************************************************************
** scan the classpath for classes in the specified package name which
** implement the QInstanceEnricherPluginInterface - any found get added
***************************************************************************/
public static void discoverAndAddPluginsInPackage(String packageName) throws QException
{
try
{
for(Class<?> aClass : ClassPathUtils.getClassesInPackage(packageName))
{
if(QInstanceEnricherPluginInterface.class.isAssignableFrom(aClass))
{
QInstanceEnricherPluginInterface<?> plugin = (QInstanceEnricherPluginInterface<?>) aClass.getConstructor().newInstance();
addEnricherPlugin(plugin);
}
}
}
catch(Exception e)
{
throw (new QException("Error discovering and adding enricher plugins in package [" + packageName + "]", e));
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -45,7 +45,6 @@ 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;
@ -143,8 +142,6 @@ public class QInstanceValidator
private static ListingHash<Class<?>, QInstanceValidatorPluginInterface<?>> validatorPlugins = new ListingHash<>();
private JoinGraph joinGraph = null;
private List<String> errors = new ArrayList<>();
@ -172,7 +169,8 @@ public class QInstanceValidator
// the enricher will build a join graph (if there are any joins). we'd like to only do that //
// once, during the enrichment/validation work, so, capture it, and store it back in the instance. //
/////////////////////////////////////////////////////////////////////////////////////////////////////
long start = System.currentTimeMillis();
JoinGraph joinGraph = null;
long start = System.currentTimeMillis();
try
{
/////////////////////////////////////////////////////////////////////////////////////////////////
@ -181,7 +179,7 @@ public class QInstanceValidator
// TODO - possible point of customization (use a different enricher, or none, or pass it options).
QInstanceEnricher qInstanceEnricher = new QInstanceEnricher(qInstance);
qInstanceEnricher.enrich();
this.joinGraph = qInstanceEnricher.getJoinGraph();
joinGraph = qInstanceEnricher.getJoinGraph();
}
catch(Exception e)
{
@ -246,11 +244,6 @@ public class QInstanceValidator
{
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
}
if(qInstance.getMetaDataActionCustomizer() != null)
{
validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
}
}
@ -1912,7 +1905,7 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
public void validateQueryFilter(QInstance qInstance, String context, QTableMetaData table, QQueryFilter queryFilter, List<QueryJoin> queryJoins)
private void validateQueryFilter(QInstance qInstance, String context, QTableMetaData table, QQueryFilter queryFilter, List<QueryJoin> queryJoins)
{
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
{
@ -1956,8 +1949,7 @@ public class QInstanceValidator
{
if(fieldName.contains("."))
{
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
String tableNameBeforeDot = fieldName.substring(0, fieldName.lastIndexOf("."));
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
if(CollectionUtils.nullSafeHasContents(queryJoins))
{
@ -1981,32 +1973,11 @@ public class QInstanceValidator
}
else
{
if(this.joinGraph != null)
{
Set<JoinGraph.JoinConnectionList> joinConnections = joinGraph.getJoinConnections(table.getName());
for(JoinGraph.JoinConnectionList joinConnectionList : joinConnections)
{
JoinGraph.JoinConnection joinConnection = joinConnectionList.list().get(joinConnectionList.list().size() - 1);
if(tableNameBeforeDot.equals(joinConnection.joinTable()))
{
QTableMetaData joinTable = qInstance.getTable(tableNameBeforeDot);
if(joinTable.getFields().containsKey(fieldNameAfterDot))
{
/////////////////////////
// mmm, looks valid... //
/////////////////////////
return (true);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////
// todo - not sure how vulnerable we are to ongoing issues here... //
// idea: let a filter (or any object?) be opted out of validation, some version of //
// a static map of objects we can check at the top of various validate methods... //
//////////////////////////////////////////////////////////////////////////////////////
errors.add("Failed to find field named: " + fieldName);
errors.add("QInstanceValidator does not yet support finding a field that looks like a join field, but isn't associated with a query.");
return (true);
// todo! for(QJoinMetaData join : CollectionUtils.nonNullMap(qInstance.getJoins()).values())
// {
// }
}
}
}

View File

@ -40,7 +40,6 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
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.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
@ -618,14 +617,4 @@ public class RunBackendStepInput extends AbstractActionInput
}
}
}
/***************************************************************************
**
***************************************************************************/
public QProcessMetaData getProcess()
{
return (QContext.getQInstance().getProcess(getProcessName()));
}
}

View File

@ -28,7 +28,6 @@ import java.util.Map;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
@ -45,7 +44,6 @@ public class ReportInput extends AbstractTableActionInput
private ReportDestination reportDestination;
private Supplier<? extends ExportStreamerInterface> overrideExportStreamerSupplier;
private QCodeReference exportStyleCustomizer;
@ -210,35 +208,4 @@ public class ReportInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for exportStyleCustomizer
*******************************************************************************/
public QCodeReference getExportStyleCustomizer()
{
return (this.exportStyleCustomizer);
}
/*******************************************************************************
** Setter for exportStyleCustomizer
*******************************************************************************/
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
{
this.exportStyleCustomizer = exportStyleCustomizer;
}
/*******************************************************************************
** Fluent setter for exportStyleCustomizer
*******************************************************************************/
public ReportInput withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
{
this.exportStyleCustomizer = exportStyleCustomizer;
return (this);
}
}

View File

@ -583,31 +583,4 @@ public abstract class QRecordEntity
return (null);
}
/***************************************************************************
**
***************************************************************************/
public static String getTableName(Class<? extends QRecordEntity> entityClass) throws QException
{
try
{
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
String tableNameValue = (String) tableNameField.get(null);
return (tableNameValue);
}
catch(Exception e)
{
throw (new QException("Could not get TABLE_NAME from entity class: " + entityClass.getSimpleName(), e));
}
}
/***************************************************************************
** named without the 'get' to avoid conflict w/ entity fields named that...
***************************************************************************/
public String tableName() throws QException
{
return (getTableName(this.getClass()));
}
}

View File

@ -29,40 +29,5 @@ package com.kingsrook.qqq.backend.core.model.metadata;
*******************************************************************************/
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>
{
private Class<?> sourceClass;
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
@Override
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
@Override
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public MetaDataProducer<T> withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -106,10 +106,14 @@ public class MetaDataProducerHelper
}
/***************************************************************************
/*******************************************************************************
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
** run them, and add their output to the given qInstance.
**
***************************************************************************/
public static List<MetaDataProducerInterface<?>> findProducers(String packageName) throws QException
** Note - they'll be sorted by the sortOrder they provide.
*******************************************************************************/
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws QException
{
List<Class<?>> classesInPackage;
try
@ -192,20 +196,6 @@ public class MetaDataProducerHelper
}
}));
return (producers);
}
/*******************************************************************************
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
** run them, and add their output to the given qInstance.
**
** Note - they'll be sorted by the sortOrder they provide.
*******************************************************************************/
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws QException
{
List<MetaDataProducerInterface<?>> producers = findProducers(packageName);
///////////////////////////////////////////////////////////////////////////
// execute each one (if enabled), adding their meta data to the instance //
///////////////////////////////////////////////////////////////////////////
@ -239,19 +229,17 @@ public class MetaDataProducerHelper
**
***************************************************************************/
@SuppressWarnings("unchecked")
private static <T extends Serializable & PossibleValueEnum<T>> MetaDataProducerInterface<?> processMetaDataProducingPossibleValueEnum(Class<?> sourceClass)
private static <T extends Serializable & PossibleValueEnum<T>> MetaDataProducerInterface<?> processMetaDataProducingPossibleValueEnum(Class<?> aClass)
{
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingPossibleValueEnum.class.getSimpleName();
if(!PossibleValueEnum.class.isAssignableFrom(sourceClass))
if(!PossibleValueEnum.class.isAssignableFrom(aClass))
{
LOG.warn(warningPrefix + ", but which is not a " + PossibleValueEnum.class.getSimpleName() + ", so it will not be used.", logPair("class", sourceClass.getSimpleName()));
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<?>[]) sourceClass.getEnumConstants();
PossibleValueSourceOfEnumGenericMetaDataProducer<T> producer = new PossibleValueSourceOfEnumGenericMetaDataProducer<>(sourceClass.getSimpleName(), (PossibleValueEnum<T>[]) values);
producer.setSourceClass(sourceClass);
return producer;
PossibleValueEnum<?>[] values = (PossibleValueEnum<?>[]) aClass.getEnumConstants();
return (new PossibleValueSourceOfEnumGenericMetaDataProducer<T>(aClass.getSimpleName(), (PossibleValueEnum<T>[]) values));
}
@ -259,32 +247,32 @@ public class MetaDataProducerHelper
/***************************************************************************
**
***************************************************************************/
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> sourceClass) throws Exception
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> aClass) throws Exception
{
List<MetaDataProducerInterface<?>> rs = new ArrayList<>();
QMetaDataProducingEntity qMetaDataProducingEntity = sourceClass.getAnnotation(QMetaDataProducingEntity.class);
QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class);
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingEntity.class.getSimpleName();
///////////////////////////////////////////////////////////
// make sures class is QRecordEntity and cast it as such //
///////////////////////////////////////////////////////////
if(!QRecordEntity.class.isAssignableFrom(sourceClass))
if(!QRecordEntity.class.isAssignableFrom(aClass))
{
LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", sourceClass.getSimpleName()));
LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName()));
return (rs);
}
@SuppressWarnings("unchecked") // safe per the check above.
Class<? extends QRecordEntity> recordEntityClass = (Class<? extends QRecordEntity>) sourceClass;
Class<? extends QRecordEntity> recordEntityClass = (Class<? extends QRecordEntity>) aClass;
////////////////////////////////////////////////
// get TABLE_NAME static field from the class //
////////////////////////////////////////////////
Field tableNameField = recordEntityClass.getDeclaredField("TABLE_NAME");
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", recordEntityClass.getSimpleName()));
LOG.warn(warningPrefix + ", but whose TABLE_NAME field is not a String, so it will not be used.", logPair("class", aClass.getSimpleName()));
return (rs);
}
@ -305,7 +293,6 @@ public class MetaDataProducerHelper
}
RecordEntityToTableGenericMetaDataProducer producer = new RecordEntityToTableGenericMetaDataProducer(tableNameValue, recordEntityClass, tableMetaDataProductionCustomizer);
producer.setSourceClass(recordEntityClass);
if(tableMetaDataCustomizer != null)
{
@ -325,9 +312,7 @@ public class MetaDataProducerHelper
////////////////////////////////////////
if(qMetaDataProducingEntity.producePossibleValueSource())
{
PossibleValueSourceOfTableGenericMetaDataProducer producer = new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue);
producer.setSourceClass(recordEntityClass);
rs.add(producer);
rs.add(new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue));
}
//////////////////////////
@ -338,11 +323,11 @@ public class MetaDataProducerHelper
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
if(childTable.childJoin().enabled())
{
CollectionUtils.addIfNotNull(rs, processChildJoin(recordEntityClass, childTable));
CollectionUtils.addIfNotNull(rs, processChildJoin(aClass, childTable));
if(childTable.childRecordListWidget().enabled())
{
CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(recordEntityClass, childTable));
CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(aClass, childTable));
}
}
else
@ -352,7 +337,7 @@ public class MetaDataProducerHelper
//////////////////////////////////////////////////////////////////////////
// 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", recordEntityClass.getSimpleName()), logPair("childEntityClass", childEntityClass.getSimpleName()));
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()));
}
}
}
@ -365,16 +350,14 @@ public class MetaDataProducerHelper
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processChildRecordListWidget(Class<? extends QRecordEntity> sourceClass, ChildTable childTable) throws Exception
private static MetaDataProducerInterface<?> processChildRecordListWidget(Class<?> aClass, ChildTable childTable) throws Exception
{
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
String parentTableName = getTableNameStaticFieldValue(sourceClass);
String parentTableName = getTableNameStaticFieldValue(aClass);
String childTableName = getTableNameStaticFieldValue(childEntityClass);
ChildRecordListWidget childRecordListWidget = childTable.childRecordListWidget();
ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer producer = new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget);
producer.setSourceClass(sourceClass);
return producer;
return (new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget));
}
@ -404,22 +387,20 @@ public class MetaDataProducerHelper
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processChildJoin(Class<? extends QRecordEntity> entityClass, ChildTable childTable) throws Exception
private static MetaDataProducerInterface<?> processChildJoin(Class<?> aClass, ChildTable childTable) throws Exception
{
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
String parentTableName = getTableNameStaticFieldValue(entityClass);
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 [" + entityClass.getSimpleName() + "]");
LOG.warn("Could not find field in [" + childEntityClass.getSimpleName() + "] with possibleValueSource referencing table [" + aClass.getSimpleName() + "]");
return (null);
}
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy());
producer.setSourceClass(entityClass);
return producer;
return (new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName));
}
@ -427,20 +408,18 @@ public class MetaDataProducerHelper
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processMetaDataProducer(Class<?> sourceCClass) throws Exception
private static MetaDataProducerInterface<?> processMetaDataProducer(Class<?> aClass) throws Exception
{
for(Constructor<?> constructor : sourceCClass.getConstructors())
for(Constructor<?> constructor : aClass.getConstructors())
{
if(constructor.getParameterCount() == 0)
{
Object o = constructor.newInstance();
MetaDataProducerInterface<?> producer = (MetaDataProducerInterface<?>) o;
producer.setSourceClass(sourceCClass);
return producer;
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", sourceCClass.getSimpleName()));
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;
}

View File

@ -73,23 +73,4 @@ public interface MetaDataProducerInterface<T extends MetaDataProducerOutput>
return (true);
}
/***************************************************************************
*
***************************************************************************/
default void setSourceClass(Class<?> sourceClass)
{
//////////
// noop //
//////////
}
/***************************************************************************
**
***************************************************************************/
default Class<?> getSourceClass()
{
return null;
}
}

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -32,12 +31,10 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
** Output object for a MetaDataProducer, which contains multiple meta-data
** objects.
*******************************************************************************/
public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, SourceQBitAware
public class MetaDataProducerMultiOutput implements MetaDataProducerOutput
{
private List<MetaDataProducerOutput> contents;
private String sourceQBitName;
/*******************************************************************************
@ -101,48 +98,4 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
return (rs);
}
/***************************************************************************
**
***************************************************************************/
@Override
public String getSourceQBitName()
{
return (this.sourceQBitName);
}
/***************************************************************************
**
***************************************************************************/
@Override
public void setSourceQBitName(String sourceQBitName)
{
this.sourceQBitName = sourceQBitName;
/////////////////////////////////////////////
// propagate the name down to the children //
/////////////////////////////////////////////
for(MetaDataProducerOutput content : contents)
{
if(content instanceof SourceQBitAware aware)
{
aware.setSourceQBitName(sourceQBitName);
}
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public MetaDataProducerMultiOutput withSourceQBitName(String sourceQBitName)
{
setSourceQBitName(sourceQBitName);
return this;
}
}

View File

@ -56,7 +56,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRule
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
@ -90,7 +89,6 @@ public class QInstance
////////////////////////////////////////////////////////////////////////////////////////////
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
////////////////////////////////////////////////////////////////////////////////////////////
private Map<String, QBitMetaData> qBits = new LinkedHashMap<>();
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
private Map<String, QJoinMetaData> joins = new LinkedHashMap<>();
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
@ -116,11 +114,8 @@ 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... //
@ -1494,11 +1489,9 @@ public class QInstance
}
/*******************************************************************************
** Getter for metaDataFilter
*******************************************************************************/
@Deprecated(since = "migrated to metaDataCustomizer")
public QCodeReference getMetaDataFilter()
{
return (this.metaDataFilter);
@ -1509,7 +1502,6 @@ public class QInstance
/*******************************************************************************
** Setter for metaDataFilter
*******************************************************************************/
@Deprecated(since = "migrated to metaDataCustomizer")
public void setMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
@ -1520,7 +1512,6 @@ public class QInstance
/*******************************************************************************
** Fluent setter for metaDataFilter
*******************************************************************************/
@Deprecated(since = "migrated to metaDataCustomizer")
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
@ -1528,99 +1519,4 @@ public class QInstance
}
/*******************************************************************************
**
*******************************************************************************/
public void addQBit(QBitMetaData qBitMetaData)
{
List<String> missingParts = new ArrayList<>();
if(!StringUtils.hasContent(qBitMetaData.getGroupId()))
{
missingParts.add("groupId");
}
if(!StringUtils.hasContent(qBitMetaData.getArtifactId()))
{
missingParts.add("artifactId");
}
if(!StringUtils.hasContent(qBitMetaData.getVersion()))
{
missingParts.add("version");
}
if(!missingParts.isEmpty())
{
throw (new IllegalArgumentException("Attempted to add a qBit without a " + StringUtils.joinWithCommasAndAnd(missingParts)));
}
String name = qBitMetaData.getName();
if(this.qBits.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second qBit with name (formed from 'groupId:artifactId:version[:namespace]'): " + name));
}
this.qBits.put(name, qBitMetaData);
}
/*******************************************************************************
** Getter for qBits
*******************************************************************************/
public Map<String, QBitMetaData> getQBits()
{
return (this.qBits);
}
/*******************************************************************************
** Setter for qBits
*******************************************************************************/
public void setQBits(Map<String, QBitMetaData> qBits)
{
this.qBits = qBits;
}
/*******************************************************************************
** Fluent setter for qBits
*******************************************************************************/
public QInstance withQBits(Map<String, QBitMetaData> qBits)
{
this.qBits = qBits;
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);
}
}

View File

@ -1,269 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<String, Serializable> 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<String, Serializable> getAdditionalStyles()
{
return (this.additionalStyles);
}
/*******************************************************************************
** Setter for additionalStyles
*******************************************************************************/
public void setAdditionalStyles(Map<String, Serializable> additionalStyles)
{
this.additionalStyles = additionalStyles;
}
/*******************************************************************************
** Fluent setter for additionalStyles
*******************************************************************************/
public Banner withAdditionalStyles(Map<String, Serializable> 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);
}
}

View File

@ -1,31 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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
{
}

View File

@ -22,9 +22,6 @@
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;
@ -33,7 +30,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
** Meta-Data to define branding in a QQQ instance.
**
*******************************************************************************/
public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable, Serializable
public class QBrandingMetaData implements TopLevelMetaDataInterface
{
private String companyName;
private String companyUrl;
@ -42,45 +39,9 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
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<BannerSlot, Banner> 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<BannerSlot, Banner> entry : this.banners.entrySet())
{
clone.banners.put(entry.getKey(), entry.getValue().clone());
}
}
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
@ -306,7 +267,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Getter for environmentBannerText
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public String getEnvironmentBannerText()
{
return (this.environmentBannerText);
@ -317,7 +277,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Setter for environmentBannerText
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public void setEnvironmentBannerText(String environmentBannerText)
{
this.environmentBannerText = environmentBannerText;
@ -328,7 +287,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Fluent setter for environmentBannerText
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public QBrandingMetaData withEnvironmentBannerText(String environmentBannerText)
{
this.environmentBannerText = environmentBannerText;
@ -340,7 +298,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Getter for environmentBannerColor
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public String getEnvironmentBannerColor()
{
return (this.environmentBannerColor);
@ -351,7 +308,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Setter for environmentBannerColor
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public void setEnvironmentBannerColor(String environmentBannerColor)
{
this.environmentBannerColor = environmentBannerColor;
@ -362,7 +318,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
/*******************************************************************************
** Fluent setter for environmentBannerColor
*******************************************************************************/
@Deprecated(since = "migrate to use banners map instead")
public QBrandingMetaData withEnvironmentBannerColor(String environmentBannerColor)
{
this.environmentBannerColor = environmentBannerColor;
@ -379,52 +334,4 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable,
{
qInstance.setBranding(this);
}
/*******************************************************************************
** Getter for banners
*******************************************************************************/
public Map<BannerSlot, Banner> getBanners()
{
return (this.banners);
}
/*******************************************************************************
** Setter for banners
*******************************************************************************/
public void setBanners(Map<BannerSlot, Banner> banners)
{
this.banners = banners;
}
/*******************************************************************************
** Fluent setter for banners
*******************************************************************************/
public QBrandingMetaData withBanners(Map<BannerSlot, Banner> 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);
}
}

View File

@ -1,38 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
/*******************************************************************************
** an object which is intended to be constructed via a CodeReference, and,
** moreso, after it is created, then the initialize method here gets called,
** passing the codeRefernce in - e.g., to do additional initalization of the
** object, e.g., properties in a QCodeReferenceWithProperties
*******************************************************************************/
public interface InitializableViaCodeReference
{
/***************************************************************************
**
***************************************************************************/
void initialize(QCodeReference codeReference);
}

View File

@ -29,7 +29,7 @@ import java.io.Serializable;
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
** maybe process steps, maybe customization to a table, etc.
*******************************************************************************/
public class QCodeReference implements Serializable, Cloneable
public class QCodeReference implements Serializable
{
private String name;
private QCodeType codeType;
@ -58,25 +58,6 @@ public class QCodeReference implements Serializable, Cloneable
/***************************************************************************
**
***************************************************************************/
@Override
public QCodeReference clone()
{
try
{
QCodeReference clone = (QCodeReference) super.clone();
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -198,4 +179,5 @@ public class QCodeReference implements Serializable, Cloneable
this.inlineCode = inlineCode;
return (this);
}
}

View File

@ -1,59 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
import java.io.Serializable;
import java.util.Map;
/*******************************************************************************
** a code reference that also has a map of properties. This object (with the
** properties) will be passed in to the referenced object, if it implements
** InitializableViaCodeReference.
*******************************************************************************/
public class QCodeReferenceWithProperties extends QCodeReference
{
private final Map<String, Serializable> properties;
/***************************************************************************
**
***************************************************************************/
public QCodeReferenceWithProperties(Class<?> javaClass, Map<String, Serializable> properties)
{
super(javaClass);
this.properties = properties;
}
/*******************************************************************************
** Getter for properties
**
*******************************************************************************/
public Map<String, Serializable> getProperties()
{
return properties;
}
}

View File

@ -62,8 +62,7 @@ public class WidgetAdHocValue extends AbstractWidgetValueSource
context.putAll(inputValues);
}
@SuppressWarnings("unchecked")
Function<Object, Object> function = QCodeLoader.getAdHoc(Function.class, codeReference);
Function<Object, Object> function = QCodeLoader.getFunction(codeReference);
Object result = function.apply(context);
return (result);
}

View File

@ -23,12 +23,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
@ -46,21 +42,20 @@ public enum AdornmentType
REVEAL,
FILE_DOWNLOAD,
FILE_UPLOAD,
TOOLTIP,
ERROR;
//////////////////////////////////////////////////////////////////////////
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
//////////////////////////////////////////////////////////////////////////
/*******************************************************************************
**
*******************************************************************************/
public interface LinkValues
{
String TARGET = "target";
String TO_RECORD_FROM_TABLE = "toRecordFromTable";
String TO_RECORD_FROM_TABLE_DYNAMIC = "toRecordFromTableDynamic";
String TARGET = "target";
String TO_RECORD_FROM_TABLE = "toRecordFromTable";
}
@ -77,8 +72,6 @@ public enum AdornmentType
String SUPPLEMENTAL_PROCESS_NAME = "supplementalProcessName";
String SUPPLEMENTAL_CODE_REFERENCE = "supplementalCodeReference";
String DOWNLOAD_URL_DYNAMIC = "downloadUrlDynamic";
////////////////////////////////////////////////////
// use these two together, as in: //
// FILE_NAME_FORMAT = "Order %s Packing Slip.pdf" //
@ -86,17 +79,6 @@ public enum AdornmentType
////////////////////////////////////////////////////
String FILE_NAME_FORMAT = "fileNameFormat";
String FILE_NAME_FORMAT_FIELDS = "fileNameFormatFields";
/***************************************************************************
**
***************************************************************************/
static String makeFieldDownloadUrl(String tableName, Serializable primaryKey, String fieldName, String fileName)
{
return ("/data/" + tableName + "/"
+ URLEncoder.encode(Objects.requireNonNullElse(ValueUtils.getValueAsString(primaryKey), ""), StandardCharsets.UTF_8).replace("+", "%20") + "/"
+ fieldName + "/"
+ URLEncoder.encode(Objects.requireNonNullElse(fileName, ""), StandardCharsets.UTF_8).replace("+", "%20"));
}
}
@ -247,15 +229,4 @@ public enum AdornmentType
}
}
/*******************************************************************************
**
*******************************************************************************/
public interface TooltipValues
{
String STATIC_TEXT = "staticText";
String TOOLTIP_DYNAMIC = "tooltipDynamic";
}
}

View File

@ -238,7 +238,7 @@ public class QFieldMetaData implements Cloneable
if(StringUtils.hasContent(fieldAnnotation.defaultValue()))
{
withDefaultValue(ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue()));
ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue());
}
}
}

View File

@ -55,7 +55,6 @@ public class QFrontendFieldMetaData implements Serializable
private String possibleValueSourceName;
private String displayFormat;
private Serializable defaultValue;
private Integer maxLength;
private List<FieldAdornment> adornments;
private List<QHelpContent> helpContents;
@ -86,7 +85,6 @@ public class QFrontendFieldMetaData implements Serializable
this.defaultValue = fieldMetaData.getDefaultValue();
this.helpContents = fieldMetaData.getHelpContents();
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
this.maxLength = fieldMetaData.getMaxLength();
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
{

View File

@ -48,8 +48,6 @@ public class QFrontendProcessMetaData
private String label;
private String tableName;
private boolean isHidden;
private Integer minInputRecords;
private Integer maxInputRecords;
private QIcon icon;
@ -74,8 +72,6 @@ public class QFrontendProcessMetaData
this.tableName = processMetaData.getTableName();
this.isHidden = processMetaData.getIsHidden();
this.stepFlow = processMetaData.getStepFlow().toString();
this.minInputRecords = processMetaData.getMinInputRecords();
this.maxInputRecords = processMetaData.getMaxInputRecords();
if(includeSteps)
{
@ -217,27 +213,4 @@ public class QFrontendProcessMetaData
{
return icon;
}
/*******************************************************************************
** Getter for minInputRecords
**
*******************************************************************************/
public Integer getMinInputRecords()
{
return minInputRecords;
}
/*******************************************************************************
** Getter for maxInputRecords
**
*******************************************************************************/
public Integer getMaxInputRecords()
{
return maxInputRecords;
}
}

View File

@ -31,7 +31,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
** Future may allow something like a "namespace", and/or multiple icons for
** use in different frontends, etc.
*******************************************************************************/
public class QIcon implements Cloneable
public class QIcon
{
private String name;
private String path;
@ -58,25 +58,6 @@ public class QIcon implements Cloneable
/***************************************************************************
**
***************************************************************************/
@Override
public QIcon clone()
{
try
{
QIcon clone = (QIcon) super.clone();
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
** Getter for name
**
@ -173,4 +154,6 @@ public class QIcon implements Cloneable
this.color = color;
return (this);
}
}

View File

@ -216,16 +216,11 @@ public class SendSESAction
{
LOG.warn("More than one FROM value was found, will send using the first one found [" + partyList.get(0).getAddress() + "].");
}
Party fromParty = partyList.get(0);
if(fromParty.getAddress() == null)
{
throw (new QException("Cannot send SES message because a FROM address was not provided."));
}
/////////////////////////////
// return the from address //
/////////////////////////////
return (getFullEmailAddress(fromParty));
return (partyList.get(0).getAddress());
}
@ -272,15 +267,15 @@ public class SendSESAction
{
if(EmailPartyRole.CC.equals(party.getRole()))
{
ccList.add(getFullEmailAddress(party));
ccList.add(party.getAddress());
}
else if(EmailPartyRole.BCC.equals(party.getRole()))
{
bccList.add(getFullEmailAddress(party));
bccList.add(party.getAddress());
}
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
{
toList.add(getFullEmailAddress(party));
toList.add(party.getAddress());
}
else
{
@ -337,22 +332,4 @@ public class SendSESAction
return amazonSES;
}
/*******************************************************************************
**
*******************************************************************************/
private String getFullEmailAddress(Party party)
{
if(party.getLabel() != null)
{
return (party.getLabel() + " <" + party.getAddress() + ">");
}
/////////////////////////////
// return the from address //
/////////////////////////////
return (party.getAddress());
}
}

View File

@ -37,7 +37,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -47,14 +46,11 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
** Meta-Data to define a process in a QQQ instance.
**
*******************************************************************************/
public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface, SourceQBitAware
public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
{
private String name;
private String label;
private String tableName;
private String sourceQBitName;
private String name;
private String label;
private String tableName;
private boolean isHidden = false;
private BasepullConfiguration basepullConfiguration;
private QPermissionRules permissionRules;
@ -874,7 +870,6 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
}
/*******************************************************************************
** Getter for processTracerCodeReference
*******************************************************************************/
@ -905,37 +900,4 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
}
/*******************************************************************************
** Getter for sourceQBitName
*******************************************************************************/
@Override
public String getSourceQBitName()
{
return (this.sourceQBitName);
}
/*******************************************************************************
** Setter for sourceQBitName
*******************************************************************************/
@Override
public void setSourceQBitName(String sourceQBitName)
{
this.sourceQBitName = sourceQBitName;
}
/*******************************************************************************
** Fluent setter for sourceQBitName
*******************************************************************************/
@Override
public QProcessMetaData withSourceQBitName(String sourceQBitName)
{
this.sourceQBitName = sourceQBitName;
return (this);
}
}

View File

@ -24,13 +24,11 @@ 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.actions.tables.query.QFilterOrderBy;
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.producers.annotations.ChildJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -41,11 +39,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
**
** 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"))
}
@QMetaDataProducingEntity(
childTables = { @ChildTable(
childTableEntityClass = LineItem.class,
childJoin = @ChildJoin(enabled = true),
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
}
)
public class Order extends QRecordEntity
** </code>
@ -63,16 +62,12 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
private String parentTableName; // e.g., order
private String foreignKeyFieldName; // e.g., orderId
private ChildJoin.OrderBy[] orderBys;
private Class<?> sourceClass;
/***************************************************************************
**
***************************************************************************/
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName, ChildJoin.OrderBy[] orderBys)
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName)
{
Objects.requireNonNull(childTableName, "childTableName cannot be null");
Objects.requireNonNull(parentTableName, "parentTableName cannot be null");
@ -81,7 +76,6 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
this.childTableName = childTableName;
this.parentTableName = parentTableName;
this.foreignKeyFieldName = foreignKeyFieldName;
this.orderBys = orderBys;
}
@ -92,75 +86,20 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
@Override
public QJoinMetaData produce(QInstance qInstance) throws QException
{
QTableMetaData parentTable = qInstance.getTable(parentTableName);
if(parentTable == null)
QTableMetaData possibleValueTable = qInstance.getTable(parentTableName);
if(possibleValueTable == null)
{
throw (new QException("Could not find tableMetaData " + parentTableName));
}
QTableMetaData childTable = qInstance.getTable(childTableName);
if(childTable == null)
{
throw (new QException("Could not find tableMetaData " + childTable));
}
QJoinMetaData join = new QJoinMetaData()
.withLeftTable(parentTableName)
.withRightTable(childTableName)
.withInferredName()
.withType(JoinType.ONE_TO_MANY)
.withJoinOn(new JoinOn(parentTable.getPrimaryKeyField(), foreignKeyFieldName));
if(orderBys != null && orderBys.length > 0)
{
for(ChildJoin.OrderBy orderBy : orderBys)
{
join.withOrderBy(new QFilterOrderBy(orderBy.fieldName(), orderBy.isAscending()));
}
}
else
{
//////////////////////////////////////////////////////////
// by default, sort by the id of the child table... mmm //
//////////////////////////////////////////////////////////
join.withOrderBy(new QFilterOrderBy(childTable.getPrimaryKeyField()));
}
.withJoinOn(new JoinOn(possibleValueTable.getPrimaryKeyField(), foreignKeyFieldName));
return (join);
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public ChildJoinFromRecordEntityGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -57,8 +57,6 @@ public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implem
private ChildRecordListWidget childRecordListWidget;
private Class<?> sourceClass;
/***************************************************************************
@ -113,36 +111,4 @@ public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implem
return (widget);
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -40,10 +40,6 @@ public class PossibleValueSourceOfEnumGenericMetaDataProducer<T extends Serializ
private final String name;
private final PossibleValueEnum<T>[] values;
private Class<?> sourceClass;
/*******************************************************************************
@ -66,37 +62,4 @@ public class PossibleValueSourceOfEnumGenericMetaDataProducer<T extends Serializ
{
return (QPossibleValueSource.newForEnum(name, values));
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public PossibleValueSourceOfEnumGenericMetaDataProducer<T> withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -37,7 +37,6 @@ public class PossibleValueSourceOfTableGenericMetaDataProducer implements MetaDa
{
private final String tableName;
private Class<?> sourceClass;
/*******************************************************************************
@ -59,38 +58,4 @@ public class PossibleValueSourceOfTableGenericMetaDataProducer implements MetaDa
{
return (QPossibleValueSource.newForTable(tableName));
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public PossibleValueSourceOfTableGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -48,7 +48,6 @@ public class RecordEntityToTableGenericMetaDataProducer implements MetaDataProdu
private static MetaDataCustomizerInterface<QTableMetaData> defaultMetaDataCustomizer = null;
private Class<?> sourceClass;
/*******************************************************************************
@ -155,37 +154,4 @@ public class RecordEntityToTableGenericMetaDataProducer implements MetaDataProdu
RecordEntityToTableGenericMetaDataProducer.defaultMetaDataCustomizer = defaultMetaDataCustomizer;
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class<?> getSourceClass()
{
return sourceClass;
}
/*******************************************************************************
** Setter for sourceClass
**
*******************************************************************************/
public void setSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Fluent setter for sourceClass
**
*******************************************************************************/
public RecordEntityToTableGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
{
this.sourceClass = sourceClass;
return (this);
}
}

View File

@ -35,16 +35,4 @@ import java.lang.annotation.RetentionPolicy;
public @interface ChildJoin
{
boolean enabled();
OrderBy[] orderBy() default { };
/***************************************************************************
**
***************************************************************************/
@interface OrderBy
{
String fieldName();
boolean isAscending() default true;
}
}

View File

@ -1,122 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
/***************************************************************************
** Common (maybe)? qbit config pattern, where the qbit may be able to provide
** a particular table, or, the application may supply it itself.
**
** If the qbit provides it, then we need to be told (by the application)
** what backendName to use for the table.
**
** Else if the application supplies it, it needs to tell the qBit what the
** tableName is.
***************************************************************************/
public class ProvidedOrSuppliedTableConfig
{
private boolean doProvideTable;
private String backendName;
private String tableName;
/***************************************************************************
**
***************************************************************************/
public ProvidedOrSuppliedTableConfig(boolean doProvideTable, String backendName, String tableName)
{
this.doProvideTable = doProvideTable;
this.backendName = backendName;
this.tableName = tableName;
}
/***************************************************************************
**
***************************************************************************/
public static ProvidedOrSuppliedTableConfig provideTableUsingBackendNamed(String backendName)
{
return (new ProvidedOrSuppliedTableConfig(true, backendName, null));
}
/***************************************************************************
**
***************************************************************************/
public static ProvidedOrSuppliedTableConfig useSuppliedTaleNamed(String tableName)
{
return (new ProvidedOrSuppliedTableConfig(false, null, tableName));
}
/***************************************************************************
**
***************************************************************************/
public String getEffectiveTableName(String tableNameIfProviding)
{
if (getDoProvideTable())
{
return tableNameIfProviding;
}
else
{
return getTableName();
}
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Getter for doProvideTable
**
*******************************************************************************/
public boolean getDoProvideTable()
{
return doProvideTable;
}
/*******************************************************************************
** Getter for backendName
**
*******************************************************************************/
public String getBackendName()
{
return backendName;
}
}

View File

@ -1,71 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
/*******************************************************************************
** extension of MetaDataProducerInterface, designed for producing meta data
** within a (java-defined, at this time) QBit.
**
** Specifically exists to accept the QBitConfig as a type parameter and a value,
** easily accessed in the producer's methods as getQBitConfig()
*******************************************************************************/
public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOutput, C extends QBitConfig> implements MetaDataProducerInterface<T>
{
private C qBitConfig = null;
/*******************************************************************************
** Getter for qBitConfig
*******************************************************************************/
public C getQBitConfig()
{
return (this.qBitConfig);
}
/*******************************************************************************
** Setter for qBitConfig
*******************************************************************************/
public void setQBitConfig(C qBitConfig)
{
this.qBitConfig = qBitConfig;
}
/*******************************************************************************
** Fluent setter for qBitConfig
*******************************************************************************/
public QBitComponentMetaDataProducer<T, C> withQBitConfig(C qBitConfig)
{
this.qBitConfig = qBitConfig;
return (this);
}
}

View File

@ -1,110 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
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.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Interface for configuration settings used both in the production of meta-data
** for a QBit, but also at runtime, e.g., to be aware of exactly how the qbit
** has been incorporated into an application.
**
** For example:
** - should the QBit define certain tables, or will they be supplied by the application?
** - what other meta-data names should the qbit reference (backends, schedulers)
** - what meta-data-customizer(s) should be used?
**
** When implementing a QBit, you'll implement this interface - adding whatever
** (if any) properties you need, and if you have any rules, then overriding
** the validate method (ideally the one that takes the List-of-String errors)
**
** When using a QBit, you'll create an instance of the QBit's config object,
** and pass it through to the QBit producer.
*******************************************************************************/
public interface QBitConfig extends Serializable
{
QLogger LOG = QLogger.getLogger(QBitConfig.class);
/***************************************************************************
**
***************************************************************************/
default void validate(QInstance qInstance) throws QBitConfigValidationException
{
List<String> errors = new ArrayList<>();
try
{
validate(qInstance, errors);
}
catch(Exception e)
{
LOG.warn("Error validating QBitConfig: " + this.getClass().getName(), e);
}
if(!errors.isEmpty())
{
throw (new QBitConfigValidationException(this, errors));
}
}
/***************************************************************************
**
***************************************************************************/
default void validate(QInstance qInstance, List<String> errors)
{
/////////////////////////////////////
// nothing to validate by default! //
/////////////////////////////////////
}
/***************************************************************************
**
***************************************************************************/
default boolean assertCondition(boolean condition, String message, List<String> errors)
{
if(!condition)
{
errors.add(message);
}
return (condition);
}
/***************************************************************************
**
***************************************************************************/
default MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
{
return (null);
}
}

View File

@ -1,44 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** thrown by QBitConfig.validate() if there's an issue.
*******************************************************************************/
public class QBitConfigValidationException extends QException
{
/***************************************************************************
**
***************************************************************************/
public QBitConfigValidationException(QBitConfig qBitConfig, List<String> errors)
{
super("Validation failed for QBitConfig: " + qBitConfig.getClass().getName() + ":\n" + StringUtils.join("\n", errors));
}
}

View File

@ -1,237 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Meta-data to define an active QBit in a QQQ Instance.
**
** The unique "name" for the QBit is composed of its groupId and artifactId
** (maven style). There is also a version - but it is not part of the unique
** name. But - there is also a namespace attribute, which IS part of the
** unique name. This will (eventually?) allow us to have multiple instances
** of the same qbit in a qInstance at the same time (e.g., 2 versions of some
** table, which should be namespace-prefixed);
**
** QBitMetaData also retains the QBitConfig that was used to produce the QBit.
**
** Some meta-data objects are aware of the fact that they may have come from a
** QBit - see SourceQBitAware interface. These objects can get their source
** QBitMetaData (this object) and its config,via that interface.
*******************************************************************************/
public class QBitMetaData implements TopLevelMetaDataInterface
{
private String groupId;
private String artifactId;
private String version;
private String namespace;
private QBitConfig config;
/***************************************************************************
**
***************************************************************************/
@Override
public String getName()
{
String name = groupId + ":" + artifactId;
if(StringUtils.hasContent(namespace))
{
name += ":" + namespace;
}
return name;
}
/***************************************************************************
**
***************************************************************************/
@Override
public void addSelfToInstance(QInstance qInstance)
{
qInstance.addQBit(this);
}
/*******************************************************************************
** Getter for config
*******************************************************************************/
public QBitConfig getConfig()
{
return (this.config);
}
/*******************************************************************************
** Setter for config
*******************************************************************************/
public void setConfig(QBitConfig config)
{
this.config = config;
}
/*******************************************************************************
** Fluent setter for config
*******************************************************************************/
public QBitMetaData withConfig(QBitConfig config)
{
this.config = config;
return (this);
}
/*******************************************************************************
** Getter for groupId
*******************************************************************************/
public String getGroupId()
{
return (this.groupId);
}
/*******************************************************************************
** Setter for groupId
*******************************************************************************/
public void setGroupId(String groupId)
{
this.groupId = groupId;
}
/*******************************************************************************
** Fluent setter for groupId
*******************************************************************************/
public QBitMetaData withGroupId(String groupId)
{
this.groupId = groupId;
return (this);
}
/*******************************************************************************
** Getter for artifactId
*******************************************************************************/
public String getArtifactId()
{
return (this.artifactId);
}
/*******************************************************************************
** Setter for artifactId
*******************************************************************************/
public void setArtifactId(String artifactId)
{
this.artifactId = artifactId;
}
/*******************************************************************************
** Fluent setter for artifactId
*******************************************************************************/
public QBitMetaData withArtifactId(String artifactId)
{
this.artifactId = artifactId;
return (this);
}
/*******************************************************************************
** Getter for version
*******************************************************************************/
public String getVersion()
{
return (this.version);
}
/*******************************************************************************
** Setter for version
*******************************************************************************/
public void setVersion(String version)
{
this.version = version;
}
/*******************************************************************************
** Fluent setter for version
*******************************************************************************/
public QBitMetaData withVersion(String version)
{
this.version = version;
return (this);
}
/*******************************************************************************
** Getter for namespace
*******************************************************************************/
public String getNamespace()
{
return (this.namespace);
}
/*******************************************************************************
** Setter for namespace
*******************************************************************************/
public void setNamespace(String namespace)
{
this.namespace = namespace;
}
/*******************************************************************************
** Fluent setter for namespace
*******************************************************************************/
public QBitMetaData withNamespace(String namespace)
{
this.namespace = namespace;
return (this);
}
}

View File

@ -1,117 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** interface for how a QBit's meta-data gets produced and added to a QInstance.
**
** When implementing a QBit, you'll implement this interface:
** - adding a QBitConfig subclass as a property
** - overriding the produce(qInstance, namespace) method - where you'll:
** -- create and add your QBitMetaData
** -- call MetaDataProducerHelper.findProducers
** -- hand off to finishProducing() in this interface
**
** When using a QBit, you'll create an instance of the QBit's config object,
** pass it in to the producer, then call produce, ala:
**
** new SomeQBitProducer()
** .withQBitConfig(someQBitConfig)
** .produce(qInstance);
**
*******************************************************************************/
public interface QBitProducer
{
QLogger LOG = QLogger.getLogger(QBitProducer.class);
/***************************************************************************
**
***************************************************************************/
default void produce(QInstance qInstance) throws QException
{
produce(qInstance, null);
}
/***************************************************************************
**
***************************************************************************/
void produce(QInstance qInstance, String namespace) throws QException;
/***************************************************************************
*
***************************************************************************/
default <C extends QBitConfig> void finishProducing(QInstance qInstance, QBitMetaData qBitMetaData, C qBitConfig, List<MetaDataProducerInterface<?>> producers) throws QException
{
qBitConfig.validate(qInstance);
///////////////////////////////
// todo - move to base class //
///////////////////////////////
for(MetaDataProducerInterface<?> producer : producers)
{
if(producer instanceof QBitComponentMetaDataProducer<?, ?>)
{
QBitComponentMetaDataProducer<?, C> qBitComponentMetaDataProducer = (QBitComponentMetaDataProducer<?, C>) producer;
qBitComponentMetaDataProducer.setQBitConfig(qBitConfig);
}
if(!producer.isEnabled())
{
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
continue;
}
MetaDataProducerOutput output = producer.produce(qInstance);
/////////////////////////////////////////
// apply table customizer, if provided //
/////////////////////////////////////////
if(qBitConfig.getTableMetaDataCustomizer() != null && output instanceof QTableMetaData table)
{
output = qBitConfig.getTableMetaDataCustomizer().customizeMetaData(qInstance, table);
}
/////////////////////////////////////////////////
// set source qbit, if output is aware of such //
/////////////////////////////////////////////////
if(output instanceof SourceQBitAware sourceQBitAware)
{
sourceQBitAware.setSourceQBitName(qBitMetaData.getName());
}
output.addSelfToInstance(qInstance);
}
}
}

View File

@ -1,77 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import com.kingsrook.qqq.backend.core.context.QContext;
/*******************************************************************************
** interface for meta data objects that may have come from a qbit, and where we
** might want to get data about that qbit (e.g., config or meta-data).
*******************************************************************************/
public interface SourceQBitAware
{
/*******************************************************************************
** Getter for sourceQBitName
*******************************************************************************/
String getSourceQBitName();
/*******************************************************************************
** Setter for sourceQBitName
*******************************************************************************/
void setSourceQBitName(String sourceQBitName);
/*******************************************************************************
** Fluent setter for sourceQBitName
*******************************************************************************/
Object withSourceQBitName(String sourceQBitName);
/***************************************************************************
**
***************************************************************************/
default QBitMetaData getSourceQBit()
{
String qbitName = getSourceQBitName();
return (QContext.getQInstance().getQBits().get(qbitName));
}
/***************************************************************************
**
***************************************************************************/
default QBitConfig getSourceQBitConfig()
{
QBitMetaData sourceQBit = getSourceQBit();
if(sourceQBit == null)
{
return null;
}
else
{
return sourceQBit.getConfig();
}
}
}

View File

@ -40,7 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
** (optionally along with queryJoins and queryInputCustomizer) is used.
** - else a staticDataSupplier is used.
*******************************************************************************/
public class QReportDataSource implements Cloneable
public class QReportDataSource
{
private String name;
@ -55,39 +55,6 @@ public class QReportDataSource implements Cloneable
/***************************************************************************
**
***************************************************************************/
@Override
public QReportDataSource clone()
{
try
{
QReportDataSource clone = (QReportDataSource) super.clone();
if(queryFilter != null)
{
clone.queryFilter = queryFilter.clone();
}
if(queryJoins != null)
{
clone.queryJoins = new ArrayList<>();
for(QueryJoin join : queryJoins)
{
queryJoins.add(join.clone());
}
}
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
** Getter for name
**
@ -307,7 +274,6 @@ public class QReportDataSource implements Cloneable
}
/*******************************************************************************
** Getter for customRecordSource
*******************************************************************************/
@ -337,4 +303,5 @@ public class QReportDataSource implements Cloneable
return (this);
}
}

View File

@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
@ -38,7 +37,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Meta-data definition of a report generated by QQQ
*******************************************************************************/
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface, Cloneable
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
{
private String name;
private String label;
@ -53,72 +52,6 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
private QIcon icon;
private QCodeReference exportStyleCustomizer;
/***************************************************************************
**
***************************************************************************/
@Override
public QReportMetaData clone()
{
try
{
QReportMetaData clone = (QReportMetaData) super.clone();
//////////////////////////////
// Deep copy mutable fields //
//////////////////////////////
if(this.inputFields != null)
{
clone.inputFields = new ArrayList<>();
for(QFieldMetaData inputField : this.inputFields)
{
clone.inputFields.add(inputField.clone());
}
}
if(this.dataSources != null)
{
clone.dataSources = new ArrayList<>();
for(QReportDataSource dataSource : this.dataSources)
{
clone.dataSources.add(dataSource.clone());
}
}
if(this.views != null)
{
clone.views = new ArrayList<>();
for(QReportView view : this.views)
{
clone.views.add(view.clone());
}
}
if(this.permissionRules != null)
{
clone.permissionRules = this.permissionRules.clone();
}
if(this.icon != null)
{
clone.icon = this.icon.clone();
}
if(this.exportStyleCustomizer != null)
{
clone.exportStyleCustomizer = this.exportStyleCustomizer.clone();
}
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError("Cloning not supported", e);
}
}
/*******************************************************************************
@ -464,35 +397,4 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
qInstance.addReport(this);
}
/*******************************************************************************
** Getter for exportStyleCustomizer
*******************************************************************************/
public QCodeReference getExportStyleCustomizer()
{
return (this.exportStyleCustomizer);
}
/*******************************************************************************
** Setter for exportStyleCustomizer
*******************************************************************************/
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
{
this.exportStyleCustomizer = exportStyleCustomizer;
}
/*******************************************************************************
** Fluent setter for exportStyleCustomizer
*******************************************************************************/
public QReportMetaData withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
{
this.exportStyleCustomizer = exportStyleCustomizer;
return (this);
}
}

View File

@ -50,7 +50,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
@ -63,7 +62,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
** Meta-Data to define a table in a QQQ instance.
**
*******************************************************************************/
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface, SourceQBitAware
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface
{
private static final QLogger LOG = QLogger.getLogger(QTableMetaData.class);
@ -74,8 +73,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
private String primaryKeyField;
private boolean isHidden = false;
private String sourceQBitName;
private Map<String, QFieldMetaData> fields;
private List<UniqueKey> uniqueKeys;
private List<Association> associations;
@ -1059,7 +1056,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
{
for(Capability disabledCapability : disabledCapabilities)
{
withoutCapability(disabledCapability);
withCapability(disabledCapability);
}
return (this);
}
@ -1557,38 +1554,4 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
}
/*******************************************************************************
** Getter for sourceQBitName
*******************************************************************************/
@Override
public String getSourceQBitName()
{
return (this.sourceQBitName);
}
/*******************************************************************************
** Setter for sourceQBitName
*******************************************************************************/
@Override
public void setSourceQBitName(String sourceQBitName)
{
this.sourceQBitName = sourceQBitName;
}
/*******************************************************************************
** Fluent setter for sourceQBitName
*******************************************************************************/
@Override
public QTableMetaData withSourceQBitName(String sourceQBitName)
{
this.sourceQBitName = sourceQBitName;
return (this);
}
}

View File

@ -1,89 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.tables;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** possible-value source provider for the `Tables` PVS - a list of all tables
** in an application/qInstance.
*******************************************************************************/
public class TablesCustomPossibleValueProvider implements QCustomPossibleValueProvider<String>
{
/***************************************************************************
**
***************************************************************************/
@Override
public QPossibleValue<String> getPossibleValue(Serializable idValue)
{
QTableMetaData table = QContext.getQInstance().getTable(ValueUtils.getValueAsString(idValue));
if(table != null && !table.getIsHidden())
{
PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table);
if(PermissionCheckResult.ALLOW.equals(permissionCheckResult))
{
return (new QPossibleValue<>(table.getName(), table.getLabel()));
}
}
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QPossibleValue<String>> search(SearchPossibleValueSourceInput input) throws QException
{
/////////////////////////////////////////////////////////////////////////////////////
// build all of the possible values (note, will be filtered by user's permissions) //
/////////////////////////////////////////////////////////////////////////////////////
List<QPossibleValue<String>> allPossibleValues = new ArrayList<>();
for(QTableMetaData table : QContext.getQInstance().getTables().values())
{
QPossibleValue<String> possibleValue = getPossibleValue(table.getName());
if(possibleValue != null)
{
allPossibleValues.add(possibleValue);
}
}
return completeCustomPVSSearch(input, allPossibleValues);
}
}

View File

@ -22,11 +22,17 @@
package com.kingsrook.qqq.backend.core.model.metadata.tables;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
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.possiblevalues.PVSValueFormatAndFields;
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.utils.StringUtils;
import org.apache.commons.lang.BooleanUtils;
/*******************************************************************************
@ -45,10 +51,22 @@ public class TablesPossibleValueSourceMetaDataProvider
{
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
.withName(NAME)
.withType(QPossibleValueSourceType.CUSTOM)
.withCustomCodeReference(new QCodeReference(TablesCustomPossibleValueProvider.class))
.withType(QPossibleValueSourceType.ENUM)
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
List<QPossibleValue<?>> enumValues = new ArrayList<>();
for(QTableMetaData table : qInstance.getTables().values())
{
if(BooleanUtils.isNotTrue(table.getIsHidden()))
{
String label = StringUtils.hasContent(table.getLabel()) ? table.getLabel() : QInstanceEnricher.nameToLabel(table.getName());
enumValues.add(new QPossibleValue<>(table.getName(), label));
}
}
enumValues.sort(Comparator.comparing(QPossibleValue::getLabel));
possibleValueSource.withEnumValues(enumValues);
return (possibleValueSource);
}

View File

@ -56,16 +56,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Note - exists under 2 names, for the RenderSavedReport process, and for the
** ScheduledReport table (and can be used in your custom code too:
*
** by default, in qqq backend core, we'll assume this widget is being used on the
** view screen for a ScheduledReport, with field names that we know from that table.
** But, allow it to be used on a different table (optionally with different field names),
** coming from the input map.
**
** e.g., that one may set in widget metaData as:
** .withDefaultValue("tableName", "myTable")
** .withDefaultValue("fieldNameId", "identifier"), etc.
** ScheduledReport table
*******************************************************************************/
public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRenderer
{
@ -97,16 +88,11 @@ public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRendere
}
else if(input.getQueryParams().containsKey("id"))
{
String tableName = input.getQueryParams().getOrDefault("tableName", ScheduledReport.TABLE_NAME);
String fieldNameId = input.getQueryParams().getOrDefault("fieldNameId", "id");
String fieldNameSavedReportId = input.getQueryParams().getOrDefault("fieldNameSavedReportId", "savedReportId");
String fieldNameInputValues = input.getQueryParams().getOrDefault("fieldNameInputValues", "inputValues");
QRecord hostRecord = new GetAction().executeForRecord(new GetInput(tableName).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get(fieldNameId))));
QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(hostRecord.getValueInteger(fieldNameSavedReportId))));
QRecord scheduledReportRecord = new GetAction().executeForRecord(new GetInput(ScheduledReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get("id"))));
QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(scheduledReportRecord.getValueInteger("savedReportId"))));
savedReport = new SavedReport(record);
String inputValues = hostRecord.getValueString(fieldNameInputValues);
String inputValues = scheduledReportRecord.getValueString("inputValues");
if(StringUtils.hasContent(inputValues))
{
JSONObject jsonObject = JsonUtils.toJSONObject(inputValues);
@ -211,8 +197,8 @@ public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRendere
}
catch(Exception e)
{
LOG.warn("Error rendering report values dynamic form widget", e, logPair("queryParams", String.valueOf(input.getQueryParams())));
throw (new QException("Error rendering report values dynamic form widget", e));
LOG.warn("Error rendering scheduled report values dynamic form widget", e, logPair("queryParams", String.valueOf(input.getQueryParams())));
throw (new QException("Error rendering scheduled report values dynamic form widget", e));
}
}

View File

@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableAudienceType;
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -350,7 +351,8 @@ public class SavedReportsMetaDataProvider
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "savedReportId", "renderedReportStatusId")))
.withSection(new QFieldSection("input", new QIcon().withName("input"), Tier.T2, List.of("userId", "reportFormat")))
.withSection(new QFieldSection("output", new QIcon().withName("output"), Tier.T2, List.of("jobUuid", "resultPath", "rowCount", "errorMessage", "startTime", "endTime")))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
.withoutCapabilities(Capability.allWriteCapabilities());
table.getField("renderedReportStatusId").setAdornments(List.of(new FieldAdornment(AdornmentType.CHIP)
.withValues(AdornmentType.ChipValues.iconAndColorValues(RenderedReportStatus.RUNNING.getId(), "pending", AdornmentType.ChipValues.COLOR_SECONDARY))

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -111,10 +110,6 @@ public class BulkInsertLoadStep extends LoadViaInsertStep implements ProcessSumm
if(field.getType().isNumeric())
{
ProcessSummaryLine idsLine = new ProcessSummaryLine(Status.INFO, "Inserted " + field.getLabel() + " values between " + firstInsertedPrimaryKey + " and " + lastInsertedPrimaryKey);
if(Objects.equals(firstInsertedPrimaryKey, lastInsertedPrimaryKey))
{
idsLine.setMessage("Inserted " + field.getLabel() + " " + firstInsertedPrimaryKey);
}
idsLine.setCount(null);
processSummary.add(idsLine);
}

View File

@ -24,12 +24,8 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -41,14 +37,9 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapp
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapping.BulkLoadTableStructureBuilder;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadFileRow;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadTableStructure;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
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;
import org.json.JSONObject;
/*******************************************************************************
@ -81,7 +72,7 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
{
@SuppressWarnings("unchecked")
List<String> headerValues = (List<String>) runBackendStepOutput.getValue("headerValues");
buildSuggestedMapping(headerValues, getPrepopulatedValues(runBackendStepInput), tableStructure, runBackendStepOutput);
buildSuggestedMapping(headerValues, tableStructure, runBackendStepOutput);
}
}
@ -90,62 +81,10 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
/***************************************************************************
**
***************************************************************************/
private Map<String, Serializable> getPrepopulatedValues(RunBackendStepInput runBackendStepInput)
{
String prepopulatedValuesJson = runBackendStepInput.getValueString("prepopulatedValues");
if(StringUtils.hasContent(prepopulatedValuesJson))
{
Map<String, Serializable> rs = new LinkedHashMap<>();
JSONObject jsonObject = JsonUtils.toJSONObject(prepopulatedValuesJson);
for(String key : jsonObject.keySet())
{
rs.put(key, jsonObject.optString(key, null));
}
return (rs);
}
return (Collections.emptyMap());
}
/***************************************************************************
**
***************************************************************************/
private void buildSuggestedMapping(List<String> headerValues, Map<String, Serializable> prepopulatedValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
private void buildSuggestedMapping(List<String> headerValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
{
BulkLoadMappingSuggester bulkLoadMappingSuggester = new BulkLoadMappingSuggester();
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues);
if(CollectionUtils.nullSafeHasContents(prepopulatedValues))
{
for(Map.Entry<String, Serializable> entry : prepopulatedValues.entrySet())
{
String fieldName = entry.getKey();
boolean foundFieldInProfile = false;
for(BulkLoadProfileField bulkLoadProfileField : bulkLoadProfile.getFieldList())
{
if(bulkLoadProfileField.getFieldName().equals(fieldName))
{
foundFieldInProfile = true;
bulkLoadProfileField.setColumnIndex(null);
bulkLoadProfileField.setHeaderName(null);
bulkLoadProfileField.setDefaultValue(entry.getValue());
break;
}
}
if(!foundFieldInProfile)
{
BulkLoadProfileField bulkLoadProfileField = new BulkLoadProfileField();
bulkLoadProfileField.setFieldName(fieldName);
bulkLoadProfileField.setDefaultValue(entry.getValue());
bulkLoadProfile.getFieldList().add(bulkLoadProfileField);
}
}
}
runBackendStepOutput.addValue("bulkLoadProfile", bulkLoadProfile);
runBackendStepOutput.addValue("suggestedBulkLoadProfile", bulkLoadProfile);
}

View File

@ -76,23 +76,6 @@ public class XlsxFileToRows extends AbstractIteratorBasedFileToRows<org.dhatim.f
}
/***************************************************************************
** open/go-to a specific sheet (by 0-based index). resets rows & iterator.
***************************************************************************/
public void openSheet(int index) throws IOException
{
Optional<Sheet> sheet = workbook.getSheet(index);
if(sheet.isEmpty())
{
throw (new IOException("No sheet found for index: " + index));
}
rows = sheet.get().openStream();
setIterator(rows.iterator());
}
/***************************************************************************
**

View File

@ -53,7 +53,7 @@ public class BaseStreamedETLStep
protected AbstractExtractStep getExtractStep(RunBackendStepInput runBackendStepInput)
{
QCodeReference codeReference = (QCodeReference) runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_EXTRACT_CODE);
return (QCodeLoader.getAdHoc(AbstractExtractStep.class, codeReference));
return (QCodeLoader.getBackendStep(AbstractExtractStep.class, codeReference));
}

View File

@ -22,13 +22,13 @@
package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
/*******************************************************************************
**
*******************************************************************************/
public class CouldNotFindQueryFilterForExtractStepException extends QUserFacingException
public class CouldNotFindQueryFilterForExtractStepException extends QException
{
/*******************************************************************************
**

View File

@ -279,7 +279,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, idStrings)));
}
throw (new CouldNotFindQueryFilterForExtractStepException("No records were selected for running this process."));
throw (new CouldNotFindQueryFilterForExtractStepException("Could not find query filter for Extract step."));
}

View File

@ -92,21 +92,16 @@ public class RenderSavedReportExecuteStep implements BackendStep
////////////////////////////////
// read inputs, set up params //
////////////////////////////////
String sesProviderName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.SES_PROVIDER_NAME);
String fromEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FROM_EMAIL_ADDRESS);
String replyToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.REPLY_TO_EMAIL_ADDRESS);
String storageTableName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME);
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT));
String sendToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS);
String emailSubject = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_SUBJECT);
SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0));
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
String storageReference = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_REFERENCE);
if(!StringUtils.hasContent(storageReference))
{
storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
}
String sesProviderName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.SES_PROVIDER_NAME);
String fromEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FROM_EMAIL_ADDRESS);
String replyToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.REPLY_TO_EMAIL_ADDRESS);
String storageTableName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME);
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT));
String sendToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS);
String emailSubject = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_SUBJECT);
SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0));
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
String storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if sending an email (or emails), validate the addresses before doing anything so user gets error and can fix //
@ -246,7 +241,7 @@ public class RenderSavedReportExecuteStep implements BackendStep
/*******************************************************************************
**
*******************************************************************************/
public static String getDownloadFileBaseName(RunBackendStepInput runBackendStepInput, SavedReport report)
private String getDownloadFileBaseName(RunBackendStepInput runBackendStepInput, SavedReport report)
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmm").withZone(ZoneId.systemDefault());
String datePart = formatter.format(Instant.now());

View File

@ -56,7 +56,6 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
public static final String FROM_EMAIL_ADDRESS = "fromEmailAddress";
public static final String REPLY_TO_EMAIL_ADDRESS = "replyToEmailAddress";
public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName";
public static final String FIELD_NAME_STORAGE_REFERENCE = "storageReference";
public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat";
public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress";
public static final String FIELD_NAME_EMAIL_SUBJECT = "emailSubject";
@ -82,7 +81,6 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withField(new QFieldMetaData(FROM_EMAIL_ADDRESS, QFieldType.STRING))
.withField(new QFieldMetaData(REPLY_TO_EMAIL_ADDRESS, QFieldType.STRING))
.withField(new QFieldMetaData(FIELD_NAME_STORAGE_TABLE_NAME, QFieldType.STRING))
.withField(new QFieldMetaData(FIELD_NAME_STORAGE_REFERENCE, QFieldType.STRING))
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))

View File

@ -318,7 +318,7 @@ public class SavedReportToReportMetaDataAdapter
/*******************************************************************************
**
*******************************************************************************/
public static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
private static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
{
QReportField reportField = new QReportField();
@ -404,5 +404,5 @@ public class SavedReportToReportMetaDataAdapter
/*******************************************************************************
**
*******************************************************************************/
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
}

View File

@ -173,21 +173,8 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
*******************************************************************************/
protected QQueryFilter getExistingRecordQueryFilter(RunBackendStepInput runBackendStepInput, List<Serializable> sourceKeyList)
{
String destinationTableForeignKeyFieldName = getSyncProcessConfig().destinationTableForeignKey;
String destinationTableName = getSyncProcessConfig().destinationTable;
QFieldMetaData destinationForeignKeyField = QContext.getQInstance().getTable(destinationTableName).getField(destinationTableForeignKeyFieldName);
List<Serializable> sourceKeysInDestinationKeyTypeList = null;
if(sourceKeyList != null)
{
sourceKeysInDestinationKeyTypeList = new ArrayList<>();
for(Serializable sourceKey : sourceKeyList)
{
sourceKeysInDestinationKeyTypeList.add(ValueUtils.getValueAsFieldType(destinationForeignKeyField.getType(), sourceKey));
}
}
return new QQueryFilter().withCriteria(new QFilterCriteria(destinationTableForeignKeyFieldName, QCriteriaOperator.IN, sourceKeysInDestinationKeyTypeList));
String destinationTableForeignKeyField = getSyncProcessConfig().destinationTableForeignKey;
return new QQueryFilter().withCriteria(new QFilterCriteria(destinationTableForeignKeyField, QCriteriaOperator.IN, sourceKeyList));
}
@ -356,12 +343,12 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
{
if(existingRecord != null)
{
LOG.debug("Skipping storing existing record because this sync process is set to not perform updates");
LOG.info("Skipping storing existing record because this sync process is set to not perform updates");
willNotInsert.incrementCountAndAddPrimaryKey(sourcePrimaryKey);
}
else
{
LOG.debug("Skipping storing new record because this sync process is set to not perform inserts");
LOG.info("Skipping storing new record because this sync process is set to not perform inserts");
willNotUpdate.incrementCountAndAddPrimaryKey(sourcePrimaryKey);
}
continue;

View File

@ -1,343 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.scheduler;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** class to give a human-friendly descriptive string from a cron expression.
** (written in half by my friend Mr. Chatty G)
*******************************************************************************/
public class CronDescriber
{
private static final Map<String, String> DAY_OF_WEEK_MAP = new HashMap<>();
private static final Map<String, String> MONTH_MAP = new HashMap<>();
static
{
DAY_OF_WEEK_MAP.put("1", "Sunday");
DAY_OF_WEEK_MAP.put("2", "Monday");
DAY_OF_WEEK_MAP.put("3", "Tuesday");
DAY_OF_WEEK_MAP.put("4", "Wednesday");
DAY_OF_WEEK_MAP.put("5", "Thursday");
DAY_OF_WEEK_MAP.put("6", "Friday");
DAY_OF_WEEK_MAP.put("7", "Saturday");
////////////////////////////////
// Quartz also allows SUN-SAT //
////////////////////////////////
DAY_OF_WEEK_MAP.put("SUN", "Sunday");
DAY_OF_WEEK_MAP.put("MON", "Monday");
DAY_OF_WEEK_MAP.put("TUE", "Tuesday");
DAY_OF_WEEK_MAP.put("WED", "Wednesday");
DAY_OF_WEEK_MAP.put("THU", "Thursday");
DAY_OF_WEEK_MAP.put("FRI", "Friday");
DAY_OF_WEEK_MAP.put("SAT", "Saturday");
MONTH_MAP.put("1", "January");
MONTH_MAP.put("2", "February");
MONTH_MAP.put("3", "March");
MONTH_MAP.put("4", "April");
MONTH_MAP.put("5", "May");
MONTH_MAP.put("6", "June");
MONTH_MAP.put("7", "July");
MONTH_MAP.put("8", "August");
MONTH_MAP.put("9", "September");
MONTH_MAP.put("10", "October");
MONTH_MAP.put("11", "November");
MONTH_MAP.put("12", "December");
////////////////////////////////
// Quartz also allows JAN-DEC //
////////////////////////////////
MONTH_MAP.put("JAN", "January");
MONTH_MAP.put("FEB", "February");
MONTH_MAP.put("MAR", "March");
MONTH_MAP.put("APR", "April");
MONTH_MAP.put("MAY", "May");
MONTH_MAP.put("JUN", "June");
MONTH_MAP.put("JUL", "July");
MONTH_MAP.put("AUG", "August");
MONTH_MAP.put("SEP", "September");
MONTH_MAP.put("OCT", "October");
MONTH_MAP.put("NOV", "November");
MONTH_MAP.put("DEC", "December");
}
/***************************************************************************
**
***************************************************************************/
public static String getDescription(String cronExpression) throws ParseException
{
String[] parts = cronExpression.trim().toUpperCase().split("\\s+");
if(parts.length < 6 || parts.length > 7)
{
throw new ParseException("Invalid cron expression: " + cronExpression, 0);
}
String seconds = parts[0];
String minutes = parts[1];
String hours = parts[2];
String dayOfMonth = parts[3];
String month = parts[4];
String dayOfWeek = parts[5];
String year = parts.length == 7 ? parts[6] : "*";
StringBuilder description = new StringBuilder();
description.append("At ");
description.append(describeTime(seconds, minutes, hours));
description.append(", on ");
description.append(describeDayOfMonth(dayOfMonth));
description.append(" of ");
description.append(describeMonth(month));
description.append(", ");
description.append(describeDayOfWeek(dayOfWeek));
if(!year.equals("*"))
{
description.append(", in ").append(year);
}
description.append(".");
return description.toString();
}
/***************************************************************************
**
***************************************************************************/
private static String describeTime(String seconds, String minutes, String hours)
{
return String.format("%s, %s, %s", describePart(seconds, "second"), describePart(minutes, "minute"), describePart(hours, "hour"));
}
/***************************************************************************
**
***************************************************************************/
private static String describeDayOfMonth(String dayOfMonth)
{
if(dayOfMonth.equals("?"))
{
return "every day";
}
else if(dayOfMonth.equals("L"))
{
return "the last day";
}
else if(dayOfMonth.contains("W"))
{
return "the nearest weekday to day " + dayOfMonth.replace("W", "");
}
else
{
return (describePart(dayOfMonth, "day"));
}
}
/***************************************************************************
**
***************************************************************************/
private static String describeMonth(String month)
{
if(month.equals("*"))
{
return "every month";
}
else if(month.contains("-"))
{
String[] parts = month.split("-");
return String.format("%s to %s", MONTH_MAP.getOrDefault(parts[0], parts[0]), MONTH_MAP.getOrDefault(parts[1], parts[1]));
}
else
{
String[] months = month.split(",");
List<String> monthNames = Arrays.stream(months).map(m -> MONTH_MAP.getOrDefault(m, m)).toList();
return StringUtils.joinWithCommasAndAnd(monthNames);
}
}
/***************************************************************************
**
***************************************************************************/
private static String describeDayOfWeek(String dayOfWeek)
{
if(dayOfWeek.equals("?") || dayOfWeek.equals("*"))
{
return "every day of the week";
}
else if(dayOfWeek.equals("L"))
{
return "the last day of the week";
}
else if(dayOfWeek.contains("#"))
{
String[] parts = dayOfWeek.split("#");
return String.format("the %s %s of the month", ordinal(parts[1]), DAY_OF_WEEK_MAP.getOrDefault(parts[0], parts[0]));
}
else if(dayOfWeek.contains("-"))
{
String[] parts = dayOfWeek.split("-");
return String.format("from %s to %s", DAY_OF_WEEK_MAP.getOrDefault(parts[0], parts[0]), DAY_OF_WEEK_MAP.getOrDefault(parts[1], parts[1]));
}
else
{
String[] days = dayOfWeek.split(",");
List<String> dayNames = Arrays.stream(days).map(d -> DAY_OF_WEEK_MAP.getOrDefault(d, d)).toList();
return StringUtils.joinWithCommasAndAnd(dayNames);
}
}
/***************************************************************************
**
***************************************************************************/
private static String describePart(String part, String label)
{
if(part.equals("*"))
{
return "every " + label;
}
else if(part.contains("/"))
{
String[] parts = part.split("/");
if(parts[0].equals("*"))
{
parts[0] = "0";
}
return String.format("every %s " + label + "s starting at %s", parts[1], parts[0]);
}
else if(part.contains(","))
{
List<String> partsList = Arrays.stream(part.split(",")).toList();
if(label.equals("hour"))
{
List<String> hourNames = partsList.stream().map(p -> hourToAmPm(p)).toList();
return StringUtils.joinWithCommasAndAnd(hourNames);
}
else
{
if(label.equals("day"))
{
return "days " + StringUtils.joinWithCommasAndAnd(partsList);
}
else
{
return StringUtils.joinWithCommasAndAnd(partsList) + " " + label + "s";
}
}
}
else if(part.contains("-"))
{
String[] parts = part.split("-");
if(label.equals("day"))
{
return String.format("%ss from %s to %s", label, parts[0], parts[1]);
}
else if(label.equals("hour"))
{
return String.format("from %s to %s", hourToAmPm(parts[0]), hourToAmPm(parts[1]));
}
else
{
return String.format("from %s to %s %s", parts[0], parts[1], label + "s");
}
}
else
{
if(label.equals("day"))
{
return label + " " + part;
}
if(label.equals("hour"))
{
return hourToAmPm(part);
}
else
{
return part + " " + label + "s";
}
}
}
/***************************************************************************
**
***************************************************************************/
private static String hourToAmPm(String part)
{
try
{
int hour = Integer.parseInt(part);
return switch(hour)
{
case 0 -> "midnight";
case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 -> hour + " AM";
case 12 -> "noon";
case 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 -> (hour - 12) + " PM";
default -> hour + " hours";
};
}
catch(Exception e)
{
return part + " hours";
}
}
/***************************************************************************
**
***************************************************************************/
private static String ordinal(String number)
{
int n = Integer.parseInt(number);
if(n >= 11 && n <= 13)
{
return n + "th";
}
return switch(n % 10)
{
case 1 -> n + "st";
case 2 -> n + "nd";
case 3 -> n + "rd";
default -> n + "th";
};
}
}

View File

@ -1,90 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.scheduler;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
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.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldDisplayBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Field display behavior, to add a human-redable tooltip to cron-expressions.
*******************************************************************************/
public class CronExpressionTooltipFieldBehavior implements FieldDisplayBehavior<CronExpressionTooltipFieldBehavior>
{
/***************************************************************************
** Add both this behavior, and the tooltip adornment to a field
** Note, if either was already there, then that part is left alone.
***************************************************************************/
public static void addToField(QFieldMetaData fieldMetaData)
{
CronExpressionTooltipFieldBehavior existingBehavior = fieldMetaData.getBehaviorOnlyIfSet(CronExpressionTooltipFieldBehavior.class);
if(existingBehavior == null)
{
fieldMetaData.withBehavior(new CronExpressionTooltipFieldBehavior());
}
if(fieldMetaData.getAdornment(AdornmentType.TOOLTIP).isEmpty())
{
fieldMetaData.withFieldAdornment((new FieldAdornment(AdornmentType.TOOLTIP)
.withValue(AdornmentType.TooltipValues.TOOLTIP_DYNAMIC, true)));
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
for(QRecord record : recordList)
{
try
{
String cronExpression = record.getValueString(field.getName());
if(StringUtils.hasContent(cronExpression))
{
String description = CronDescriber.getDescription(cronExpression);
record.setDisplayValue(field.getName() + ":" + AdornmentType.TooltipValues.TOOLTIP_DYNAMIC, description);
}
}
catch(Exception e)
{
/////////////////////
// just leave null //
/////////////////////
}
}
}
}

View File

@ -475,31 +475,4 @@ public class StringUtils
return (s);
}
/***************************************************************************
**
***************************************************************************/
public static String appendIncrementingSuffix(String input)
{
////////////////////////////////
// remove any existing suffix //
////////////////////////////////
String base = input.replaceAll(" \\(\\d+\\)$", "");
if(input.matches(".* \\(\\d+\\)$"))
{
//////////////////////////
// increment if matches //
//////////////////////////
int current = Integer.parseInt(input.replaceAll(".* \\((\\d+)\\)$", "$1"));
return base + " (" + (current + 1) + ")";
}
else
{
////////////////////////////////////
// no match so put a 1 at the end //
////////////////////////////////////
return base + " (1)";
}
}
}

View File

@ -247,16 +247,6 @@ public class Memoization<K, V>
/*******************************************************************************
**
*******************************************************************************/
public void clearKey(K key)
{
this.map.remove(key);
}
/*******************************************************************************
** Setter for timeoutSeconds
**

View File

@ -18,14 +18,21 @@
</File>
</Appenders>
<Loggers>
<Logger name="org.mongodb.driver" level="WARN" />
<Logger name="org.eclipse.jetty" level="INFO" />
<Logger name="io.javalin" level="INFO" />
<Logger name="org.apache.log4j.xml" additivity="false">
</Logger>
<Logger name="org.mongodb.driver" level="WARN">
</Logger>
<Logger name="org.eclipse.jetty" level="INFO">
</Logger>
<Logger name="io.javalin" level="INFO">
</Logger>
<!-- c3p0 -->
<Logger name="com.mchange.v2" level="INFO" />
<Logger name="org.quartz" level="INFO" />
<Logger name="liquibase" level="INFO" />
<Logger name="com.amazonaws" level="INFO" />
<Logger name="com.mchange.v2" level="INFO">
</Logger>
<Logger name="org.quartz" level="INFO">
</Logger>
<Logger name="liquibase" level="INFO">
</Logger>
<Root level="all">
<AppenderRef ref="SystemOutAppender"/>
<AppenderRef ref="SyslogAppender"/>

View File

@ -22,19 +22,12 @@
package com.kingsrook.qqq.backend.core.actions.customizers;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.Timer;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
@ -87,7 +80,6 @@ class QCodeLoaderTest extends BaseTest
}
/*******************************************************************************
**
*******************************************************************************/
@ -99,50 +91,4 @@ class QCodeLoaderTest extends BaseTest
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testCodeReferenceWithProperties()
{
assertNull(QCodeLoader.getAdHoc(SomeClass.class, new QCodeReference(SomeClass.class)));
SomeClass someObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someValue")));
assertEquals("someValue", someObject.someProperty);
SomeClass someOtherObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someOtherValue")));
assertEquals("someOtherValue", someOtherObject.someProperty);
}
/***************************************************************************
**
***************************************************************************/
public static class SomeClass implements InitializableViaCodeReference
{
private String someProperty;
/***************************************************************************
**
***************************************************************************/
@Override
public void initialize(QCodeReference codeReference)
{
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
{
someProperty = ValueUtils.getValueAsString(codeReferenceWithProperties.getProperties().get("property"));
}
if(!StringUtils.hasContent(someProperty))
{
throw new IllegalStateException("Missing property");
}
}
}
}

View File

@ -1,188 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
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.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for RecordListWidgetRenderer
*******************************************************************************/
class RecordListWidgetRendererTest extends BaseTest
{
/***************************************************************************
**
***************************************************************************/
private QWidgetMetaData defineWidget()
{
return RecordListWidgetRenderer.widgetMetaDataBuilder("testRecordListWidget")
.withTableName(TestUtils.TABLE_NAME_SHAPE)
.withMaxRows(20)
.withLabel("Some Shapes")
.withFilter(new QQueryFilter()
.withCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, "${input.maxShapeId}")
.withCriteria("name", QCriteriaOperator.NOT_EQUALS, "Square")
.withOrderBy(new QFilterOrderBy("id", false))
).getWidgetMetaData();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testValidation() throws QInstanceValidationException
{
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
widgetMetaData.getDefaultValues().remove("tableName");
qInstance.addWidget(widgetMetaData);
assertThatThrownBy(() -> new QInstanceValidator().validate(qInstance))
.isInstanceOf(QInstanceValidationException.class)
.hasMessageContaining("defaultValue for tableName must be given");
}
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
widgetMetaData.getDefaultValues().remove("filter");
qInstance.addWidget(widgetMetaData);
assertThatThrownBy(() -> new QInstanceValidator().validate(qInstance))
.isInstanceOf(QInstanceValidationException.class)
.hasMessageContaining("defaultValue for filter must be given");
}
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
widgetMetaData.getDefaultValues().remove("tableName");
widgetMetaData.getDefaultValues().remove("filter");
qInstance.addWidget(widgetMetaData);
assertThatThrownBy(() -> new QInstanceValidator().validate(qInstance))
.isInstanceOf(QInstanceValidationException.class)
.hasMessageContaining("defaultValue for filter must be given")
.hasMessageContaining("defaultValue for tableName must be given");
}
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
QQueryFilter filter = (QQueryFilter) widgetMetaData.getDefaultValues().get("filter");
filter.addCriteria(new QFilterCriteria("noField", QCriteriaOperator.EQUALS, "noValue"));
qInstance.addWidget(widgetMetaData);
assertThatThrownBy(() -> new QInstanceValidator().validate(qInstance))
.isInstanceOf(QInstanceValidationException.class)
.hasMessageContaining("Criteria fieldName noField is not a field in this table");
}
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
qInstance.addWidget(widgetMetaData);
//////////////////////////////////
// make sure valid setup passes //
//////////////////////////////////
new QInstanceValidator().validate(qInstance);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRender() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widgetMetaData = defineWidget();
qInstance.addWidget(widgetMetaData);
TestUtils.insertDefaultShapes(qInstance);
TestUtils.insertExtraShapes(qInstance);
{
RecordListWidgetRenderer recordListWidgetRenderer = new RecordListWidgetRenderer();
RenderWidgetInput input = new RenderWidgetInput();
input.setWidgetMetaData(widgetMetaData);
input.setQueryParams(Map.of("maxShapeId", "1"));
RenderWidgetOutput output = recordListWidgetRenderer.render(input);
ChildRecordListData widgetData = (ChildRecordListData) output.getWidgetData();
assertEquals(1, widgetData.getTotalRows());
assertEquals(1, widgetData.getQueryOutput().getRecords().get(0).getValue("id"));
assertEquals("Triangle", widgetData.getQueryOutput().getRecords().get(0).getValue("name"));
}
{
RecordListWidgetRenderer recordListWidgetRenderer = new RecordListWidgetRenderer();
RenderWidgetInput input = new RenderWidgetInput();
input.setWidgetMetaData(widgetMetaData);
input.setQueryParams(Map.of("maxShapeId", "4"));
RenderWidgetOutput output = recordListWidgetRenderer.render(input);
ChildRecordListData widgetData = (ChildRecordListData) output.getWidgetData();
assertEquals(3, widgetData.getTotalRows());
/////////////////////////////////////////////////////////////////////////
// id=2,name=Square was skipped due to NOT_EQUALS Square in the filter //
// max-shape-id applied we don't get id=5 or 6 //
// and they're ordered as specified in the filter (id desc) //
/////////////////////////////////////////////////////////////////////////
assertEquals(4, widgetData.getQueryOutput().getRecords().get(0).getValue("id"));
assertEquals("Rectangle", widgetData.getQueryOutput().getRecords().get(0).getValue("name"));
assertEquals(3, widgetData.getQueryOutput().getRecords().get(1).getValue("id"));
assertEquals("Circle", widgetData.getQueryOutput().getRecords().get(1).getValue("name"));
assertEquals(1, widgetData.getQueryOutput().getRecords().get(2).getValue("id"));
assertEquals("Triangle", widgetData.getQueryOutput().getRecords().get(2).getValue("name"));
}
}
}

View File

@ -364,7 +364,6 @@ class MetaDataActionTest extends BaseTest
**
*******************************************************************************/
@Test
@Deprecated(since = "migrated to metaDataCustomizer")
void testFilter() throws QException
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
@ -398,7 +397,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("actionCustomizer (via metaDataFilter reference) of type: DenyAllFilter")).hasSize(1);
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("filter of type: DenyAllFilter")).hasSize(1);
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
@ -414,59 +413,6 @@ 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");
}
/***************************************************************************
**
***************************************************************************/
@ -516,67 +462,6 @@ 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;
}
/***************************************************************************
**
***************************************************************************/

View File

@ -38,7 +38,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.TestExcelStyler;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
@ -57,7 +56,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
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.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
@ -492,34 +490,6 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void runXlsxWithStyleCustomizer() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-customized.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(defineTableOnlyReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
reportInput.setExportStyleCustomizer(new QCodeReference(TestExcelStyler.class));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -1,76 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting.excel;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingStyleCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/*******************************************************************************
**
*******************************************************************************/
public class TestExcelStyler implements ExcelPoiBasedStreamingStyleCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<Integer> getColumnWidthsForView(QReportView view)
{
return List.of(60, 50, 40);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<String> getMergedRangesForView(QReportView view)
{
return List.of("A1:B1");
}
/***************************************************************************
**
***************************************************************************/
@Override
public void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
{
Font font = workbook.createFont();
font.setFontHeightInPoints((short) 16);
font.setBold(true);
XSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFont(font);
styles.put("header", cellStyle);
}
}

View File

@ -23,14 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -54,18 +50,4 @@ class CountActionTest extends BaseTest
CountOutput result = new CountAction().execute(request);
assertNotNull(result);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testStaticWrapper() throws QException
{
TestUtils.insertDefaultShapes(QContext.getQInstance());
assertEquals(3, CountAction.execute(TestUtils.TABLE_NAME_SHAPE, null));
assertEquals(3, CountAction.execute(TestUtils.TABLE_NAME_SHAPE, new QQueryFilter()));
}
}

View File

@ -30,23 +30,19 @@ import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.context.QContext;
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.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DateTimeDisplayValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@ -241,102 +237,4 @@ class QValueFormatterTest extends BaseTest
assertEquals("2024-04-04 02:12:00 PM CDT", record.getDisplayValue("createDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testBlobValuesToDownloadUrls()
{
byte[] blobBytes = "hello".getBytes();
{
QTableMetaData table = new QTableMetaData()
.withName("testTable")
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("blobField", QFieldType.BLOB)
.withFieldAdornment(new FieldAdornment().withType(AdornmentType.FILE_DOWNLOAD)
.withValue(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT, "blob-%s.txt")
.withValue(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS, new ArrayList<>(List.of("id")))));
//////////////////////////////////////////////////////////////////
// verify display value gets set to formated file-name + fields //
// and raw value becomes URL for downloading the byte //
//////////////////////////////////////////////////////////////////
QRecord record = new QRecord().withValue("id", 47).withValue("blobField", blobBytes);
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record));
assertEquals("/data/testTable/47/blobField/blob-47.txt", record.getValueString("blobField"));
assertEquals("blob-47.txt", record.getDisplayValue("blobField"));
////////////////////////////////////////////////////////
// verify that w/ no blob value, we don't do anything //
////////////////////////////////////////////////////////
QRecord recordWithoutBlobValue = new QRecord().withValue("id", 47);
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(recordWithoutBlobValue));
assertNull(recordWithoutBlobValue.getValue("blobField"));
assertNull(recordWithoutBlobValue.getDisplayValue("blobField"));
}
{
FieldAdornment adornment = new FieldAdornment().withType(AdornmentType.FILE_DOWNLOAD)
.withValue(AdornmentType.FileDownloadValues.FILE_NAME_FIELD, "fileName");
QTableMetaData table = new QTableMetaData()
.withName("testTable")
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("fileName", QFieldType.STRING))
.withField(new QFieldMetaData("blobField", QFieldType.BLOB)
.withFieldAdornment(adornment));
////////////////////////////////////////////////////
// here get the file name directly from one field //
////////////////////////////////////////////////////
QRecord record = new QRecord().withValue("id", 47).withValue("blobField", blobBytes).withValue("fileName", "myBlob.txt");
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record));
assertEquals("/data/testTable/47/blobField/myBlob.txt", record.getValueString("blobField"));
assertEquals("myBlob.txt", record.getDisplayValue("blobField"));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// switch to use dynamic url, rerun, and assert we get the values as they were on the record before the call //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
adornment.withValue(AdornmentType.FileDownloadValues.DOWNLOAD_URL_DYNAMIC, true);
record = new QRecord().withValue("id", 47).withValue("blobField", blobBytes).withValue("fileName", "myBlob.txt")
.withDisplayValue("blobField:" + AdornmentType.FileDownloadValues.DOWNLOAD_URL_DYNAMIC, "/something-custom/")
.withDisplayValue("blobField", "myDisplayValue");
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record));
assertArrayEquals(blobBytes, record.getValueByteArray("blobField"));
assertEquals("myDisplayValue", record.getDisplayValue("blobField"));
}
{
FieldAdornment adornment = new FieldAdornment().withType(AdornmentType.FILE_DOWNLOAD);
QTableMetaData table = new QTableMetaData()
.withName("testTable")
.withLabel("Test Table")
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("blobField", QFieldType.BLOB).withLabel("Blob").withFieldAdornment(adornment));
///////////////////////////////////////////////////////////////////////////////////////////
// w/o file name format or whatever, generate a file name from table & id & field labels //
///////////////////////////////////////////////////////////////////////////////////////////
QRecord record = new QRecord().withValue("id", 47).withValue("blobField", blobBytes);
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record));
assertEquals("/data/testTable/47/blobField/Test%20Table%2047%20Blob", record.getValueString("blobField"));
assertEquals("Test Table 47 Blob", record.getDisplayValue("blobField"));
////////////////////////////////////////
// add a default extension and re-run //
////////////////////////////////////////
adornment.withValue(AdornmentType.FileDownloadValues.DEFAULT_EXTENSION, "html");
record = new QRecord().withValue("id", 47).withValue("blobField", blobBytes);
QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record));
assertEquals("/data/testTable/47/blobField/Test%20Table%2047%20Blob.html", record.getValueString("blobField"));
assertEquals("Test Table 47 Blob.html", record.getDisplayValue("blobField"));
}
}
}

View File

@ -27,8 +27,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.enrichment.testplugins.TestEnricherPlugin;
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
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.fields.AdornmentType;
@ -596,31 +595,27 @@ class QInstanceEnricherTest extends BaseTest
{
QInstance qInstance = TestUtils.defineInstance();
QInstanceEnricher.addEnricherPlugin(new TestEnricherPlugin());
QInstanceEnricher.addEnricherPlugin(new QInstanceEnricherPluginInterface<QFieldMetaData>()
{
/***************************************************************************
*
***************************************************************************/
@Override
public void enrich(QFieldMetaData field, QInstance qInstance)
{
if(field != null)
{
field.setLabel(field.getLabel() + " Plugged");
}
}
});
new QInstanceEnricher(qInstance).enrich();
qInstance.getTables().values().forEach(table -> table.getFields().values().forEach(field -> assertThat(field.getLabel()).endsWith("Plugged")));
qInstance.getProcesses().values().forEach(process -> process.getInputFields().forEach(field -> assertThat(field.getLabel()).endsWith("Plugged")));
qInstance.getProcesses().values().forEach(process -> process.getOutputFields().forEach(field -> assertThat(field.getLabel()).endsWith("Plugged")));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDiscoverAndAddPlugins() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
new QInstanceEnricher(qInstance).enrich();
qInstance.getTables().values().forEach(table -> table.getFields().values().forEach(field -> assertThat(field.getLabel()).doesNotEndWith("Plugged")));
qInstance = TestUtils.defineInstance();
QInstanceEnricher.discoverAndAddPluginsInPackage(getClass().getPackageName() + ".enrichment.testplugins");
new QInstanceEnricher(qInstance).enrich();
qInstance.getTables().values().forEach(table -> table.getFields().values().forEach(field -> assertThat(field.getLabel()).endsWith("Plugged")));
}
}

View File

@ -40,7 +40,6 @@ 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;
@ -161,20 +160,6 @@ 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.

View File

@ -1,48 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.enrichment.testplugins;
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class TestEnricherPlugin implements QInstanceEnricherPluginInterface<QFieldMetaData>
{
/***************************************************************************
**
***************************************************************************/
@Override
public void enrich(QFieldMetaData field, QInstance qInstance)
{
if(field != null)
{
field.setLabel(field.getLabel() + " Plugged");
}
}
}

View File

@ -66,7 +66,7 @@ class NowWithOffsetTest extends BaseTest
assertThat(twoWeeksFromNowMillis).isCloseTo(now + (14 * DAY_IN_MILLIS), allowedDiff);
long oneMonthAgoMillis = ((Instant) NowWithOffset.minus(1, ChronoUnit.MONTHS).evaluate(dateTimeField)).toEpochMilli();
assertThat(oneMonthAgoMillis).isCloseTo(now - (30 * DAY_IN_MILLIS), allowedDiffPlusTwoDays); // two days, to work on 3/1...
assertThat(oneMonthAgoMillis).isCloseTo(now - (30 * DAY_IN_MILLIS), allowedDiffPlusOneDay);
long twoMonthsFromNowMillis = ((Instant) NowWithOffset.plus(2, ChronoUnit.MONTHS).evaluate(dateTimeField)).toEpochMilli();
assertThat(twoMonthsFromNowMillis).isCloseTo(now + (60 * DAY_IN_MILLIS), allowedDiffPlusTwoDays);

View File

@ -41,7 +41,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -567,22 +566,4 @@ class QRecordEntityTest extends BaseTest
assertEquals(0, order.getLineItems().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableName() throws QException
{
assertEquals(Item.TABLE_NAME, QRecordEntity.getTableName(Item.class));
assertEquals(Item.TABLE_NAME, Item.getTableName(Item.class));
assertEquals(Item.TABLE_NAME, new Item().tableName());
//////////////////////////////////
// no TABLE_NAME in Order class //
//////////////////////////////////
assertThatThrownBy(() -> Order.getTableName(Order.class));
}
}

View File

@ -1,151 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
import java.util.LinkedHashMap;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.testqbit.TestQBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.testqbit.TestQBitProducer;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.testqbit.metadata.OtherTableMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.testqbit.metadata.SomeTableMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for QBitProducer
*******************************************************************************/
class QBitProducerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
TestQBitConfig config = new TestQBitConfig()
.withOtherTableConfig(ProvidedOrSuppliedTableConfig.provideTableUsingBackendNamed(TestUtils.MEMORY_BACKEND_NAME))
.withIsSomeTableEnabled(true)
.withSomeSetting("yes")
.withTableMetaDataCustomizer((i, table) ->
{
if(table.getBackendName() == null)
{
table.setBackendName(TestUtils.DEFAULT_BACKEND_NAME);
}
table.addField(new QFieldMetaData("custom", QFieldType.STRING));
return (table);
});
QInstance qInstance = QContext.getQInstance();
new TestQBitProducer().withTestQBitConfig(config).produce(qInstance);
///////////////////////////////////////////////////////////////////////////////////////////////////////
// OtherTable should have been provided by the qbit, with the backend name we told it above (MEMORY) //
///////////////////////////////////////////////////////////////////////////////////////////////////////
QTableMetaData otherTable = qInstance.getTable(OtherTableMetaDataProducer.NAME);
assertNotNull(otherTable);
assertEquals(TestUtils.MEMORY_BACKEND_NAME, otherTable.getBackendName());
assertNotNull(otherTable.getField("custom"));
QBitMetaData sourceQBit = otherTable.getSourceQBit();
assertEquals("testQBit", sourceQBit.getArtifactId());
////////////////////////////////////////////////////////////////////////////////
// SomeTable should have been provided, w/ backend name set by the customizer //
////////////////////////////////////////////////////////////////////////////////
QTableMetaData someTable = qInstance.getTable(SomeTableMetaDataProducer.NAME);
assertNotNull(someTable);
assertEquals(TestUtils.DEFAULT_BACKEND_NAME, someTable.getBackendName());
assertNotNull(otherTable.getField("custom"));
TestQBitConfig qBitConfig = (TestQBitConfig) someTable.getSourceQBitConfig();
assertEquals("yes", qBitConfig.getSomeSetting());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDisableThings() throws QException
{
TestQBitConfig config = new TestQBitConfig()
.withOtherTableConfig(ProvidedOrSuppliedTableConfig.useSuppliedTaleNamed(TestUtils.TABLE_NAME_PERSON_MEMORY))
.withIsSomeTableEnabled(false);
QInstance qInstance = QContext.getQInstance();
new TestQBitProducer().withTestQBitConfig(config).produce(qInstance);
//////////////////////////////////////
// neither table should be produced //
//////////////////////////////////////
QTableMetaData otherTable = qInstance.getTable(OtherTableMetaDataProducer.NAME);
assertNull(otherTable);
QTableMetaData someTable = qInstance.getTable(SomeTableMetaDataProducer.NAME);
assertNull(someTable);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testValidationErrors() throws QException
{
QInstance qInstance = QContext.getQInstance();
TestQBitConfig config = new TestQBitConfig();
assertThatThrownBy(() -> new TestQBitProducer().withTestQBitConfig(config).produce(qInstance))
.isInstanceOf(QBitConfigValidationException.class)
.hasMessageContaining("otherTableConfig must be set")
.hasMessageContaining("isSomeTableEnabled must be set");
qInstance.setQBits(new LinkedHashMap<>());
config.setIsSomeTableEnabled(true);
assertThatThrownBy(() -> new TestQBitProducer().withTestQBitConfig(config).produce(qInstance))
.isInstanceOf(QBitConfigValidationException.class)
.hasMessageContaining("otherTableConfig must be set");
qInstance.setQBits(new LinkedHashMap<>());
config.setOtherTableConfig(ProvidedOrSuppliedTableConfig.useSuppliedTaleNamed(TestUtils.TABLE_NAME_PERSON_MEMORY));
new TestQBitProducer().withTestQBitConfig(config).produce(qInstance);
}
}

Some files were not shown because too many files have changed in this diff Show More