Merge pull request #23 from Kingsrook/dev

refresh querystats from dev
This commit is contained in:
2023-06-22 19:07:15 -05:00
committed by GitHub
87 changed files with 6582 additions and 224 deletions

View File

@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.logging.log4j.Level;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -50,6 +51,7 @@ public class AsyncJobManager
{
private static final QLogger LOG = QLogger.getLogger(AsyncJobManager.class);
private String forcedJobUUID = null;
/*******************************************************************************
@ -69,7 +71,8 @@ public class AsyncJobManager
*******************************************************************************/
public <T extends Serializable> T startJob(String jobName, long timeout, TimeUnit timeUnit, AsyncJob<T> asyncJob) throws JobGoingAsyncException, QException
{
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.ASYNC_JOB_STATUS);
UUID jobUUID = StringUtils.hasContent(forcedJobUUID) ? UUID.fromString(forcedJobUUID) : UUID.randomUUID();
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(jobUUID, StateType.ASYNC_JOB_STATUS);
AsyncJobStatus asyncJobStatus = new AsyncJobStatus();
asyncJobStatus.setState(AsyncJobState.RUNNING);
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
@ -205,4 +208,35 @@ public class AsyncJobManager
jobStatus.ifPresent(asyncJobStatus -> asyncJobStatus.setCancelRequested(true));
}
/*******************************************************************************
** Getter for forcedJobUUID
*******************************************************************************/
public String getForcedJobUUID()
{
return (this.forcedJobUUID);
}
/*******************************************************************************
** Setter for forcedJobUUID
*******************************************************************************/
public void setForcedJobUUID(String forcedJobUUID)
{
this.forcedJobUUID = forcedJobUUID;
}
/*******************************************************************************
** Fluent setter for forcedJobUUID
*******************************************************************************/
public AsyncJobManager withForcedJobUUID(String forcedJobUUID)
{
this.forcedJobUUID = forcedJobUUID;
return (this);
}
}

View File

@ -76,6 +76,21 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
/*******************************************************************************
** Execute to insert 1 audit, with a list of detail child records provided as just string messages
*******************************************************************************/
public static void executeWithStringDetails(String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message, List<String> detailMessages)
{
List<QRecord> detailRecords = null;
if(CollectionUtils.nullSafeHasContents(detailMessages))
{
detailRecords = detailMessages.stream().map(m -> new QRecord().withValue("message", m)).toList();
}
execute(tableName, recordId, securityKeyValues, message, detailRecords);
}
/*******************************************************************************
** Execute to insert 1 audit, with a list of detail child records
*******************************************************************************/

View File

@ -39,12 +39,15 @@ import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
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;
@ -61,11 +64,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
import com.kingsrook.qqq.backend.core.model.session.QSession;
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.collections.MapBuilder;
import org.apache.commons.lang.NotImplementedException;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -270,16 +276,39 @@ public class PollingAutomationPerTableRunner implements Runnable
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord record : queryOutput.getRecords())
{
// todo - get filter if there is/was one
rs.add(new TableAutomationAction()
.withName("Script:" + record.getValue("scriptId"))
.withFilter(null)
.withTriggerEvent(triggerEvent)
.withPriority(record.getValueInteger("priority"))
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
.withIncludeRecordAssociations(true)
);
TableTrigger tableTrigger = new TableTrigger(record);
try
{
QQueryFilter filter = null;
Integer filterId = tableTrigger.getFilterId();
if(filterId != null)
{
GetInput getInput = new GetInput();
getInput.setTableName(SavedFilter.TABLE_NAME);
getInput.setPrimaryKey(filterId);
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() != null)
{
SavedFilter savedFilter = new SavedFilter(getOutput.getRecord());
filter = JsonUtils.toObject(savedFilter.getFilterJson(), QQueryFilter.class);
}
}
rs.add(new TableAutomationAction()
.withName("Script:" + tableTrigger.getScriptId())
.withFilter(filter)
.withTriggerEvent(triggerEvent)
.withPriority(tableTrigger.getPriority())
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
.withIncludeRecordAssociations(true)
);
}
catch(Exception e)
{
LOG.error("Error setting up table trigger", e, logPair("tableTriggerId", tableTrigger.getId()));
}
}
}

View File

@ -101,6 +101,17 @@ public class ExecuteCodeAction
context.putAll(input.getInput());
}
/////////////////////////////////////////////////////////////////////////////////
// set the qCodeExecutor into any context objects which are QCodeExecutorAware //
/////////////////////////////////////////////////////////////////////////////////
for(Serializable value : context.values())
{
if(value instanceof QCodeExecutorAware qCodeExecutorAware)
{
qCodeExecutorAware.setQCodeExecutor(qCodeExecutor);
}
}
Serializable codeOutput = qCodeExecutor.execute(codeReference, context, executionLogger);
output.setOutput(codeOutput);
executionLogger.acceptExecutionEnd(codeOutput);

View File

@ -41,4 +41,14 @@ public interface QCodeExecutor
*******************************************************************************/
Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException;
/*******************************************************************************
** Process an object from the script's language/runtime into a (more) native java object.
** e.g., a Nashorn ScriptObjectMirror will end up as a "primitive", or a List or Map of such
**
*******************************************************************************/
default Object convertObjectToJava(Object object)
{
return (object);
}
}

View File

@ -0,0 +1,36 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.scripts;
/*******************************************************************************
** Interface for classes that can accept a QCodeExecutor object via a setter.
*******************************************************************************/
public interface QCodeExecutorAware
{
/*******************************************************************************
**
*******************************************************************************/
void setQCodeExecutor(QCodeExecutor qCodeExecutor);
}

View File

@ -136,7 +136,7 @@ public class QValueFormatter
{
return formatValue(displayFormat, ValueUtils.getValueAsBigDecimal(value));
}
else if(e.getMessage().equals("d != java.math.BigDecimal"))
else if(e.getMessage().equals("d != java.math.BigDecimal") || e.getMessage().equals("d != java.lang.String"))
{
return formatValue(displayFormat, ValueUtils.getValueAsInteger(value));
}

View File

@ -22,12 +22,19 @@
package com.kingsrook.qqq.backend.core.exceptions;
import org.apache.logging.log4j.Level;
/*******************************************************************************
* Base class for checked exceptions thrown in qqq.
*
*******************************************************************************/
public class QException extends Exception
{
private boolean hasLoggedWarning;
private boolean hasLoggedError;
/*******************************************************************************
** Constructor of message
@ -59,4 +66,102 @@ public class QException extends Exception
{
super(message, cause);
}
/*******************************************************************************
** Getter for hasLoggedWarning
*******************************************************************************/
public boolean getHasLoggedWarning()
{
return (this.hasLoggedWarning);
}
/*******************************************************************************
** Setter for hasLoggedWarning
*******************************************************************************/
public void setHasLoggedWarning(boolean hasLoggedWarning)
{
this.hasLoggedWarning = hasLoggedWarning;
}
/*******************************************************************************
** Fluent setter for hasLoggedWarning
*******************************************************************************/
public QException withHasLoggedWarning(boolean hasLoggedWarning)
{
this.hasLoggedWarning = hasLoggedWarning;
return (this);
}
/*******************************************************************************
** Getter for hasLoggedError
*******************************************************************************/
public boolean getHasLoggedError()
{
return (this.hasLoggedError);
}
/*******************************************************************************
** Setter for hasLoggedError
*******************************************************************************/
public void setHasLoggedError(boolean hasLoggedError)
{
this.hasLoggedError = hasLoggedError;
}
/*******************************************************************************
** Fluent setter for hasLoggedError
*******************************************************************************/
public QException withHasLoggedError(boolean hasLoggedError)
{
this.hasLoggedError = hasLoggedError;
return (this);
}
/*******************************************************************************
** helper function for getting if level logged
*******************************************************************************/
public boolean hasLoggedLevel(Level level)
{
if(Level.WARN.equals(level))
{
return (hasLoggedWarning);
}
if(Level.ERROR.equals(level))
{
return (hasLoggedError);
}
return (false);
}
/*******************************************************************************
** helper function for setting if level logged
*******************************************************************************/
public void setHasLoggedLevel(Level level)
{
if(Level.WARN.equals(level))
{
setHasLoggedWarning(true);
}
if(Level.ERROR.equals(level))
{
setHasLoggedError(true);
}
}
}

View File

@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -57,12 +58,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponen
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
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.processes.QSupplementalProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteLoadStep;
@ -96,6 +98,13 @@ public class QInstanceEnricher
//////////////////////////////////////////////////////////
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
//////////////////////////////////////////////////////////////////////////////////////////////////
// let an instance define mappings to be applied during name-to-label enrichments, //
// e.g., to avoid ever incorrectly camel-casing an acronym (e.g., "Tla" shoudl always be "TLA") //
// or to expand abbreviations in code (e.g., "Addr" should always be "Address" //
//////////////////////////////////////////////////////////////////////////////////////////////////
private static final Map<String, String> labelMappings = new LinkedHashMap<>();
/*******************************************************************************
@ -261,9 +270,9 @@ public class QInstanceEnricher
{
table.getFields().values().forEach(this::enrichField);
for(QMiddlewareTableMetaData middlewareTableMetaData : CollectionUtils.nonNullMap(table.getMiddlewareMetaData()).values())
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
{
middlewareTableMetaData.enrich(table);
supplementalTableMetaData.enrich(table);
}
}
@ -367,6 +376,11 @@ public class QInstanceEnricher
process.getStepList().forEach(this::enrichStep);
}
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
{
supplementalProcessMetaData.enrich(this, process);
}
enrichPermissionRules(process);
}
@ -641,7 +655,17 @@ public class QInstanceEnricher
////////////////////////////////////////////////////////////////
.replaceAll("([0-9])([A-Za-z])", "$1 $2");
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix);
String label = name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix;
/////////////////////////////////////////////////////////////////////////////////////////////
// apply any label mappings - e.g., to force app-specific acronyms/initialisms to all-caps //
/////////////////////////////////////////////////////////////////////////////////////////////
for(Map.Entry<String, String> entry : labelMappings.entrySet())
{
label = label.replaceAll(entry.getKey(), entry.getValue());
}
return (label);
}
@ -1105,4 +1129,35 @@ public class QInstanceEnricher
{
return (this.joinGraph);
}
/*******************************************************************************
**
*******************************************************************************/
public static void addLabelMapping(String from, String to)
{
labelMappings.put(from, to);
}
/*******************************************************************************
**
*******************************************************************************/
public static void removeLabelMapping(String from)
{
labelMappings.remove(from);
}
/*******************************************************************************
**
*******************************************************************************/
public static void clearLabelMappings()
{
labelMappings.clear();
}
}

View File

@ -48,7 +48,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
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.fields.AdornmentType;
@ -63,6 +63,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
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.processes.QSupplementalProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
@ -158,7 +159,7 @@ public class QInstanceValidator
validateQueuesAndProviders(qInstance);
validateJoins(qInstance);
validateSecurityKeyTypes(qInstance);
validateMiddlewareMetaData(qInstance);
validateSupplementalMetaData(qInstance);
validateUniqueTopLevelNames(qInstance);
}
@ -182,11 +183,11 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateMiddlewareMetaData(QInstance qInstance)
private void validateSupplementalMetaData(QInstance qInstance)
{
for(QMiddlewareInstanceMetaData middlewareInstanceMetaData : CollectionUtils.nonNullMap(qInstance.getMiddlewareMetaData()).values())
for(QSupplementalInstanceMetaData supplementalInstanceMetaData : CollectionUtils.nonNullMap(qInstance.getSupplementalMetaData()).values())
{
middlewareInstanceMetaData.validate(qInstance, this);
supplementalInstanceMetaData.validate(qInstance, this);
}
}
@ -572,6 +573,11 @@ public class QInstanceValidator
RECORD_SECURITY_LOCKS_LOOP:
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
{
if(!assertCondition(recordSecurityLock != null, prefix + "has a null recordSecurityLock (did you mean to give it a null list of locks?)"))
{
continue;
}
String securityKeyTypeName = recordSecurityLock.getSecurityKeyType();
if(assertCondition(StringUtils.hasContent(securityKeyTypeName), prefix + "has a recordSecurityLock that is missing a securityKeyType"))
{
@ -1226,6 +1232,11 @@ public class QInstanceValidator
}
}
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
{
supplementalProcessMetaData.validate(qInstance, process, this);
}
});
}
}
@ -1703,7 +1714,7 @@ public class QInstanceValidator
** But if it throws, add the provided message to the list of errors (and return false,
** e.g., in case you need to stop evaluating rules to avoid exceptions).
*******************************************************************************/
private boolean assertNoException(UnsafeLambda unsafeLambda, String message)
public boolean assertNoException(UnsafeLambda unsafeLambda, String message)
{
try
{
@ -1736,7 +1747,7 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void warn(String message)
public void warn(String message)
{
if(printWarnings)
{

View File

@ -29,10 +29,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -392,7 +394,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message, Throwable t)
{
logger.warn(makeJsonString(message, t));
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t));
}
@ -402,7 +404,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message, Throwable t, LogPair... logPairs)
{
logger.warn(makeJsonString(message, t, logPairs));
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t, logPairs));
}
@ -412,7 +414,7 @@ public class QLogger
*******************************************************************************/
public void warn(Throwable t)
{
logger.warn(makeJsonString(null, t));
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(null, t));
}
@ -452,7 +454,7 @@ public class QLogger
*******************************************************************************/
public void error(String message, Throwable t)
{
logger.error(makeJsonString(message, t));
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t));
}
@ -462,7 +464,7 @@ public class QLogger
*******************************************************************************/
public void error(String message, Throwable t, LogPair... logPairs)
{
logger.error(makeJsonString(message, t, logPairs));
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t, logPairs));
}
@ -472,7 +474,7 @@ public class QLogger
*******************************************************************************/
public void error(Throwable t)
{
logger.error(makeJsonString(null, t));
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(null, t));
}
@ -532,7 +534,7 @@ public class QLogger
if(t != null)
{
logPairList.add(logPair("stackTrace", LogUtils.filterStackTrace(ExceptionUtils.getStackTrace(t))));
logPairList.add(logPair("stackTrace", LogUtils.filterStackTrace(org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace(t))));
}
return (LogUtils.jsonLog(logPairList));
@ -582,4 +584,40 @@ public class QLogger
}
}
}
/*******************************************************************************
**
*******************************************************************************/
protected Level determineIfShouldDowngrade(Throwable t, Level level)
{
//////////////////////////////////////////////////////////////////////////////////////
// look for QExceptions in the chain, if none found, return the log level passed in //
//////////////////////////////////////////////////////////////////////////////////////
List<QException> exceptionList = ExceptionUtils.getClassListFromRootChain(t, QException.class);
if(CollectionUtils.nullSafeIsEmpty(exceptionList))
{
return (level);
}
////////////////////////////////////////////////////////////////////
// check if any QException in this chain to see if it has already //
// logged this level, if so, downgrade to INFO //
////////////////////////////////////////////////////////////////////
for(QException qException : exceptionList)
{
if(qException.hasLoggedLevel(level))
{
log(Level.DEBUG, "Downgrading log message from " + level.toString() + " to " + Level.INFO, t);
return (Level.INFO);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// if it has not logged at this level, set that it has in QException, and return passed in level //
///////////////////////////////////////////////////////////////////////////////////////////////////
exceptionList.get(0).setHasLoggedLevel(level);
return (level);
}
}

View File

@ -22,8 +22,10 @@
package com.kingsrook.qqq.backend.core.model.actions.processes;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -76,6 +78,35 @@ public class ProcessSummaryFilterLink implements ProcessSummaryLineInterface
/*******************************************************************************
**
*******************************************************************************/
@JsonIgnore
public String getFullText()
{
StringBuilder rs = new StringBuilder();
if(StringUtils.hasContent(linkPreText))
{
rs.append(linkPreText).append(" ");
}
if(StringUtils.hasContent(linkText))
{
rs.append(linkText).append(" ");
}
if(StringUtils.hasContent(linkPostText))
{
rs.append(linkPostText).append(" ");
}
rs.deleteCharAt(rs.length() - 1);
return (rs.toString());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -395,15 +396,19 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
{
if(count != null)
{
String baseMessage;
if(count.equals(1))
{
setMessage((isPast ? getSingularPastMessage() : getSingularFutureMessage())
+ (messageSuffix == null ? "" : messageSuffix));
baseMessage = isPast ? getSingularPastMessage() : getSingularFutureMessage();
}
else
{
setMessage((isPast ? getPluralPastMessage() : getPluralFutureMessage())
+ (messageSuffix == null ? "" : messageSuffix));
baseMessage = isPast ? getPluralPastMessage() : getPluralFutureMessage();
}
if(StringUtils.hasContent(baseMessage))
{
setMessage(baseMessage + ObjectUtils.requireConditionElse(messageSuffix, StringUtils::hasContent, ""));
}
}
}

View File

@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -64,6 +66,35 @@ public class ProcessSummaryRecordLink implements ProcessSummaryLineInterface
/*******************************************************************************
**
*******************************************************************************/
@JsonIgnore
public String getFullText()
{
StringBuilder rs = new StringBuilder();
if(StringUtils.hasContent(linkPreText))
{
rs.append(linkPreText).append(" ");
}
if(StringUtils.hasContent(linkText))
{
rs.append(linkText).append(" ");
}
if(StringUtils.hasContent(linkPostText))
{
rs.append(linkPostText).append(" ");
}
rs.deleteCharAt(rs.length() - 1);
return (rs.toString());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -23,6 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
@ -31,6 +35,7 @@ import com.kingsrook.qqq.backend.core.context.QContext;
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.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
@ -194,6 +199,17 @@ public class RunProcessInput extends AbstractActionInput
/*******************************************************************************
**
*******************************************************************************/
public RunProcessInput withValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Setter for values
**
@ -269,7 +285,7 @@ public class RunProcessInput extends AbstractActionInput
*******************************************************************************/
public String getValueString(String fieldName)
{
return ((String) getValue(fieldName));
return (ValueUtils.getValueAsString(getValue(fieldName)));
}
@ -280,7 +296,67 @@ public class RunProcessInput extends AbstractActionInput
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return ((Integer) getValue(fieldName));
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getValueBigDecimal(String fieldName)
{
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Boolean getValueBoolean(String fieldName)
{
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public LocalTime getValueLocalTime(String fieldName)
{
return (ValueUtils.getValueAsLocalTime(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public LocalDate getValueLocalDate(String fieldName)
{
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public byte[] getValueByteArray(String fieldName)
{
return (ValueUtils.getValueAsByteArray(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Instant getValueInstant(String fieldName)
{
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
}

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
@ -122,6 +127,99 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (this.processState.getValues().get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return (ValueUtils.getValueAsString(getValue(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getValueBigDecimal(String fieldName)
{
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Boolean getValueBoolean(String fieldName)
{
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public LocalTime getValueLocalTime(String fieldName)
{
return (ValueUtils.getValueAsLocalTime(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public LocalDate getValueLocalDate(String fieldName)
{
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public byte[] getValueByteArray(String fieldName)
{
return (ValueUtils.getValueAsByteArray(getValue(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Instant getValueInstant(String fieldName)
{
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
}
/*******************************************************************************
** Setter for values
**
@ -133,6 +231,17 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
/*******************************************************************************
**
*******************************************************************************/
public RunProcessOutput withValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Setter for values
**

View File

@ -56,4 +56,14 @@ public enum AggregateOperator
{
return sqlPrefix;
}
/*******************************************************************************
**
*******************************************************************************/
public Aggregate of(String fieldName)
{
return (new Aggregate(fieldName, this));
}
}

View File

@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
@ -50,7 +51,7 @@ public class TableTrigger extends QRecordEntity
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
private String tableName;
@QField(/* todo possibleValueSourceName = */)
@QField(possibleValueSourceName = SavedFilter.TABLE_NAME)
private Integer filterId;
@QField(possibleValueSourceName = Script.TABLE_NAME)

View File

@ -91,7 +91,7 @@ public class QInstance
private Map<String, QQueueProviderMetaData> queueProviders = new LinkedHashMap<>();
private Map<String, QQueueMetaData> queues = new LinkedHashMap<>();
private Map<String, QMiddlewareInstanceMetaData> middlewareMetaData = new LinkedHashMap<>();
private Map<String, QSupplementalInstanceMetaData> supplementalMetaData = new LinkedHashMap<>();
private Map<String, String> environmentValues = new LinkedHashMap<>();
private String defaultTimeZoneId = "UTC";
@ -1083,60 +1083,60 @@ public class QInstance
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public Map<String, QMiddlewareInstanceMetaData> getMiddlewareMetaData()
public Map<String, QSupplementalInstanceMetaData> getSupplementalMetaData()
{
return (this.middlewareMetaData);
return (this.supplementalMetaData);
}
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public QMiddlewareInstanceMetaData getMiddlewareMetaData(String type)
public QSupplementalInstanceMetaData getSupplementalMetaData(String type)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
return (null);
}
return this.middlewareMetaData.get(type);
return this.supplementalMetaData.get(type);
}
/*******************************************************************************
** Setter for middlewareMetaData
** Setter for supplementalMetaData
*******************************************************************************/
public void setMiddlewareMetaData(Map<String, QMiddlewareInstanceMetaData> middlewareMetaData)
public void setSupplementalMetaData(Map<String, QSupplementalInstanceMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QInstance withMiddlewareMetaData(Map<String, QMiddlewareInstanceMetaData> middlewareMetaData)
public QInstance withSupplementalMetaData(Map<String, QSupplementalInstanceMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
return (this);
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QInstance withMiddlewareMetaData(QMiddlewareInstanceMetaData middlewareMetaData)
public QInstance withSupplementalMetaData(QSupplementalInstanceMetaData supplementalMetaData)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
this.middlewareMetaData = new HashMap<>();
this.supplementalMetaData = new HashMap<>();
}
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
return (this);
}

View File

@ -27,9 +27,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Base-class for instance-level meta-data defined for a specific middleware.
** Base-class for instance-level meta-data defined by some supplemental module, etc,
** outside of qqq core
*******************************************************************************/
public abstract class QMiddlewareInstanceMetaData
public abstract class QSupplementalInstanceMetaData
{
protected String type;
@ -58,7 +59,7 @@ public abstract class QMiddlewareInstanceMetaData
/*******************************************************************************
** Fluent setter for type
*******************************************************************************/
public QMiddlewareInstanceMetaData withType(String type)
public QSupplementalInstanceMetaData withType(String type)
{
this.type = type;
return (this);

View File

@ -85,7 +85,7 @@ public class QFieldMetaData implements Cloneable
private List<FieldAdornment> adornments;
private Map<String, QMiddlewareFieldMetaData> middlewareMetaData;
private Map<String, QSupplementalFieldMetaData> supplementalMetaData;
@ -840,60 +840,60 @@ public class QFieldMetaData implements Cloneable
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public Map<String, QMiddlewareFieldMetaData> getMiddlewareMetaData()
public Map<String, QSupplementalFieldMetaData> getSupplementalMetaData()
{
return (this.middlewareMetaData);
return (this.supplementalMetaData);
}
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public QMiddlewareFieldMetaData getMiddlewareMetaData(String type)
public QSupplementalFieldMetaData getSupplementalMetaData(String type)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
return (null);
}
return this.middlewareMetaData.get(type);
return this.supplementalMetaData.get(type);
}
/*******************************************************************************
** Setter for middlewareMetaData
** Setter for supplementalMetaData
*******************************************************************************/
public void setMiddlewareMetaData(Map<String, QMiddlewareFieldMetaData> middlewareMetaData)
public void setSupplementalMetaData(Map<String, QSupplementalFieldMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QFieldMetaData withMiddlewareMetaData(Map<String, QMiddlewareFieldMetaData> middlewareMetaData)
public QFieldMetaData withSupplementalMetaData(Map<String, QSupplementalFieldMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
return (this);
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QFieldMetaData withMiddlewareMetaData(QMiddlewareFieldMetaData middlewareMetaData)
public QFieldMetaData withSupplementalMetaData(QSupplementalFieldMetaData supplementalMetaData)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
this.middlewareMetaData = new HashMap<>();
this.supplementalMetaData = new HashMap<>();
}
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
return (this);
}

View File

@ -23,9 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
/*******************************************************************************
** Base-class for field-level meta-data defined for a specific middleware.
** Base-class for field-level meta-data defined by some supplemental module, etc,
** outside of qqq core
*******************************************************************************/
public abstract class QMiddlewareFieldMetaData
public abstract class QSupplementalFieldMetaData
{
protected String type;
@ -54,7 +55,7 @@ public abstract class QMiddlewareFieldMetaData
/*******************************************************************************
** Fluent setter for type
*******************************************************************************/
public QMiddlewareFieldMetaData withType(String type)
public QSupplementalFieldMetaData withType(String type)
{
this.type = type;
return (this);

View File

@ -77,6 +77,21 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
/*******************************************************************************
** Create a new possible value source, for a table, with default settings.
** e.g., name & table name from the tableName parameter; type=TABLE; and LABEL_ONLY format
*******************************************************************************/
public static QPossibleValueSource newForTable(String tableName)
{
return new QPossibleValueSource()
.withName(tableName)
.withType(QPossibleValueSourceType.TABLE)
.withTableName(tableName)
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -54,6 +54,9 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
private BasepullConfiguration basepullConfiguration;
private QPermissionRules permissionRules;
private Integer minInputRecords = null;
private Integer maxInputRecords = null;
private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in
private Map<String, QStepMetaData> steps; // this is the full map of possible steps
@ -61,6 +64,8 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
private QScheduleMetaData schedule;
private Map<String, QSupplementalProcessMetaData> supplementalMetaData;
/*******************************************************************************
@ -544,4 +549,126 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
qInstance.addProcess(this);
}
/*******************************************************************************
** Getter for supplementalMetaData
*******************************************************************************/
public Map<String, QSupplementalProcessMetaData> getSupplementalMetaData()
{
return (this.supplementalMetaData);
}
/*******************************************************************************
** Getter for supplementalMetaData
*******************************************************************************/
public QSupplementalProcessMetaData getSupplementalMetaData(String type)
{
if(this.supplementalMetaData == null)
{
return (null);
}
return this.supplementalMetaData.get(type);
}
/*******************************************************************************
** Setter for supplementalMetaData
*******************************************************************************/
public void setSupplementalMetaData(Map<String, QSupplementalProcessMetaData> supplementalMetaData)
{
this.supplementalMetaData = supplementalMetaData;
}
/*******************************************************************************
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QProcessMetaData withSupplementalMetaData(Map<String, QSupplementalProcessMetaData> supplementalMetaData)
{
this.supplementalMetaData = supplementalMetaData;
return (this);
}
/*******************************************************************************
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QProcessMetaData withSupplementalMetaData(QSupplementalProcessMetaData supplementalMetaData)
{
if(this.supplementalMetaData == null)
{
this.supplementalMetaData = new HashMap<>();
}
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
return (this);
}
/*******************************************************************************
** Getter for minInputRecords
*******************************************************************************/
public Integer getMinInputRecords()
{
return (this.minInputRecords);
}
/*******************************************************************************
** Setter for minInputRecords
*******************************************************************************/
public void setMinInputRecords(Integer minInputRecords)
{
this.minInputRecords = minInputRecords;
}
/*******************************************************************************
** Fluent setter for minInputRecords
*******************************************************************************/
public QProcessMetaData withMinInputRecords(Integer minInputRecords)
{
this.minInputRecords = minInputRecords;
return (this);
}
/*******************************************************************************
** Getter for maxInputRecords
*******************************************************************************/
public Integer getMaxInputRecords()
{
return (this.maxInputRecords);
}
/*******************************************************************************
** Setter for maxInputRecords
*******************************************************************************/
public void setMaxInputRecords(Integer maxInputRecords)
{
this.maxInputRecords = maxInputRecords;
}
/*******************************************************************************
** Fluent setter for maxInputRecords
*******************************************************************************/
public QProcessMetaData withMaxInputRecords(Integer maxInputRecords)
{
this.maxInputRecords = maxInputRecords;
return (this);
}
}

View File

@ -0,0 +1,92 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Base-class for process-level meta-data defined by some supplemental module, etc,
** outside of qqq core
*******************************************************************************/
public abstract class QSupplementalProcessMetaData
{
protected String type;
/*******************************************************************************
** Getter for type
*******************************************************************************/
public String getType()
{
return (this.type);
}
/*******************************************************************************
** Setter for type
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
** Fluent setter for type
*******************************************************************************/
public QSupplementalProcessMetaData withType(String type)
{
this.type = type;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void enrich(QInstanceEnricher qInstanceEnricher, QProcessMetaData process)
{
////////////////////////
// noop in base class //
////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
public void validate(QInstance qInstance, QProcessMetaData process, QInstanceValidator qInstanceValidator)
{
////////////////////////
// noop in base class //
////////////////////////
}
}

View File

@ -23,9 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
/*******************************************************************************
** Base-class for table-level meta-data defined for a specific middleware.
** Base-class for table-level meta-data defined by some supplemental module, etc,
** outside of qqq core
*******************************************************************************/
public abstract class QMiddlewareTableMetaData
public abstract class QSupplementalTableMetaData
{
protected String type;
@ -54,7 +55,7 @@ public abstract class QMiddlewareTableMetaData
/*******************************************************************************
** Fluent setter for type
*******************************************************************************/
public QMiddlewareTableMetaData withType(String type)
public QSupplementalTableMetaData withType(String type)
{
this.type = type;
return (this);

View File

@ -99,7 +99,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
private CacheOf cacheOf;
private Map<String, QMiddlewareTableMetaData> middlewareMetaData;
private Map<String, QSupplementalTableMetaData> supplementalMetaData;
private List<ExposedJoin> exposedJoins;
@ -1203,60 +1203,60 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public Map<String, QMiddlewareTableMetaData> getMiddlewareMetaData()
public Map<String, QSupplementalTableMetaData> getSupplementalMetaData()
{
return (this.middlewareMetaData);
return (this.supplementalMetaData);
}
/*******************************************************************************
** Getter for middlewareMetaData
** Getter for supplementalMetaData
*******************************************************************************/
public QMiddlewareTableMetaData getMiddlewareMetaData(String type)
public QSupplementalTableMetaData getSupplementalMetaData(String type)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
return (null);
}
return this.middlewareMetaData.get(type);
return this.supplementalMetaData.get(type);
}
/*******************************************************************************
** Setter for middlewareMetaData
** Setter for supplementalMetaData
*******************************************************************************/
public void setMiddlewareMetaData(Map<String, QMiddlewareTableMetaData> middlewareMetaData)
public void setSupplementalMetaData(Map<String, QSupplementalTableMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QTableMetaData withMiddlewareMetaData(Map<String, QMiddlewareTableMetaData> middlewareMetaData)
public QTableMetaData withSupplementalMetaData(Map<String, QSupplementalTableMetaData> supplementalMetaData)
{
this.middlewareMetaData = middlewareMetaData;
this.supplementalMetaData = supplementalMetaData;
return (this);
}
/*******************************************************************************
** Fluent setter for middlewareMetaData
** Fluent setter for supplementalMetaData
*******************************************************************************/
public QTableMetaData withMiddlewareMetaData(QMiddlewareTableMetaData middlewareMetaData)
public QTableMetaData withSupplementalMetaData(QSupplementalTableMetaData supplementalMetaData)
{
if(this.middlewareMetaData == null)
if(this.supplementalMetaData == null)
{
this.middlewareMetaData = new HashMap<>();
this.supplementalMetaData = new HashMap<>();
}
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
return (this);
}

View File

@ -0,0 +1,283 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.savedfilters;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Entity bean for the saved filter table
*******************************************************************************/
public class SavedFilter extends QRecordEntity
{
public static final String TABLE_NAME = "savedFilter";
@QField(isEditable = false)
private Integer id;
@QField(isEditable = false)
private Instant createDate;
@QField(isEditable = false)
private Instant modifyDate;
@QField(isRequired = true)
private String label;
@QField(isEditable = false)
private String tableName;
@QField(isEditable = false)
private String userId;
@QField(isEditable = false)
private String filterJson;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public SavedFilter()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public SavedFilter(QRecord qRecord) throws QException
{
populateFromQRecord(qRecord);
}
/*******************************************************************************
** Getter for id
**
*******************************************************************************/
public Integer getId()
{
return id;
}
/*******************************************************************************
** Setter for id
**
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Setter for label
**
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
**
*******************************************************************************/
public SavedFilter withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public SavedFilter withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for userId
**
*******************************************************************************/
public String getUserId()
{
return userId;
}
/*******************************************************************************
** Setter for userId
**
*******************************************************************************/
public void setUserId(String userId)
{
this.userId = userId;
}
/*******************************************************************************
** Fluent setter for userId
**
*******************************************************************************/
public SavedFilter withUserId(String userId)
{
this.userId = userId;
return (this);
}
/*******************************************************************************
** Getter for filterJson
**
*******************************************************************************/
public String getFilterJson()
{
return filterJson;
}
/*******************************************************************************
** Setter for filterJson
**
*******************************************************************************/
public void setFilterJson(String filterJson)
{
this.filterJson = filterJson;
}
/*******************************************************************************
** Fluent setter for filterJson
**
*******************************************************************************/
public SavedFilter withFilterJson(String filterJson)
{
this.filterJson = filterJson;
return (this);
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.model.savedfilters;
import java.util.function.Consumer;
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.possiblevalues.PVSValueFormatAndFields;
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.processes.implementations.savedfilters.DeleteSavedFilterProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.savedfilters.QuerySavedFilterProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.savedfilters.StoreSavedFilterProcess;
/*******************************************************************************
**
*******************************************************************************/
public class SavedFiltersMetaDataProvider
{
/*******************************************************************************
**
*******************************************************************************/
public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
instance.addTable(defineSavedFilterTable(backendName, backendDetailEnricher));
instance.addPossibleValueSource(defineSavedFilterPossibleValueSource());
instance.addProcess(QuerySavedFilterProcess.getProcessMetaData());
instance.addProcess(StoreSavedFilterProcess.getProcessMetaData());
instance.addProcess(DeleteSavedFilterProcess.getProcessMetaData());
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineSavedFilterTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
QTableMetaData table = new QTableMetaData()
.withName(SavedFilter.TABLE_NAME)
.withLabel("Saved Filter")
.withRecordLabelFormat("%s")
.withRecordLabelFields("label")
.withBackendName(backendName)
.withPrimaryKeyField("id")
.withFieldsFromEntity(SavedFilter.class);
if(backendDetailEnricher != null)
{
backendDetailEnricher.accept(table);
}
return (table);
}
/*******************************************************************************
**
*******************************************************************************/
private QPossibleValueSource defineSavedFilterPossibleValueSource()
{
return new QPossibleValueSource()
.withName(SavedFilter.TABLE_NAME)
.withType(QPossibleValueSourceType.TABLE)
.withTableName(SavedFilter.TABLE_NAME)
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
}
}

View File

@ -102,6 +102,8 @@ public class ScriptsMetaDataProvider
{
return (new QProcessMetaData()
.withName(STORE_SCRIPT_REVISION_PROCESS_NAME)
.withTableName(Script.TABLE_NAME)
.withIsHidden(true)
.withStepList(List.of(
new QBackendStepMetaData()
.withName("main")
@ -118,6 +120,8 @@ public class ScriptsMetaDataProvider
{
return (new QProcessMetaData()
.withName(TEST_SCRIPT_PROCESS_NAME)
.withTableName(Script.TABLE_NAME)
.withIsHidden(true)
.withStepList(List.of(
new QBackendStepMetaData()
.withName("main")
@ -327,7 +331,12 @@ public class ScriptsMetaDataProvider
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
tableMetaData.getField("scriptId").withPossibleValueSourceFilter(new QQueryFilter(
new QFilterCriteria("scriptType.name", QCriteriaOperator.EQUALS, SCRIPT_TYPE_NAME_RECORD)
new QFilterCriteria("scriptType.name", QCriteriaOperator.EQUALS, SCRIPT_TYPE_NAME_RECORD),
new QFilterCriteria("script.tableName", QCriteriaOperator.EQUALS, "${input.tableName}")
));
tableMetaData.getField("filterId").withPossibleValueSourceFilter(new QQueryFilter(
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, "${input.tableName}")
));
return tableMetaData;

View File

@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes.implementations.etl.streamedwithfrontend;
import com.kingsrook.qqq.backend.core.exceptions.QException;
/*******************************************************************************
**
*******************************************************************************/
public class CouldNotFindQueryFilterForExtractStepException extends QException
{
/*******************************************************************************
**
*******************************************************************************/
public CouldNotFindQueryFilterForExtractStepException(String message)
{
super(message);
}
}

View File

@ -223,7 +223,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, idStrings)));
}
throw (new QException("Could not find query filter for Extract step."));
throw (new CouldNotFindQueryFilterForExtractStepException("Could not find query filter for Extract step."));
}

View File

@ -27,6 +27,7 @@ import java.util.List;
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
@ -71,11 +72,14 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
return;
}
if(runBackendStepInput.getFrontendStepBehavior() != null && runBackendStepInput.getFrontendStepBehavior().equals(RunProcessInput.FrontendStepBehavior.SKIP))
{
LOG.debug("Skipping preview because frontend behavior is [" + RunProcessInput.FrontendStepBehavior.SKIP + "].");
return;
}
//////////////////////////////
// set up the extract steps //
//////////////////////////////
AbstractExtractStep extractStep = getExtractStep(runBackendStepInput);
RecordPipe recordPipe = new RecordPipe();
extractStep.setLimit(limit);
extractStep.setRecordPipe(recordPipe);
extractStep.preRun(runBackendStepInput, runBackendStepOutput);
/////////////////////////////////////////////////////////////////
// if we're running inside an automation, then skip this step. //
@ -86,17 +90,26 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
return;
}
//////////////////////////////////////////
// set up the extract & transform steps //
//////////////////////////////////////////
AbstractExtractStep extractStep = getExtractStep(runBackendStepInput);
RecordPipe recordPipe = new RecordPipe();
extractStep.setLimit(limit);
extractStep.setRecordPipe(recordPipe);
extractStep.preRun(runBackendStepInput, runBackendStepOutput);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if skipping frontend steps, skip this action - //
// but, if inside an (ideally, only async) API call, at least do the count, so status calls can get x of y status //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(RunProcessInput.FrontendStepBehavior.SKIP.equals(runBackendStepInput.getFrontendStepBehavior()))
{
if(QContext.getQSession().getValue("apiVersion") != null)
{
countRecords(runBackendStepInput, runBackendStepOutput, extractStep);
}
LOG.debug("Skipping preview because frontend behavior is [" + RunProcessInput.FrontendStepBehavior.SKIP + "].");
return;
}
countRecords(runBackendStepInput, runBackendStepOutput, extractStep);
//////////////////////////////
// setup the transform step //
//////////////////////////////
AbstractTransformStep transformStep = getTransformStep(runBackendStepInput);
transformStep.preRun(runBackendStepInput, runBackendStepOutput);

View File

@ -407,6 +407,30 @@ public class StreamedETLWithFrontendProcess
/*******************************************************************************
** Fluent setter for minInputRecords
**
*******************************************************************************/
public Builder withMinInputRecords(Integer minInputRecords)
{
processMetaData.setMinInputRecords(minInputRecords);
return (this);
}
/*******************************************************************************
** Fluent setter for maxInputRecords
**
*******************************************************************************/
public Builder withMaxInputRecords(Integer maxInputRecords)
{
processMetaData.setMaxInputRecords(maxInputRecords);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,88 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes.implementations.savedfilters;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
/*******************************************************************************
** Process used by the delete filter dialog
*******************************************************************************/
public class DeleteSavedFilterProcess implements BackendStep
{
private static final QLogger LOG = QLogger.getLogger(DeleteSavedFilterProcess.class);
/*******************************************************************************
**
*******************************************************************************/
public static QProcessMetaData getProcessMetaData()
{
return (new QProcessMetaData()
.withName("deleteSavedFilter")
.withStepList(List.of(
new QBackendStepMetaData()
.withCode(new QCodeReference(DeleteSavedFilterProcess.class))
.withName("delete")
)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
ActionHelper.validateSession(runBackendStepInput);
try
{
Integer savedFilterId = runBackendStepInput.getValueInteger("id");
DeleteInput input = new DeleteInput();
input.setTableName(SavedFilter.TABLE_NAME);
input.setPrimaryKeys(List.of(savedFilterId));
new DeleteAction().execute(input);
}
catch(Exception e)
{
LOG.warn("Error deleting saved filter", e);
throw (e);
}
}
}

View File

@ -0,0 +1,117 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes.implementations.savedfilters;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
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.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
/*******************************************************************************
** Process used by the saved filter dialogs
*******************************************************************************/
public class QuerySavedFilterProcess implements BackendStep
{
private static final QLogger LOG = QLogger.getLogger(QuerySavedFilterProcess.class);
/*******************************************************************************
**
*******************************************************************************/
public static QProcessMetaData getProcessMetaData()
{
return (new QProcessMetaData()
.withName("querySavedFilter")
.withStepList(List.of(
new QBackendStepMetaData()
.withCode(new QCodeReference(QuerySavedFilterProcess.class))
.withName("query")
)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
ActionHelper.validateSession(runBackendStepInput);
try
{
Integer savedFilterId = runBackendStepInput.getValueInteger("id");
if(savedFilterId != null)
{
GetInput input = new GetInput();
input.setTableName(SavedFilter.TABLE_NAME);
input.setPrimaryKey(savedFilterId);
GetOutput output = new GetAction().execute(input);
runBackendStepOutput.addRecord(output.getRecord());
runBackendStepOutput.addValue("savedFilter", output.getRecord());
runBackendStepOutput.addValue("savedFilterList", (Serializable) List.of(output.getRecord()));
}
else
{
String tableName = runBackendStepInput.getValueString("tableName");
QueryInput input = new QueryInput();
input.setTableName(SavedFilter.TABLE_NAME);
input.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, tableName))
.withOrderBy(new QFilterOrderBy("label")));
QueryOutput output = new QueryAction().execute(input);
runBackendStepOutput.setRecords(output.getRecords());
runBackendStepOutput.addValue("savedFilterList", (Serializable) output.getRecords());
}
}
catch(Exception e)
{
LOG.warn("Error deleting saved filter", e);
throw (e);
}
}
}

View File

@ -0,0 +1,117 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes.implementations.savedfilters;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
/*******************************************************************************
** Process used by the saved filter dialog
*******************************************************************************/
public class StoreSavedFilterProcess implements BackendStep
{
private static final QLogger LOG = QLogger.getLogger(StoreSavedFilterProcess.class);
/*******************************************************************************
**
*******************************************************************************/
public static QProcessMetaData getProcessMetaData()
{
return (new QProcessMetaData()
.withName("storeSavedFilter")
.withStepList(List.of(
new QBackendStepMetaData()
.withCode(new QCodeReference(StoreSavedFilterProcess.class))
.withName("store")
)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
ActionHelper.validateSession(runBackendStepInput);
try
{
QRecord qRecord = new QRecord()
.withValue("id", runBackendStepInput.getValueInteger("id"))
.withValue("label", runBackendStepInput.getValueString("label"))
.withValue("tableName", runBackendStepInput.getValueString("tableName"))
.withValue("filterJson", runBackendStepInput.getValueString("filterJson"))
.withValue("userId", runBackendStepInput.getSession().getUser().getIdReference());
List<QRecord> savedFilterList = new ArrayList<>();
if(qRecord.getValueInteger("id") == null)
{
InsertInput input = new InsertInput();
input.setTableName(SavedFilter.TABLE_NAME);
input.setRecords(List.of(qRecord));
InsertOutput output = new InsertAction().execute(input);
savedFilterList = output.getRecords();
}
else
{
UpdateInput input = new UpdateInput();
input.setTableName(SavedFilter.TABLE_NAME);
input.setRecords(List.of(qRecord));
UpdateOutput output = new UpdateAction().execute(input);
savedFilterList = output.getRecords();
}
runBackendStepOutput.addValue("savedFilterList", (Serializable) savedFilterList);
}
catch(Exception e)
{
LOG.warn("Error storing data saved filter", e);
throw (e);
}
}
}

View File

@ -22,7 +22,9 @@
package com.kingsrook.qqq.backend.core.utils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -60,6 +62,45 @@ public class ExceptionUtils
/*******************************************************************************
** Find a list of exceptions of the given class in an exception's caused-by chain.
** Returns empty list if none found.
**
*******************************************************************************/
public static <T extends Throwable> List<T> getClassListFromRootChain(Throwable e, Class<T> targetClass)
{
List<T> throwableList = new ArrayList<>();
if(targetClass.isInstance(e))
{
throwableList.add(targetClass.cast(e));
}
///////////////////////////////////////////////////
// iterate through the chain with a limit of 100 //
///////////////////////////////////////////////////
int counter = 0;
while(counter++ < 100)
{
////////////////////////////////////////////////////////////////////////
// look for the same class from the last throwable found of that type //
////////////////////////////////////////////////////////////////////////
e = findClassInRootChain(e.getCause(), targetClass);
if(e == null)
{
break;
}
////////////////////////////////////////////////////////////////////////
// if we did not break, higher one must have been found, keep looking //
////////////////////////////////////////////////////////////////////////
throwableList.add(targetClass.cast(e));
}
return (throwableList);
}
/*******************************************************************************
** Get the root exception in a caused-by-chain.
**
@ -88,4 +129,39 @@ public class ExceptionUtils
return (root);
}
/*******************************************************************************
**
*******************************************************************************/
public static String concatenateMessagesFromChain(Exception exception)
{
if(exception == null)
{
return (null);
}
List<String> messages = new ArrayList<>();
Throwable root = exception;
Set<Throwable> seen = new HashSet<>();
do
{
if(StringUtils.hasContent(root.getMessage()))
{
messages.add(root.getMessage());
}
else
{
messages.add(root.getClass().getSimpleName());
}
seen.add(root);
root = root.getCause();
}
while(root != null && !seen.contains(root));
return (StringUtils.join("; ", messages));
}
}

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.utils;
import java.util.function.Consumer;
import java.util.function.Predicate;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
@ -96,4 +99,44 @@ public class ObjectUtils
return (defaultIfThrew);
}
/*******************************************************************************
**
*******************************************************************************/
public static <T> void ifNotNull(T object, Consumer<T> consumer)
{
if(object != null)
{
consumer.accept(object);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static <T, E extends Exception> void ifNotNullUnsafe(T object, UnsafeConsumer<T, E> consumer) throws E
{
if(object != null)
{
consumer.run(object);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static <T> T requireConditionElse(T a, Predicate<T> condition, T b)
{
if(condition.test(a))
{
return (a);
}
return (b);
}
}

View File

@ -35,18 +35,18 @@ import java.util.function.Supplier;
**
** Can use it 2 ways:
** MapBuilder.of(key, value, key2, value2, ...) => Map (a HashMap)
** MapBuilder.<KeyType ValueType>of(SomeMap::new).with(key, value).with(key2, value2)...build() => SomeMap (the type you specify)
** MapBuilder.of(() -> new SomeMap<SomeKeyType, SomeValueType>()).with(key, value).with(key2, value2)...build() => SomeMap (the type you specify)
*******************************************************************************/
public class MapBuilder<K, V>
public class MapBuilder<K, V, M extends Map<K, V>>
{
private Map<K, V> map;
private M map;
/*******************************************************************************
**
*******************************************************************************/
private MapBuilder(Map<K, V> map)
private MapBuilder(M map)
{
this.map = map;
}
@ -56,7 +56,7 @@ public class MapBuilder<K, V>
/*******************************************************************************
**
*******************************************************************************/
public static <K, V> MapBuilder<K, V> of(Supplier<Map<K, V>> mapSupplier)
public static <K, V, M extends Map<K, V>> MapBuilder<K, V, M> of(Supplier<M> mapSupplier)
{
return (new MapBuilder<>(mapSupplier.get()));
}
@ -66,7 +66,7 @@ public class MapBuilder<K, V>
/*******************************************************************************
**
*******************************************************************************/
public MapBuilder<K, V> with(K key, V value)
public MapBuilder<K, V, M> with(K key, V value)
{
map.put(key, value);
return (this);
@ -77,7 +77,7 @@ public class MapBuilder<K, V>
/*******************************************************************************
**
*******************************************************************************/
public Map<K, V> build()
public M build()
{
return (this.map);
}

View File

@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
@ -47,7 +48,10 @@ public class BaseTest
@BeforeEach
void baseBeforeEach()
{
QContext.init(TestUtils.defineInstance(), new QSession());
QContext.init(TestUtils.defineInstance(), new QSession()
.withUser(new QUser()
.withIdReference("001")
.withFullName("Anonymous")));
resetMemoryRecordStore();
}

View File

@ -197,6 +197,16 @@ class QInstanceEnricherTest extends BaseTest
assertEquals("Something USA", QInstanceEnricher.nameToLabel("somethingUSA"));
assertEquals("Number 1 Dad", QInstanceEnricher.nameToLabel("number1Dad"));
assertEquals("Number 417 Dad", QInstanceEnricher.nameToLabel("number417Dad"));
assertEquals("Default Wms System Id", QInstanceEnricher.nameToLabel("defaultWmsSystemId"));
QInstanceEnricher.addLabelMapping("\\bWms\\b", "WMS");
assertEquals("Default WMS System Id", QInstanceEnricher.nameToLabel("defaultWmsSystemId"));
QInstanceEnricher.clearLabelMappings();
assertEquals("Api Client Id", QInstanceEnricher.nameToLabel("apiClientId"));
QInstanceEnricher.addLabelMapping("\\bApi\\b", "API");
assertEquals("API Client Id", QInstanceEnricher.nameToLabel("apiClientId"));
QInstanceEnricher.clearLabelMappings();
}

View File

@ -0,0 +1,190 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.logging;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.filter.LevelRangeFilter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Unit test for com.kingsrook.qqq.backend.core.logging.QLogger
**
*******************************************************************************/
@Disabled // disabled because could never get the custom appender class to receive logEvents that have their levels set (always null)
class QLoggerTest extends BaseTest
{
private static final QLogger LOG = QLogger.getLogger(QLoggerTest.class);
private static final ListAppender listAppender = ListAppender.createAppender();
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeAll() throws Exception
{
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDowngradingWarnings() throws Exception
{
org.apache.logging.log4j.core.Logger logger = (org.apache.logging.log4j.core.Logger) LogManager.getLogger(QLoggerTest.class);
logger.addAppender(listAppender);
listAppender.start();
try
{
try
{
try
{
try
{
throw (new QException("Some deepest exception..."));
}
catch(Exception e)
{
String warning = "Less deep warning";
LOG.warn(warning, e);
throw (new QException(warning, e));
}
}
catch(Exception e2)
{
String warning = "Middle warning";
LOG.warn(warning, e2);
throw (new QException(warning, e2));
}
}
catch(Exception e2)
{
String warning = "Last warning";
LOG.warn(warning, e2);
throw (new QException(warning, e2));
}
}
catch(Exception e3)
{
/////////////////////////
// check results below //
/////////////////////////
}
assertThat(listAppender.getEventList()).isNotNull();
assertThat(listAppender.getEventList().size()).isEqualTo(5);
int counter = 0;
for(LogEvent logEvent : listAppender.getEventList())
{
if(counter == 0)
{
assertThat(logEvent.getLevel()).isEqualTo(Level.WARN);
}
else
{
assertThat(logEvent.getLevel()).isEqualTo(Level.INFO);
}
counter++;
}
}
/*******************************************************************************
** appender to add to logger to keep a list of log events
*******************************************************************************/
@Plugin(name = "ListAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public static class ListAppender extends AbstractAppender
{
private List<LogEvent> eventList = new ArrayList<>();
/*******************************************************************************
**
*******************************************************************************/
protected ListAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final boolean ignoreExceptions, final Property[] properties)
{
super(name, filter, layout, ignoreExceptions, properties);
}
/*******************************************************************************
**
*******************************************************************************/
@PluginFactory
public static ListAppender createAppender()
{
LevelRangeFilter levelRangeFilter = LevelRangeFilter.createFilter(Level.TRACE, Level.ERROR, Filter.Result.ACCEPT, Filter.Result.ACCEPT);
// return (new ListAppender("ListApppender", levelRangeFilter, null, true, null));
return (new ListAppender("ListApppender", null, null, true, null));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void append(LogEvent event)
{
eventList.add(event);
}
/*******************************************************************************
** Getter for eventList
*******************************************************************************/
public List<LogEvent> getEventList()
{
return (this.eventList);
}
}
}

View File

@ -0,0 +1,143 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.processes.implementations.savedfilters;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFiltersMetaDataProvider;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
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;
/*******************************************************************************
** Unit test for all saved filter processes
*******************************************************************************/
class SavedFilterProcessTests extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance qInstance = QContext.getQInstance();
new SavedFiltersMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
String tableName = TestUtils.TABLE_NAME_PERSON_MEMORY;
{
///////////////////////////////////////////
// query - should be no filters to start //
///////////////////////////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("tableName", tableName);
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedFilterList")).size());
}
Integer savedFilterId;
{
////////////////////////
// store a new filter //
////////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(StoreSavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("label", "My Filter");
runProcessInput.addValue("tableName", tableName);
runProcessInput.addValue("filterJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
assertEquals(1, savedFilterList.size());
savedFilterId = savedFilterList.get(0).getValueInteger("id");
assertNotNull(savedFilterId);
}
{
////////////////////////////////////
// query - should find our filter //
////////////////////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("tableName", tableName);
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
assertEquals(1, savedFilterList.size());
assertEquals(1, savedFilterList.get(0).getValueInteger("id"));
assertEquals("My Filter", savedFilterList.get(0).getValueString("label"));
}
{
///////////////////////
// update our filter //
///////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(StoreSavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("id", savedFilterId);
runProcessInput.addValue("label", "My Updated Filter");
runProcessInput.addValue("tableName", tableName);
runProcessInput.addValue("filterJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
assertEquals(1, savedFilterList.size());
assertEquals(1, savedFilterList.get(0).getValueInteger("id"));
assertEquals("My Updated Filter", savedFilterList.get(0).getValueString("label"));
}
{
///////////////////////
// delete our filter //
///////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(DeleteSavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("id", savedFilterId);
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
}
{
////////////////////////////////////////
// query - should be no filters again //
////////////////////////////////////////
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
runProcessInput.addValue("tableName", tableName);
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedFilterList")).size());
}
}
}

View File

@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
@ -88,6 +89,33 @@ class ExceptionUtilsTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testConcatenateMessagesFromChain()
{
assertNull(ExceptionUtils.concatenateMessagesFromChain(null));
assertEquals("QException", ExceptionUtils.concatenateMessagesFromChain(new QException((String) null)));
assertEquals("QException", ExceptionUtils.concatenateMessagesFromChain(new QException("")));
assertEquals("foo; bar", ExceptionUtils.concatenateMessagesFromChain(new QException("foo", new QException("bar"))));
assertEquals("foo; QException; bar", ExceptionUtils.concatenateMessagesFromChain(new QException("foo", new QException(null, new QException("bar")))));
MyException selfCaused = new MyException("selfCaused");
selfCaused.setCause(selfCaused);
assertEquals("selfCaused", ExceptionUtils.concatenateMessagesFromChain(selfCaused));
MyException cycle1 = new MyException("cycle1");
MyException cycle2 = new MyException("cycle2");
cycle1.setCause(cycle2);
cycle2.setCause(cycle1);
assertEquals("cycle1; cycle2", ExceptionUtils.concatenateMessagesFromChain(cycle1));
assertEquals("cycle2; cycle1", ExceptionUtils.concatenateMessagesFromChain(cycle2));
}
/*******************************************************************************
** Test exception class - lets you set the cause, easier to create a loop.
*******************************************************************************/
@ -97,6 +125,9 @@ class ExceptionUtilsTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
public MyException(String message)
{
super(message);
@ -104,6 +135,9 @@ class ExceptionUtilsTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
public MyException(Throwable cause)
{
super(cause);
@ -111,6 +145,9 @@ class ExceptionUtilsTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
public void setCause(Throwable cause)
{
myCause = cause;

View File

@ -78,7 +78,7 @@ class MapBuilderTest
@Test
void testTypeYouRequest()
{
Map<String, Integer> myTreeMap = MapBuilder.<String, Integer>of(TreeMap::new).with("1", 1).with("2", 2).build();
TreeMap<String, Integer> myTreeMap = MapBuilder.of(() -> new TreeMap<String, Integer>()).with("1", 1).with("2", 2).build();
assertTrue(myTreeMap instanceof TreeMap);
}